Skip to content

Commit 93503a4

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

28 files changed

+2683
-508
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

+22-14
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import com.intellij.codeInsight.hints.InlayHintsProvider
2020
import com.intellij.codeInsight.hints.InlayHintsSink
2121
import com.intellij.codeInsight.hints.NoSettings
2222
import com.intellij.codeInsight.hints.SettingsKey
23+
import com.intellij.codeInsight.hints.presentation.PresentationFactory
2324
import com.intellij.json.psi.JsonFile
24-
import com.intellij.openapi.application.ApplicationManager
2525
import com.intellij.openapi.application.ReadAction
2626
import com.intellij.openapi.editor.Editor
2727
import com.intellij.psi.PsiElement
@@ -30,6 +30,7 @@ import com.intellij.ui.dsl.builder.panel
3030
import com.redhat.devtools.intellij.common.validation.KubernetesTypeInfo
3131
import com.redhat.devtools.intellij.kubernetes.editor.inlay.base64.Base64Presentations
3232
import com.redhat.devtools.intellij.kubernetes.editor.inlay.selector.SelectorPresentations
33+
import com.redhat.devtools.intellij.kubernetes.editor.util.PsiElements
3334
import org.jetbrains.yaml.psi.YAMLFile
3435
import javax.swing.JComponent
3536

@@ -66,35 +67,42 @@ internal class ResourceEditorInlayHintsProvider : InlayHintsProvider<NoSettings>
6667
}
6768
return when(element) {
6869
is YAMLFile -> {
69-
create(element, sink, editor)
70+
create(element, sink, editor, factory)
7071
false
7172
}
7273
is JsonFile -> {
73-
create(element, sink, editor)
74+
create(element, sink, editor, factory)
7475
false
7576
}
7677
else -> true
7778
}
7879
}
7980

80-
private fun create(file: YAMLFile, sink: InlayHintsSink, editor: Editor) {
81+
private fun create(file: YAMLFile, sink: InlayHintsSink, editor: Editor, factory: PresentationFactory) {
8182
return ReadAction.run<Exception> {
82-
file.documents.forEach { document ->
83-
val info = KubernetesTypeInfo.create(document) ?: return@forEach
84-
val element = document.topLevelValue ?: return@forEach
85-
Base64Presentations.create(element, info, sink, editor)
86-
SelectorPresentations.create(element, file, info, sink, editor)
87-
}
83+
file.documents
84+
.mapNotNull { document -> document.topLevelValue }
85+
.forEach { element ->
86+
createPresentations(element, sink, editor, factory)
87+
}
8888
}
8989
}
9090

91-
private fun create(file: JsonFile, sink: InlayHintsSink, editor: Editor) {
91+
private fun create(file: JsonFile, sink: InlayHintsSink, editor: Editor, factory: PresentationFactory) {
9292
return ReadAction.run<Exception> {
93-
val info = KubernetesTypeInfo.create(file) ?: return@run
94-
val element = file.topLevelValue ?: return@run
95-
Base64Presentations.create(element, info, sink, editor)
93+
file.allTopLevelValues.forEach { element ->
94+
createPresentations(element, sink, editor, factory)
95+
}
9696
}
9797
}
9898

99+
private fun createPresentations(element: PsiElement, sink: InlayHintsSink, editor: Editor, factory: PresentationFactory) {
100+
val info = KubernetesTypeInfo.create(element) ?: return
101+
Base64Presentations.create(element, info, sink, editor, factory)
102+
103+
val allElements = PsiElements.getAll(editor.virtualFile.fileType, editor.project)
104+
SelectorPresentations.createForSelector(element, allElements, sink = sink, editor = editor, factory = factory)
105+
SelectorPresentations.createForLabel(element, allElements, sink = sink, editor = editor, factory = factory)
106+
}
99107
}
100108
}

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

