Skip to content

Commit ce365fd

Browse files
committed
feat: display occurrences in std search usage (#642)
Signed-off-by: Andre Dietisheim <[email protected]>
1 parent 5ab207f commit ce365fd

29 files changed

+2556
-485
lines changed

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/editor/ResourceEditorFactory.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,6 @@ open class ResourceEditorFactory protected constructor(
171171

172172
/* for testing purposes */
173173
protected open fun getTelemetryMessageBuilder(): TelemetryMessageBuilder {
174-
return TelemetryService.instance;
174+
return TelemetryService.instance
175175
}
176176
}

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/editor/inlay/ResourceEditorInlayHintsProvider.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import com.intellij.codeInsight.hints.InlayHintsSink
2121
import com.intellij.codeInsight.hints.NoSettings
2222
import com.intellij.codeInsight.hints.SettingsKey
2323
import com.intellij.json.psi.JsonFile
24-
import com.intellij.openapi.application.ApplicationManager
2524
import com.intellij.openapi.application.ReadAction
2625
import com.intellij.openapi.editor.Editor
2726
import com.intellij.psi.PsiElement
@@ -83,7 +82,7 @@ internal class ResourceEditorInlayHintsProvider : InlayHintsProvider<NoSettings>
8382
val info = KubernetesTypeInfo.create(document) ?: return@forEach
8483
val element = document.topLevelValue ?: return@forEach
8584
Base64Presentations.create(element, info, sink, editor)
86-
SelectorPresentations.create(element, file, info, sink, editor)
85+
SelectorPresentations.create(element, sink, editor)
8786
}
8887
}
8988
}

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/editor/inlay/base64/Base64Presentations.kt

