Skip to content

Commit aba2406

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

21 files changed

+2023
-322
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/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
}

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

+345
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
package com.example.kubernetes.inlayhints
2+
3+
import com.intellij.codeInsight.hints.*
4+
import com.intellij.codeInsight.hints.presentation.MenuOnClickPresentation
5+
import com.intellij.json.psi.*
6+
import com.intellij.lang.annotation.Annotator
7+
import com.intellij.openapi.actionSystem.AnAction
8+
import com.intellij.openapi.actionSystem.AnActionEvent
9+
import com.intellij.openapi.actionSystem.DefaultActionGroup
10+
import com.intellij.openapi.ui.popup.JBPopupFactory
11+
import com.intellij.openapi.ui.popup.ListPopup
12+
import com.intellij.psi.PsiElement
13+
import com.intellij.psi.util.PsiTreeUtil
14+
import org.jetbrains.yaml.psi.*
15+
import org.jetbrains.yaml.psi.impl.YAMLKeyValueImpl
16+
import org.jetbrains.yaml.psi.impl.YAMLMappingImpl
17+
import org.jetbrains.yaml.psi.impl.YAMLSequenceImpl
18+
import java.awt.event.MouseEvent
19+
import javax.swing.JComponent
20+
21+
/*
22+
class KubernetesResourceMatchingHintProvider : InlayHintsProvider<NoSettings>, Annotator {
23+
24+
override fun createSettings(): NoSettings = NoSettings()
25+
26+
override val name: String = "Kubernetes Resource Matching Count"
27+
28+
override val key: SettingsKey<NoSettings> = SettingsKey("kubernetes.resource.matching.count")
29+
30+
//override fun isLanguageSupported(file: PsiFile): Boolean = file is YAMLFile || file is JsonFile
31+
32+
override fun collectHints(
33+
element: PsiElement,
34+
sink: InlayHintsSink,
35+
settings: NoSettings,
36+
collector: InlayHintsCollector
37+
) {
38+
when (element) {
39+
is YAMLKeyValueImpl -> handleYaml(element, sink)
40+
is JsonProperty -> handleJson(element, sink)
41+
}
42+
}
43+
44+
private fun handleYaml(keyValue: YAMLKeyValueImpl, sink: InlayHintsSink) {
45+
val keyText = keyValue.keyText
46+
47+
if (keyText == "matchLabels" || keyText == "matchExpressions") {
48+
val value = keyValue.value
49+
if (value is YAMLMappingImpl || value is YAMLSequenceImpl) {
50+
val matchingResources = findMatchingYamlResources(keyValue)
51+
if (matchingResources.isNotEmpty()) {
52+
sink.addInlineElement(keyValue.textRange.endOffset, false, { factory ->
53+
factory.text(
54+
"(" + matchingResources.size + " matches)",
55+
factory.psiSingleReference(matchingResources[0]),
56+
MenuOnClickPresentation(
57+
"(" + matchingResources.size + " matches)",
58+
keyValue.project
59+
) { event: MouseEvent, component: JComponent ->
60+
val matchesMenu = createMatchesYamlMenu(matchingResources, keyValue)
61+
matchesMenu.show(component, event.x, event.y)
62+
})
63+
})
64+
}
65+
}
66+
}
67+
}
68+
69+
private fun handleJson(property: JsonProperty, sink: InlayHintsSink) {
70+
val keyText = property.name
71+
72+
if (keyText == "matchLabels" || keyText == "matchExpressions") {
73+
val value = property.value
74+
if (value is JsonObject || value is com.intellij.json.psi.JsonArray) {
75+
val matchingResources = findMatchingJsonResources(property)
76+
if (matchingResources.isNotEmpty()) {
77+
sink.addInlineElement(property.textRange.endOffset, false, { factory ->
78+
factory.text(
79+
"(" + matchingResources.size + " matches)",
80+
factory.psiSingleReference(matchingResources[0]),
81+
MenuOnClickPresentation(
82+
"(" + matchingResources.size + " matches)",
83+
property.project
84+
) { event: MouseEvent, component: JComponent ->
85+
val matchesMenu = createMatchesJsonMenu(matchingResources, property)
86+
matchesMenu.show(component, event.x, event.y)
87+
})
88+
})
89+
}
90+
}
91+
}
92+
}
93+
94+
// YAML Logic
95+
private fun findMatchingYamlResources(keyValue: YAMLKeyValueImpl): List<YAMLMappingImpl> {
96+
// ... (YAML logic as before)
97+
val file = keyValue.containingFile as? YAMLFile ?: return emptyList()
98+
val selector = keyValue.value as? YAMLMappingImpl ?: return emptyList()
99+
100+
val allMappings = PsiTreeUtil.getChildrenOfTypeAsList(file, YAMLMappingImpl::class.java)
101+
102+
return allMappings.filter { mapping ->
103+
isResourceMapping(mapping) && matchesSelector(mapping, selector, keyValue.keyText)
104+
}
105+
}
106+
107+
private fun isResourceMapping(mapping: YAMLMappingImpl): Boolean {
108+
// ... (YAML logic as before)
109+
val kind = mapping.getKeyValueByKey("kind")?.valueText ?: return false
110+
return kind == "Pod" || kind == "Service" || kind == "Deployment" || kind == "ReplicaSet" || kind == "StatefulSet"
111+
}
112+
113+
private fun matchesSelector(
114+
resourceMapping: YAMLMappingImpl,
115+
selector: YAMLMappingImpl,
116+
selectorType: String
117+
): Boolean {
118+
// ... (YAML logic as before)
119+
val metadata = resourceMapping.getKeyValueByKey("metadata")?.value as? YAMLMappingImpl ?: return false
120+
val labels = metadata.getKeyValueByKey("labels")?.value as? YAMLMappingImpl ?: return false
121+
122+
return when (selectorType) {
123+
"matchLabels" -> matchesLabels(labels, selector)
124+
"matchExpressions" -> matchesExpressions(labels, selector as YAMLSequenceImpl)
125+
else -> false
126+
}
127+
}
128+
129+
private fun matchesLabels(resourceLabels: YAMLMappingImpl, selectorLabels: YAMLMappingImpl): Boolean {
130+
// ... (YAML logic as before)
131+
return selectorLabels.keyValueList.all { selectorLabel ->
132+
val resourceLabel = resourceLabels.getKeyValueByKey(selectorLabel.keyText)
133+
resourceLabel?.valueText == selectorLabel.valueText
134+
}
135+
}
136+
137+
private fun matchesExpressions(resourceLabels: YAMLMappingImpl, expressions: YAMLSequenceImpl): Boolean {
138+
// ... (YAML logic as before)
139+
return expressions.items.all { expressionElement ->
140+
if (expressionElement !is YAMLMappingImpl) return@all true
141+
val expression = expressionElement as YAMLMappingImpl
142+
143+
val key = expression.getKeyValueByKey("key")?.valueText ?: return@all true
144+
val operator = expression.getKeyValueByKey("operator")?.valueText ?: return@all true
145+
val values = expression.getKeyValueByKey("values")?.value as? YAMLSequenceImpl
146+
147+
val resourceLabel = resourceLabels.getKeyValueByKey(key)
148+
val resourceValue = resourceLabel?.valueText
149+
150+
when (operator) {
151+
"In" -> values?.items?.map { it.text }?.contains("\"$resourceValue\"") ?: false
152+
"NotIn" -> values?.items?.map { it.text }?.contains("\"$resourceValue\"")?.not() ?: false
153+
"Exists" -> resourceValue != null
154+
"DoesNotExist" -> resourceValue == null
155+
else -> false
156+
}
157+
}
158+
}
159+
160+
private fun createMatchesYamlMenu(matchingResources: List<YAMLMappingImpl>, element: PsiElement): ListPopup {
161+
// ... (YAML menu logic as before)
162+
val actionGroup = DefaultActionGroup()
163+
matchingResources.forEach { resource ->
164+
val resourceName = resource.getKeyValueByKey("metadata")?.getKeyValueByKey("name")?.valueText ?: "<unknown>"
165+
actionGroup.add(object : AnAction(resourceName) {
166+
override fun actionPerformed(e: AnActionEvent) {
167+
resource.navigate(true)
168+
}
169+
})
170+
}
171+
return JBPopupFactory.getInstance()
172+
.createActionGroupPopup("Matching Resources", actionGroup, element.project, true, null, 10)
173+
}
174+
175+
// JSON Logic
176+
private fun findMatchingJsonResources(property: JsonProperty): List<JsonObject> {
177+
val file = property.containingFile as? JsonFile ?: return emptyList()
178+
val selector = property.value as? JsonObject ?: return emptyList()
179+
180+
val allObjects = PsiTreeUtil.getChildrenOfTypeAsList(file, JsonObject::class.java)
181+
182+
return allObjects.filter { obj ->
183+
isJsonResourceObject(obj) && matchesJsonSelector(obj, selector, property.name)
184+
}
185+
}
186+
187+
private fun isJsonResourceObject(obj: JsonObject): Boolean {
188+
val kindProperty = obj.findProperty("kind")?.value as? JsonStringLiteral ?: return false
189+
val kind = kindProperty.value
190+
191+
return kind == "Pod" || kind == "Service" || kind == "Deployment" || kind == "ReplicaSet" || kind == "StatefulSet"
192+
}
193+
194+
private fun matchesJsonSelector(resourceObj: JsonObject, selector: JsonObject, selectorType: String): Boolean {
195+
val metadata = resourceObj.findProperty("metadata")?.value as? JsonObject ?: return false
196+
val labels = metadata.findProperty("labels")?.value as? JsonObject ?: return false
197+
198+
return when (selectorType) {
199+
"matchLabels" -> matchesJsonLabels(labels, selector)
200+
"matchExpressions" -> matchesJsonExpressions(
201+
labels,
202+
selector
203+
) // Assuming selector is JsonObject for matchExpressions
204+
else -> false
205+
}
206+
}
207+
208+
private fun matchesJsonLabels(resourceLabels: JsonObject, selectorLabels: JsonObject): Boolean {
209+
for (selectorProperty in selectorLabels.propertyList) {
210+
val resourceProperty = resourceLabels.findProperty(selectorProperty.name)
211+
if (resourceProperty == null || (resourceProperty.value as? JsonStringLiteral)?.value != (selectorProperty.value as? JsonStringLiteral)?.value) {
212+
return false
213+
}
214+
}
215+
return true
216+
}
217+
218+
private fun matchesJsonExpressions(resourceLabels: JsonObject, selector: JsonObject): Boolean {
219+
val expressions =
220+
selector.findProperty("expressions")?.value as? com.intellij.json.psi.JsonArray ?: return false;
221+
222+
for (expressionElement in expressions.valueList) {
223+
if (expressionElement !is JsonObject) return false;
224+
val expression = expressionElement as JsonObject;
225+
226+
val key = expression.findProperty("key")?.value as? JsonStringLiteral ?: return false;
227+
val operator = expression.findProperty("operator")?.value as? JsonStringLiteral ?: return false;
228+
val values = expression.findProperty("values")?.value as? com.intellij.json.psi.JsonArray;
229+
230+
val resourceLabel = resourceLabels.findProperty(key.value)?.value as? JsonStringLiteral;
231+
val resourceValue = resourceLabel?.value;
232+
233+
when (operator.value) {
234+
"In" -> {
235+
if (values == null || resourceValue == null) return false;
236+
val expectedValues = values.valueList.map { (it as? JsonStringLiteral)?.value }.filterNotNull();
237+
if (!expectedValues.contains(resourceValue)) return false;
238+
}
239+
240+
"NotIn" -> {
241+
if (values == null || resourceValue == null) return false;
242+
val excludedValues = values.valueList.map { (it as? JsonStringLiteral)?.value }.filterNotNull();
243+
if (excludedValues.contains(resourceValue)) return false;
244+
}
245+
246+
"Exists" -> {
247+
if (resourceValue == null) return false;
248+
}
249+
250+
"DoesNotExist" -> {
251+
if (resourceValue != null) return false;
252+
}
253+
254+
else -> return false;
255+
}
256+
257+
}
258+
259+
return true;
260+
}
261+
}
262+
263+
*/

0 commit comments

Comments
 (0)