+22-19
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,16 @@ object Base64Presentations {
4040
private const val SECRET_RESOURCE_KIND = "Secret"
4141
private const val CONFIGMAP_RESOURCE_KIND = "ConfigMap"
4242

43-
fun create(element: PsiElement, info: KubernetesTypeInfo, sink: InlayHintsSink, editor: Editor): Collection<InlayPresentation>? {
43+
fun create(element: PsiElement, info: KubernetesTypeInfo, sink: InlayHintsSink, editor: Editor, factory: PresentationFactory): Collection<InlayPresentation>? {
4444
return when {
4545
isKubernetesResource(SECRET_RESOURCE_KIND, info) -> {
46-
val data = getDataValue(element) ?: return null
47-
StringPresentationsFactory(data, sink, editor).create()
46+
val data = element.getDataValue() ?: return null
47+
StringPresentationsFactory(data, sink, editor, factory).create()
4848
}
4949

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

5555
else -> null
@@ -59,7 +59,8 @@ object Base64Presentations {
5959
abstract class InlayPresentationsFactory(
6060
private val element: PsiElement,
6161
protected val sink: InlayHintsSink,
62-
protected val editor: Editor
62+
protected val editor: Editor,
63+
protected val factory: PresentationFactory
6364
) {
6465

6566
protected companion object {
@@ -77,8 +78,8 @@ object Base64Presentations {
7778

7879
}
7980

80-
class StringPresentationsFactory(element: PsiElement, sink: InlayHintsSink, editor: Editor)
81-
: InlayPresentationsFactory(element, sink, editor) {
81+
class StringPresentationsFactory(element: PsiElement, sink: InlayHintsSink, editor: Editor, factory: PresentationFactory)
82+
: InlayPresentationsFactory(element, sink, editor, factory) {
8283

8384
override fun create(adapter: Base64ValueAdapter): InlayPresentation? {
8485
val decoded = adapter.getDecoded() ?: return null
@@ -88,20 +89,22 @@ object Base64Presentations {
8889
onValidValue(adapter::set, editor.project),
8990
editor
9091
)::show
91-
val presentation = create(decoded, onClick, editor) ?: return null
92+
val presentation = create(decoded, onClick, factory) ?: return null
9293
sink.addInlineElement(offset, false, presentation, false)
9394
return presentation
9495
}
9596

96-
private fun create(text: String, onClick: (event: MouseEvent) -> Unit, editor: Editor): InlayPresentation? {
97-
val factory = PresentationFactory(editor)
97+
private fun create(text: String, onClick: (event: MouseEvent) -> Unit, factory: PresentationFactory): InlayPresentation? {
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?)
@@ -117,8 +120,8 @@ object Base64Presentations {
117120

118121
}
119122

120-
class BinaryPresentationsFactory(element: PsiElement, sink: InlayHintsSink, editor: Editor)
121-
: InlayPresentationsFactory(element, sink, editor) {
123+
class BinaryPresentationsFactory(element: PsiElement, sink: InlayHintsSink, editor: Editor, factory: PresentationFactory)
124+
: InlayPresentationsFactory(element, sink, editor, factory) {
122125

123126
override fun create(adapter: Base64ValueAdapter): InlayPresentation? {
124127
val decoded = adapter.getDecodedBytes() ?: return null

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,161 @@
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.intellij.psi.util.PsiElementFilter
15+
import com.redhat.devtools.intellij.common.validation.KubernetesTypeInfo
16+
import com.redhat.devtools.intellij.kubernetes.editor.util.areMatchingMatchExpressions
17+
import com.redhat.devtools.intellij.kubernetes.editor.util.areMatchingMatchLabels
18+
import com.redhat.devtools.intellij.kubernetes.editor.util.getKubernetesTypeInfo
19+
import com.redhat.devtools.intellij.kubernetes.editor.util.getLabels
20+
import com.redhat.devtools.intellij.kubernetes.editor.util.getResource
21+
import com.redhat.devtools.intellij.kubernetes.editor.util.getTemplate
22+
import com.redhat.devtools.intellij.kubernetes.editor.util.hasMatchExpressions
23+
import com.redhat.devtools.intellij.kubernetes.editor.util.hasMatchLabels
24+
import com.redhat.devtools.intellij.kubernetes.editor.util.hasSelector
25+
import com.redhat.devtools.intellij.kubernetes.editor.util.isCronJob
26+
import com.redhat.devtools.intellij.kubernetes.editor.util.isDaemonSet
27+
import com.redhat.devtools.intellij.kubernetes.editor.util.isDeployment
28+
import com.redhat.devtools.intellij.kubernetes.editor.util.isJob
29+
import com.redhat.devtools.intellij.kubernetes.editor.util.isNetworkPolicy
30+
import com.redhat.devtools.intellij.kubernetes.editor.util.isPersistentVolume
31+
import com.redhat.devtools.intellij.kubernetes.editor.util.isPersistentVolumeClaim
32+
import com.redhat.devtools.intellij.kubernetes.editor.util.isPod
33+
import com.redhat.devtools.intellij.kubernetes.editor.util.isPodDisruptionBudget
34+
import com.redhat.devtools.intellij.kubernetes.editor.util.isReplicaSet
35+
import com.redhat.devtools.intellij.kubernetes.editor.util.isService
36+
import com.redhat.devtools.intellij.kubernetes.editor.util.isStatefulSet
37+
38+
/**
39+
* A filter that accepts labels, that are matching a given selector.
40+
*/
41+
class LabelsFilter(selector: PsiElement): PsiElementFilter {
42+
43+
private val selectorResource: PsiElement? by lazy {
44+
selector.getResource()
45+
}
46+
47+
private val selectorResourceType: KubernetesTypeInfo? by lazy {
48+
selectorResource?.getKubernetesTypeInfo()
49+
}
50+
51+
private val hasSelector: Boolean by lazy {
52+
selectorResource?.hasSelector() ?: false
53+
}
54+
55+
private val hasMatchLabels: Boolean by lazy {
56+
selectorResource?.hasMatchLabels() ?: false
57+
}
58+
59+
private val hasMatchExpressions: Boolean by lazy {
60+
selectorResource?.hasMatchExpressions() ?: false
61+
}
62+
63+
override fun isAccepted(toAccept: PsiElement): Boolean {
64+
val selectorResource = selectorResource
65+
if (selectorResource == null
66+
|| selectorResourceType == null
67+
|| !hasSelector) {
68+
return false
69+
}
70+
71+
val labeledResourceType = toAccept.getKubernetesTypeInfo() ?: return false
72+
if (!canSelect(labeledResourceType)) {
73+
return false
74+
}
75+
76+
val labels = getLabels(labeledResourceType, toAccept, selectorResourceType) ?: return false
77+
78+
return when {
79+
hasMatchLabels && hasMatchExpressions ->
80+
selectorResource.areMatchingMatchLabels(labels)
81+
&& selectorResource.areMatchingMatchExpressions(labels)
82+
83+
hasMatchLabels ->
84+
selectorResource.areMatchingMatchLabels(labels)
85+
86+
hasMatchExpressions ->
87+
selectorResource.areMatchingMatchExpressions(labels)
88+
89+
else -> false
90+
}
91+
}
92+
93+
private fun canSelect(type: KubernetesTypeInfo): Boolean {
94+
val selectorType = selectorResourceType ?: return false
95+
return when {
96+
selectorType.isDeployment() ->
97+
type.isPod()
98+
|| type.isDeployment() // can select deployment template
99+
100+
selectorType.isCronJob() ->
101+
type.isPod()
102+
|| type.isCronJob() // template
103+
104+
selectorType.isDaemonSet() ->
105+
type.isPod()
106+
|| type.isDaemonSet() // template
107+
108+
selectorType.isJob() ->
109+
type.isPod()
110+
|| type.isJob() // template
111+
112+
selectorType.isReplicaSet() ->
113+
type.isPod()
114+
|| type.isReplicaSet() // template
115+
116+
selectorType.isStatefulSet() ->
117+
type.isPod()
118+
|| type.isStatefulSet() // template
119+
120+
selectorType.isNetworkPolicy()
121+
|| selectorType.isPodDisruptionBudget()
122+
|| selectorType.isService() ->
123+
type.isPod()
124+
125+
selectorType.isPersistentVolumeClaim() ->
126+
type.isPersistentVolume()
127+
|| type.isPersistentVolumeClaim()
128+
129+
else ->
130+
false
131+
}
132+
}
133+
134+
private fun getLabels(
135+
labeledType: KubernetesTypeInfo,
136+
labeledResource: PsiElement,
137+
selectorResourceType: KubernetesTypeInfo?
138+
): PsiElement? {
139+
return when {
140+
selectorResourceType == null ->
141+
null
142+
143+
(selectorResourceType.isCronJob()
144+
&& labeledType.isCronJob())
145+
|| (selectorResourceType.isDaemonSet()
146+
&& labeledType.isDaemonSet())
147+
|| (selectorResourceType.isDeployment()
148+
&& labeledType.isDeployment())
149+
|| (selectorResourceType.isJob()
150+
&& labeledType.isJob())
151+
|| (selectorResourceType.isReplicaSet()
152+
&& labeledType.isReplicaSet())
153+
|| (selectorResourceType.isStatefulSet()
154+
&& labeledType.isStatefulSet()) ->
155+
labeledResource.getTemplate()
156+
157+
else ->
158+
labeledResource.getLabels()
159+
}
160+
}
161+
}

0 commit comments

Comments
 (0)