+11-8
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,12 @@ object Base64Presentations {
4343
fun create(element: PsiElement, info: KubernetesTypeInfo, sink: InlayHintsSink, editor: Editor): Collection<InlayPresentation>? {
4444
return when {
4545
isKubernetesResource(SECRET_RESOURCE_KIND, info) -> {
46-
val data = getDataValue(element) ?: return null
46+
val data = element.getDataValue() ?: return null
4747
StringPresentationsFactory(data, sink, editor).create()
4848
}
4949

5050
isKubernetesResource(CONFIGMAP_RESOURCE_KIND, info) -> {
51-
val binaryData = getBinaryData(element) ?: return null
51+
val binaryData = element.getBinaryData() ?: return null
5252
BinaryPresentationsFactory(binaryData, sink, editor).create()
5353
}
5454

@@ -96,12 +96,15 @@ object Base64Presentations {
9696
private fun create(text: String, onClick: (event: MouseEvent) -> Unit, editor: Editor): InlayPresentation? {
9797
val factory = PresentationFactory(editor)
9898
val trimmed = trimWithEllipsis(text, INLAY_HINT_MAX_WIDTH) ?: return null
99-
val textPresentation = factory.smallText(trimmed)
100-
val hoverPresentation = factory.referenceOnHover(textPresentation) { event, _ ->
101-
onClick.invoke(event)
102-
}
103-
val tooltipPresentation = factory.withTooltip("Click to change value", hoverPresentation)
104-
return factory.roundWithBackground(tooltipPresentation)
99+
return factory.roundWithBackground(
100+
factory.withTooltip(
101+
"Click to change value",
102+
factory.referenceOnHover(
103+
factory.smallText(trimmed)) { event, _ ->
104+
onClick.invoke(event)
105+
}
106+
)
107+
)
105108
}
106109

107110
private fun onValidValue(setter: (value: String, wrapAt: Int) -> Unit, project: Project?)

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/editor/inlay/base64/Base64ValueAdapter.kt

+7-1
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010
******************************************************************************/
1111
package com.redhat.devtools.intellij.kubernetes.editor.inlay.base64
1212

13+
import com.intellij.json.psi.JsonProperty
1314
import com.intellij.psi.PsiElement
1415
import com.redhat.devtools.intellij.kubernetes.editor.util.decodeBase64
1516
import com.redhat.devtools.intellij.kubernetes.editor.util.decodeBase64ToBytes
1617
import com.redhat.devtools.intellij.kubernetes.editor.util.encodeBase64
1718
import com.redhat.devtools.intellij.kubernetes.editor.util.getValue
1819
import com.redhat.devtools.intellij.kubernetes.editor.util.setValue
20+
import org.jetbrains.yaml.psi.YAMLKeyValue
1921

2022
class Base64ValueAdapter(private val element: PsiElement) {
2123

@@ -80,6 +82,10 @@ class Base64ValueAdapter(private val element: PsiElement) {
8082
}
8183

8284
fun getStartOffset(): Int? {
83-
return com.redhat.devtools.intellij.kubernetes.editor.util.getStartOffset(element)
85+
return when(element) {
86+
is YAMLKeyValue -> element.value?.textRange?.startOffset
87+
is JsonProperty -> element.value?.textRange?.startOffset
88+
else -> null
89+
}
8490
}
8591
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Red Hat, Inc.
3+
* Distributed under license by Red Hat, Inc. All rights reserved.
4+
* This program is made available under the terms of the
5+
* Eclipse Public License v2.0 which accompanies this distribution,
6+
* and is available at http://www.eclipse.org/legal/epl-v20.html
7+
*
8+
* Contributors:
9+
* Red Hat, Inc. - initial API and implementation
10+
******************************************************************************/
11+
package com.redhat.devtools.intellij.kubernetes.editor.inlay.selector
12+
13+
import com.intellij.psi.PsiElement
14+
import com.redhat.devtools.intellij.common.validation.KubernetesTypeInfo
15+
import com.redhat.devtools.intellij.kubernetes.editor.util.areMatchingMatchExpressions
16+
import com.redhat.devtools.intellij.kubernetes.editor.util.areMatchingMatchLabels
17+
import com.redhat.devtools.intellij.kubernetes.editor.util.getKubernetesTypeInfo
18+
import com.redhat.devtools.intellij.kubernetes.editor.util.getLabels
19+
import com.redhat.devtools.intellij.kubernetes.editor.util.getTemplate
20+
import com.redhat.devtools.intellij.kubernetes.editor.util.hasMatchExpressions
21+
import com.redhat.devtools.intellij.kubernetes.editor.util.hasMatchLabels
22+
import com.redhat.devtools.intellij.kubernetes.editor.util.hasSelector
23+
import com.redhat.devtools.intellij.kubernetes.editor.util.isCronJob
24+
import com.redhat.devtools.intellij.kubernetes.editor.util.isDaemonSet
25+
import com.redhat.devtools.intellij.kubernetes.editor.util.isDeployment
26+
import com.redhat.devtools.intellij.kubernetes.editor.util.isJob
27+
import com.redhat.devtools.intellij.kubernetes.editor.util.isNetworkPolicy
28+
import com.redhat.devtools.intellij.kubernetes.editor.util.isPersistentVolume
29+
import com.redhat.devtools.intellij.kubernetes.editor.util.isPersistentVolumeClaim
30+
import com.redhat.devtools.intellij.kubernetes.editor.util.isPod
31+
import com.redhat.devtools.intellij.kubernetes.editor.util.isPodDisruptionBudget
32+
import com.redhat.devtools.intellij.kubernetes.editor.util.isReplicaSet
33+
import com.redhat.devtools.intellij.kubernetes.editor.util.isService
34+
import com.redhat.devtools.intellij.kubernetes.editor.util.isStatefulSet
35+
36+
/**
37+
* A filter that accepts labels, that are matching a given selector.
38+
*/
39+
class LabelsFilter(private val selectorResource: PsiElement) {
40+
41+
private val selectorResourceType: KubernetesTypeInfo? by lazy {
42+
selectorResource.getKubernetesTypeInfo()
43+
}
44+
45+
private val hasSelector: Boolean by lazy {
46+
selectorResource.hasSelector()
47+
}
48+
49+
private val hasMatchLabels: Boolean by lazy {
50+
selectorResource.hasMatchLabels()
51+
}
52+
53+
private val hasMatchExpressions: Boolean by lazy {
54+
selectorResource.hasMatchExpressions()
55+
}
56+
57+
fun filterMatching(labels: Collection<PsiElement>): Collection<PsiElement> {
58+
return labels
59+
.filter(::isMatching)
60+
}
61+
62+
fun isMatching(element: PsiElement): Boolean {
63+
if (!hasSelector
64+
|| selectorResourceType == null) {
65+
return false
66+
}
67+
68+
val selectableType = element.getKubernetesTypeInfo() ?: return false
69+
if (!SelectorType(this.selectorResourceType).canSelect(selectableType)) {
70+
return false
71+
}
72+
73+
val labels = getLabels(selectableType, element, selectorResourceType) ?: return false
74+
75+
return when {
76+
hasMatchLabels && hasMatchExpressions ->
77+
selectorResource.areMatchingMatchLabels(labels)
78+
&& selectorResource.areMatchingMatchExpressions(labels)
79+
80+
hasMatchLabels ->
81+
selectorResource.areMatchingMatchLabels(labels)
82+
83+
hasMatchExpressions ->
84+
selectorResource.areMatchingMatchExpressions(labels)
85+
86+
else -> false
87+
}
88+
}
89+
90+
private fun getLabels(
91+
selectableType: KubernetesTypeInfo,
92+
selectableElement: PsiElement,
93+
selectorResourceType: KubernetesTypeInfo?
94+
): PsiElement? {
95+
return when {
96+
selectorResourceType == null ->
97+
null
98+
99+
(selectorResourceType.isCronJob()
100+
&& selectableType.isCronJob())
101+
|| (selectorResourceType.isDaemonSet()
102+
&& selectableType.isDaemonSet())
103+
|| (selectorResourceType.isDeployment()
104+
&& selectableType.isDeployment())
105+
|| (selectorResourceType.isJob()
106+
&& selectableType.isJob())
107+
|| (selectorResourceType.isReplicaSet()
108+
&& selectableType.isReplicaSet())
109+
|| (selectorResourceType.isStatefulSet()
110+
&& selectableType.isStatefulSet()) ->
111+
selectableElement.getTemplate()
112+
113+
else ->
114+
selectableElement.getLabels()
115+
}
116+
}
117+
}
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,101 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Red Hat, Inc.
3+
* Distributed under license by Red Hat, Inc. All rights reserved.
4+
* This program is made available under the terms of the
5+
* Eclipse Public License v2.0 which accompanies this distribution,
6+
* and is available at http://www.eclipse.org/legal/epl-v20.html
7+
*
8+
* Contributors:
9+
* Red Hat, Inc. - initial API and implementation
10+
******************************************************************************/
111
package com.redhat.devtools.intellij.kubernetes.editor.inlay.selector
212

313
import com.intellij.codeInsight.hints.InlayHintsSink
414
import com.intellij.codeInsight.hints.presentation.InlayPresentation
15+
import com.intellij.codeInsight.hints.presentation.PresentationFactory
16+
import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction
517
import com.intellij.openapi.editor.Editor
618
import com.intellij.psi.PsiElement
7-
import com.intellij.psi.util.PsiTreeUtil
8-
import com.redhat.devtools.intellij.common.validation.KubernetesTypeInfo
9-
import com.redhat.devtools.intellij.kubernetes.editor.util.getKind
10-
import com.redhat.devtools.intellij.kubernetes.editor.util.getMatchExpressions
11-
import com.redhat.devtools.intellij.kubernetes.editor.util.getMatchLabels
12-
import com.redhat.devtools.intellij.kubernetes.editor.util.getMetadataName
13-
import com.redhat.devtools.intellij.kubernetes.editor.util.hasKindAndName
14-
import com.redhat.devtools.intellij.kubernetes.editor.util.isMatchingExpressions
15-
import com.redhat.devtools.intellij.kubernetes.editor.util.isMatchingLabels
16-
import org.jetbrains.yaml.psi.YAMLKeyValue
17-
import org.jetbrains.yaml.psi.YAMLMapping
19+
import com.intellij.ui.IconManager
20+
import com.intellij.ui.awt.RelativePoint
21+
import com.redhat.devtools.intellij.kubernetes.editor.util.PsiFiles
22+
import com.redhat.devtools.intellij.kubernetes.editor.util.getAllElements
23+
import com.redhat.devtools.intellij.kubernetes.editor.util.getSelectorKey
24+
import java.awt.event.MouseEvent
25+
1826

1927
object SelectorPresentations {
2028

21-
fun create(element: PsiElement, root: PsiElement, info: KubernetesTypeInfo, sink: InlayHintsSink, editor: Editor): Collection<InlayPresentation>? {
22-
if (element is YAMLMapping) {
23-
findMatchingResources(element, root)
29+
private val selectorIcon = IconManager.getInstance().getIcon("icons/selector.svg", javaClass)
30+
31+
fun create(
32+
element: PsiElement,
33+
sink: InlayHintsSink,
34+
editor: Editor,
35+
filter: LabelsFilter = LabelsFilter(element)
36+
): Collection<InlayPresentation> {
37+
val project = editor.project ?: return emptyList()
38+
val fileType = editor.virtualFile.fileType
39+
val matchingElements = PsiFiles
40+
.getAll(fileType, project)
41+
.flatMap { file -> file.getAllElements() }
42+
.filter(filter::isMatching)
43+
if (matchingElements.isEmpty()) {
44+
return emptyList()
2445
}
25-
return emptyList()
26-
}
46+
val factory = PresentationFactory(editor)
47+
48+
val offset = element.getSelectorKey()?.textRange?.endOffset
49+
?: return emptyList()
50+
51+
val presentation = createText(factory, matchingElements, editor, element)
52+
sink.addInlineElement(offset, true, presentation, true)
2753

28-
private fun findMatchingResources(selectorResource: YAMLMapping, root: PsiElement): List<MatchingResource> {
29-
val matchLabels = selectorResource.getMatchLabels()
30-
val matchExpressions = selectorResource.getMatchExpressions()
31-
val allResources = PsiTreeUtil.findChildrenOfType(root, YAMLMapping::class.java)
32-
.filter { it != selectorResource // dont match yourself
33-
it.hasKindAndName() }
54+
val iconPresentation = createIcon(factory, editor, element)
55+
sink.addInlineElement(offset, true, iconPresentation, true)
3456

35-
return allResources
36-
.filter { resource ->
37-
resource.isMatchingLabels(matchLabels)
38-
&& resource.isMatchingExpressions(matchExpressions)
39-
}
40-
.map { resource ->
41-
val kind = resource.getKind() ?: return emptyList()
42-
val name = resource.getMetadataName() ?: return emptyList()
43-
MatchingResource(kind, name, resource)
44-
}
57+
return listOf(presentation, iconPresentation)
4558
}
4659

47-
private data class Resource(
48-
val name: String?,
49-
val kind: String?,
50-
val labels: Map<String, String>,
51-
val element: YAMLMapping
52-
) {
53-
fun findMatchLabels(): List<Triple<String, String, YAMLKeyValue>> {
54-
return element.getMatchLabels()?.keyValues?.map { keyValue ->
55-
Triple(keyValue.keyText, keyValue.valueText, keyValue)
56-
}
57-
?: emptyList()
58-
}
60+
private fun createText(
61+
factory: PresentationFactory,
62+
matchingElements: List<PsiElement>,
63+
editor: Editor,
64+
element: PsiElement
65+
): InlayPresentation {
66+
return factory.withTooltip(
67+
"Click to see matching resources",
68+
factory.referenceOnHover(
69+
factory.roundWithBackground(
70+
factory.text(
71+
"${matchingElements.size} matching"
72+
)
73+
), onClick(editor, element)
74+
)
75+
)
5976
}
6077

61-
private data class MatchExpression(
62-
val key: String,
63-
val operator: String,
64-
val values: List<String>,
65-
val element: YAMLMapping
66-
)
78+
private fun createIcon(
79+
factory: PresentationFactory,
80+
editor: Editor,
81+
element: PsiElement
82+
): InlayPresentation {
83+
val iconPresentation = factory.referenceOnHover(
84+
factory.roundWithBackground(
85+
factory.smallScaledIcon(selectorIcon)
86+
),
87+
onClick(editor, element)
88+
)
89+
return iconPresentation
90+
}
6791

68-
private data class MatchingResource(val kind: String, val name: String, val element: PsiElement)
92+
private fun onClick(
93+
editor: Editor,
94+
element: PsiElement
95+
): (event: MouseEvent, point: java.awt.Point) -> Unit {
96+
return { event, point ->
97+
GotoDeclarationAction.startFindUsages(editor, element.project, element, RelativePoint(event))
98+
}
99+
}
69100

70101
}

0 commit comments

Comments
 (0)