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