From 78cc13334ad3ad588f4a65a58b99c2a51c36a7fa Mon Sep 17 00:00:00 2001 From: Daniele Ricci Date: Fri, 24 Jan 2025 13:55:21 +0100 Subject: [PATCH 1/5] Experimenting with find usages for automation (wip) [skip ci] --- .../HassAutomationFindUsagesProvider.kt | 71 +++++++++++++++++++ src/main/resources/META-INF/plugin.xml | 3 + .../resources/messages/MyBundle.properties | 1 + 3 files changed, 75 insertions(+) create mode 100644 src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassAutomationFindUsagesProvider.kt diff --git a/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassAutomationFindUsagesProvider.kt b/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassAutomationFindUsagesProvider.kt new file mode 100644 index 0000000..3be3583 --- /dev/null +++ b/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassAutomationFindUsagesProvider.kt @@ -0,0 +1,71 @@ +package it.casaricci.hass.plugin.findUsages + +import com.intellij.lang.cacheBuilder.WordsScanner +import com.intellij.lang.findUsages.FindUsagesProvider +import com.intellij.openapi.diagnostic.thisLogger +import com.intellij.psi.PsiElement +import com.intellij.psi.util.parentOfType +import it.casaricci.hass.plugin.HassKnownDomains +import it.casaricci.hass.plugin.MyBundle +import org.jetbrains.yaml.YAMLWordsScanner +import org.jetbrains.yaml.psi.YAMLKeyValue +import org.jetbrains.yaml.psi.YAMLSequence +import org.jetbrains.yaml.psi.YAMLSequenceItem + +class HassAutomationFindUsagesProvider : FindUsagesProvider { + + override fun getWordsScanner(): WordsScanner { + return YAMLWordsScanner() + } + + override fun canFindUsagesFor(element: PsiElement): Boolean { + thisLogger().trace("canFindUsageFor: $element") + return isMyElement(element) + } + + override fun getHelpId(element: PsiElement): String? { + return null + } + + override fun getType(element: PsiElement): String { + if (isMyElement(element)) { + return MyBundle.message("hass.findUsages.haAutomation") + } + return "" + } + + override fun getDescriptiveName(element: PsiElement): String { + if (isMyElement(element)) { + return element.text + } + return "" + } + + override fun getNodeText(element: PsiElement, useFullName: Boolean): String { + if (isMyElement(element)) { + return element.text + ":" + } + return "" + } + + private fun isMyElement(element: PsiElement): Boolean { + thisLogger().trace("element: $element") + val parent = if (element is YAMLKeyValue) { + thisLogger().trace("element.keyText: " + element.keyText) + element.parentMapping + } else { + thisLogger().trace("element.text: " + element.text) + element.parentOfType()?.parentMapping + } + thisLogger().trace( + "lookup parent.keyText: " + parent?.parentOfType() + ?.parentOfType() + ?.parentOfType() + ?.keyText + ) + return parent?.parentOfType() + ?.parentOfType() + ?.parentOfType() + ?.keyText == HassKnownDomains.AUTOMATION + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 6bc3b96..5885f67 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -20,6 +20,9 @@ + diff --git a/src/main/resources/messages/MyBundle.properties b/src/main/resources/messages/MyBundle.properties index a0c3f12..916684e 100644 --- a/src/main/resources/messages/MyBundle.properties +++ b/src/main/resources/messages/MyBundle.properties @@ -9,6 +9,7 @@ hass.facet.editor.token.invalid=A token is needed to download data from Home Ass hass.facet.editor.refresh.text=Refresh data hass.findUsages.haScript=Home Assistant script +hass.findUsages.haAutomation=Home Assistant automation hass.findUsages.haSecret=Home Assistant secret hass.notification.refreshCache.progress=Updating states data from Home Assistant From f0927c88c7e4cd952d21030029890fdf95a2daeb Mon Sep 17 00:00:00 2001 From: Daniele Ricci Date: Thu, 30 Jan 2025 21:28:55 +0100 Subject: [PATCH 2/5] Experimenting with PsiElementEvaluator [skip ci] Undocumented API which might be useful for this, but I don't really know what I'm doing --- .../HassAutomationFindUsagesProvider.kt | 19 ++++++++- .../hass/plugin/language/HassAutomation.kt | 17 ++++++++ .../hass/plugin/psi/HassElementEvaluator.kt | 41 +++++++++++++++++++ src/main/resources/META-INF/plugin.xml | 3 ++ 4 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/it/casaricci/hass/plugin/language/HassAutomation.kt create mode 100644 src/main/kotlin/it/casaricci/hass/plugin/psi/HassElementEvaluator.kt diff --git a/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassAutomationFindUsagesProvider.kt b/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassAutomationFindUsagesProvider.kt index 3be3583..b2e5f0f 100644 --- a/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassAutomationFindUsagesProvider.kt +++ b/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassAutomationFindUsagesProvider.kt @@ -1,13 +1,18 @@ package it.casaricci.hass.plugin.findUsages +import com.intellij.lang.cacheBuilder.DefaultWordsScanner import com.intellij.lang.cacheBuilder.WordsScanner import com.intellij.lang.findUsages.FindUsagesProvider import com.intellij.openapi.diagnostic.thisLogger import com.intellij.psi.PsiElement +import com.intellij.psi.tree.IElementType +import com.intellij.psi.tree.TokenSet import com.intellij.psi.util.parentOfType import it.casaricci.hass.plugin.HassKnownDomains import it.casaricci.hass.plugin.MyBundle -import org.jetbrains.yaml.YAMLWordsScanner +import org.jetbrains.yaml.YAMLElementTypes +import org.jetbrains.yaml.YAMLTokenTypes +import org.jetbrains.yaml.lexer.YAMLFlexLexer import org.jetbrains.yaml.psi.YAMLKeyValue import org.jetbrains.yaml.psi.YAMLSequence import org.jetbrains.yaml.psi.YAMLSequenceItem @@ -15,7 +20,17 @@ import org.jetbrains.yaml.psi.YAMLSequenceItem class HassAutomationFindUsagesProvider : FindUsagesProvider { override fun getWordsScanner(): WordsScanner { - return YAMLWordsScanner() + return DefaultWordsScanner( + YAMLFlexLexer(), + TokenSet.create( + YAMLTokenTypes.SCALAR_KEY, + YAMLTokenTypes.SCALAR_TEXT, + YAMLTokenTypes.TEXT, + ), + TokenSet.create( + YAMLTokenTypes.COMMENT + ), + YAMLElementTypes.SCALAR_VALUES) } override fun canFindUsagesFor(element: PsiElement): Boolean { diff --git a/src/main/kotlin/it/casaricci/hass/plugin/language/HassAutomation.kt b/src/main/kotlin/it/casaricci/hass/plugin/language/HassAutomation.kt new file mode 100644 index 0000000..c4860e6 --- /dev/null +++ b/src/main/kotlin/it/casaricci/hass/plugin/language/HassAutomation.kt @@ -0,0 +1,17 @@ +package it.casaricci.hass.plugin.language + +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiNamedElement +import org.jetbrains.yaml.psi.impl.YAMLPsiElementImpl + +class HassAutomation(element: PsiElement, private val automationName: String) + : YAMLPsiElementImpl(element.node), PsiNamedElement { + + override fun getName(): String { + return automationName + } + + override fun setName(name: String): PsiElement { + TODO("Not yet implemented") + } +} diff --git a/src/main/kotlin/it/casaricci/hass/plugin/psi/HassElementEvaluator.kt b/src/main/kotlin/it/casaricci/hass/plugin/psi/HassElementEvaluator.kt new file mode 100644 index 0000000..f418144 --- /dev/null +++ b/src/main/kotlin/it/casaricci/hass/plugin/psi/HassElementEvaluator.kt @@ -0,0 +1,41 @@ +package it.casaricci.hass.plugin.psi + +import com.intellij.codeInsight.TargetElementEvaluatorEx2 +import com.intellij.openapi.diagnostic.thisLogger +import com.intellij.psi.PsiElement +import com.intellij.psi.util.parentOfType +import it.casaricci.hass.plugin.HASS_AUTOMATION_NAME_PROPERTY +import it.casaricci.hass.plugin.HassKnownDomains +import it.casaricci.hass.plugin.language.HassAutomation +import org.jetbrains.yaml.psi.YAMLKeyValue +import org.jetbrains.yaml.psi.YAMLScalar +import org.jetbrains.yaml.psi.YAMLSequence +import org.jetbrains.yaml.psi.YAMLSequenceItem + +class HassElementEvaluator : TargetElementEvaluatorEx2() { + override fun getNamedElement(element: PsiElement): PsiElement? { + thisLogger().trace("NAMED: $element") + val maybeAutomation = getAutomationAlias(element) + if (maybeAutomation?.parentOfType() + ?.parentOfType() + ?.parentOfType()?.keyText == HassKnownDomains.AUTOMATION) { + + // return the key-value element + //return element.parent.parent + return HassAutomation(element, maybeAutomation.valueText) + } + + return super.getNamedElement(element) + } + + private fun getAutomationAlias(element: PsiElement): YAMLKeyValue? { + val keyValue = element.parentOfType() + ?.parentOfType() + return if (keyValue?.keyText == HASS_AUTOMATION_NAME_PROPERTY) { + keyValue + } + else { + null + } + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 5885f67..16bc2e4 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -14,6 +14,9 @@ + + From 20966a789e52919c37b60c152705793ced8536a2 Mon Sep 17 00:00:00 2001 From: Daniele Ricci Date: Tue, 4 Feb 2025 17:51:29 +0100 Subject: [PATCH 3/5] Experimenting with PsiElementEvaluator [skip ci] Maybe I'm on to something. --- .../HassAutomationFindUsagesProvider.kt | 59 +++++-------------- .../HassScriptFindUsagesProvider.kt | 2 + .../hass/plugin/language/HassAutomation.kt | 34 +++++++++-- .../hass/plugin/psi/HassElementEvaluator.kt | 51 +++++++++++----- .../hass/plugin/psi/HassEntityReference.kt | 6 +- 5 files changed, 86 insertions(+), 66 deletions(-) diff --git a/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassAutomationFindUsagesProvider.kt b/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassAutomationFindUsagesProvider.kt index b2e5f0f..8b6f7d9 100644 --- a/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassAutomationFindUsagesProvider.kt +++ b/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassAutomationFindUsagesProvider.kt @@ -1,40 +1,22 @@ package it.casaricci.hass.plugin.findUsages -import com.intellij.lang.cacheBuilder.DefaultWordsScanner import com.intellij.lang.cacheBuilder.WordsScanner import com.intellij.lang.findUsages.FindUsagesProvider import com.intellij.openapi.diagnostic.thisLogger import com.intellij.psi.PsiElement -import com.intellij.psi.tree.IElementType -import com.intellij.psi.tree.TokenSet -import com.intellij.psi.util.parentOfType -import it.casaricci.hass.plugin.HassKnownDomains +import com.intellij.psi.PsiNamedElement import it.casaricci.hass.plugin.MyBundle -import org.jetbrains.yaml.YAMLElementTypes -import org.jetbrains.yaml.YAMLTokenTypes -import org.jetbrains.yaml.lexer.YAMLFlexLexer -import org.jetbrains.yaml.psi.YAMLKeyValue -import org.jetbrains.yaml.psi.YAMLSequence -import org.jetbrains.yaml.psi.YAMLSequenceItem +import it.casaricci.hass.plugin.language.HassAutomation +import org.jetbrains.yaml.YAMLWordsScanner class HassAutomationFindUsagesProvider : FindUsagesProvider { override fun getWordsScanner(): WordsScanner { - return DefaultWordsScanner( - YAMLFlexLexer(), - TokenSet.create( - YAMLTokenTypes.SCALAR_KEY, - YAMLTokenTypes.SCALAR_TEXT, - YAMLTokenTypes.TEXT, - ), - TokenSet.create( - YAMLTokenTypes.COMMENT - ), - YAMLElementTypes.SCALAR_VALUES) + return YAMLWordsScanner() } override fun canFindUsagesFor(element: PsiElement): Boolean { - thisLogger().trace("canFindUsageFor: $element") + thisLogger().trace("canFindUsageFor: $element/" + element::class.simpleName) return isMyElement(element) } @@ -51,36 +33,23 @@ class HassAutomationFindUsagesProvider : FindUsagesProvider { override fun getDescriptiveName(element: PsiElement): String { if (isMyElement(element)) { - return element.text + return (element as PsiNamedElement).name ?: "" } return "" } override fun getNodeText(element: PsiElement, useFullName: Boolean): String { - if (isMyElement(element)) { - return element.text + ":" - } - return "" + return getDescriptiveName(element) } private fun isMyElement(element: PsiElement): Boolean { - thisLogger().trace("element: $element") - val parent = if (element is YAMLKeyValue) { - thisLogger().trace("element.keyText: " + element.keyText) - element.parentMapping - } else { - thisLogger().trace("element.text: " + element.text) - element.parentOfType()?.parentMapping - } - thisLogger().trace( - "lookup parent.keyText: " + parent?.parentOfType() + return element is HassAutomation + /* TODO I believe above statement is enough -- if (element is YAMLMapping) { + return element.parentOfType() ?.parentOfType() - ?.parentOfType() - ?.keyText - ) - return parent?.parentOfType() - ?.parentOfType() - ?.parentOfType() - ?.keyText == HassKnownDomains.AUTOMATION + ?.parentOfType()?.keyText == HassKnownDomains.AUTOMATION + } + return false + */ } } diff --git a/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassScriptFindUsagesProvider.kt b/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassScriptFindUsagesProvider.kt index 315df7b..477cc5f 100644 --- a/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassScriptFindUsagesProvider.kt +++ b/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassScriptFindUsagesProvider.kt @@ -30,6 +30,7 @@ class HassScriptFindUsagesProvider : FindUsagesProvider { return "" } + // FIXME this is not used I think because element is never wrapped override fun getDescriptiveName(element: PsiElement): String { if (isMyElement(element)) { return element.text @@ -37,6 +38,7 @@ class HassScriptFindUsagesProvider : FindUsagesProvider { return "" } + // FIXME this is not used I think because element is never wrapped override fun getNodeText(element: PsiElement, useFullName: Boolean): String { if (isMyElement(element)) { return element.text + ":" diff --git a/src/main/kotlin/it/casaricci/hass/plugin/language/HassAutomation.kt b/src/main/kotlin/it/casaricci/hass/plugin/language/HassAutomation.kt index c4860e6..12c5ccc 100644 --- a/src/main/kotlin/it/casaricci/hass/plugin/language/HassAutomation.kt +++ b/src/main/kotlin/it/casaricci/hass/plugin/language/HassAutomation.kt @@ -1,17 +1,41 @@ package it.casaricci.hass.plugin.language import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile import com.intellij.psi.PsiNamedElement -import org.jetbrains.yaml.psi.impl.YAMLPsiElementImpl +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.util.IncorrectOperationException +import org.jetbrains.yaml.YAMLBundle +import org.jetbrains.yaml.YAMLElementGenerator +import org.jetbrains.yaml.psi.YAMLScalar +import org.jetbrains.yaml.psi.impl.YAMLKeyValueImpl -class HassAutomation(element: PsiElement, private val automationName: String) - : YAMLPsiElementImpl(element.node), PsiNamedElement { +/** + * Wraps the "alias" key value element of an automation, giving the element [PsiNamedElement] title. + */ +class HassAutomation(private val element: YAMLScalar) : PsiNamedElement, YAMLScalar by element { + /** + * Automation name is actually the value of the "alias" key. + */ override fun getName(): String { - return automationName + return element.textValue } + /** + * Copied from [YAMLKeyValueImpl] + [org.jetbrains.yaml.YAMLUtil], although it seems like overkill. + */ override fun setName(name: String): PsiElement { - TODO("Not yet implemented") + if (name == element.textValue) { + throw IncorrectOperationException(YAMLBundle.message("rename.same.name")) + } + + val elementGenerator = YAMLElementGenerator.getInstance(element.project) + + val tempFile: PsiFile = elementGenerator.createDummyYamlWithText(name) + val textElement = PsiTreeUtil.collectElementsOfType(tempFile, YAMLScalar::class.java).iterator().next() + + element.replace(textElement) + return this } } diff --git a/src/main/kotlin/it/casaricci/hass/plugin/psi/HassElementEvaluator.kt b/src/main/kotlin/it/casaricci/hass/plugin/psi/HassElementEvaluator.kt index f418144..e7328f3 100644 --- a/src/main/kotlin/it/casaricci/hass/plugin/psi/HassElementEvaluator.kt +++ b/src/main/kotlin/it/casaricci/hass/plugin/psi/HassElementEvaluator.kt @@ -2,7 +2,9 @@ package it.casaricci.hass.plugin.psi import com.intellij.codeInsight.TargetElementEvaluatorEx2 import com.intellij.openapi.diagnostic.thisLogger +import com.intellij.openapi.editor.Editor import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile import com.intellij.psi.util.parentOfType import it.casaricci.hass.plugin.HASS_AUTOMATION_NAME_PROPERTY import it.casaricci.hass.plugin.HassKnownDomains @@ -12,30 +14,49 @@ import org.jetbrains.yaml.psi.YAMLScalar import org.jetbrains.yaml.psi.YAMLSequence import org.jetbrains.yaml.psi.YAMLSequenceItem +// TODO refactor this a little maybe and avoid expensive operations when possible class HassElementEvaluator : TargetElementEvaluatorEx2() { override fun getNamedElement(element: PsiElement): PsiElement? { thisLogger().trace("NAMED: $element") - val maybeAutomation = getAutomationAlias(element) - if (maybeAutomation?.parentOfType() - ?.parentOfType() - ?.parentOfType()?.keyText == HassKnownDomains.AUTOMATION) { - - // return the key-value element - //return element.parent.parent - return HassAutomation(element, maybeAutomation.valueText) + val automation: YAMLScalar? = getAutomationInfoLeaf(element) + if (automation != null) { + return HassAutomation(automation) } return super.getNamedElement(element) } - private fun getAutomationAlias(element: PsiElement): YAMLKeyValue? { - val keyValue = element.parentOfType() - ?.parentOfType() - return if (keyValue?.keyText == HASS_AUTOMATION_NAME_PROPERTY) { - keyValue + override fun adjustReferenceOrReferencedElement( + file: PsiFile, + editor: Editor, + offset: Int, + flags: Int, + refElement: PsiElement? + ): PsiElement? { + thisLogger().trace("ADJUST-REFERENCED-ELEM: $refElement (flags=$flags)") + if (refElement is YAMLScalar) { + val automation = getAutomationInfo(refElement) + if (automation != null) { + return HassAutomation(refElement) + } } - else { - null + return super.adjustReferenceOrReferencedElement(file, editor, offset, flags, refElement) + } + + private fun getAutomationInfoLeaf(element: PsiElement): YAMLScalar? { + return getAutomationInfo(element.parentOfType()) + } + + private fun getAutomationInfo(element: YAMLScalar?): YAMLScalar? { + val keyValue = element?.parentOfType() + if (keyValue?.keyText == HASS_AUTOMATION_NAME_PROPERTY && + keyValue.parentOfType() + ?.parentOfType() + ?.parentOfType()?.keyText == HassKnownDomains.AUTOMATION + ) { + return element } + + return null } } diff --git a/src/main/kotlin/it/casaricci/hass/plugin/psi/HassEntityReference.kt b/src/main/kotlin/it/casaricci/hass/plugin/psi/HassEntityReference.kt index 8adc40b..6c43789 100644 --- a/src/main/kotlin/it/casaricci/hass/plugin/psi/HassEntityReference.kt +++ b/src/main/kotlin/it/casaricci/hass/plugin/psi/HassEntityReference.kt @@ -77,12 +77,16 @@ class HassEntityReference( .toTypedArray() } + /** + * Automations are identified by the value of their "alias" key. The actual PSI element is wrapped by + * [it.casaricci.hass.plugin.language.HassAutomation]. + */ private fun handleAutomation(module: Module, entityName: String): Array { val service = HassDataRepository.getInstance(module.project) return service.getAutomations(module).filter { it.valueText == entityName } - .map { result -> PsiElementResolveResult(result) } + .mapNotNull { result -> result.value?.let { PsiElementResolveResult(it) } } .toTypedArray() } From 7aaf305bd5a656fac2a5c7c5433bf1b7f94da20e Mon Sep 17 00:00:00 2001 From: Daniele Ricci Date: Tue, 4 Feb 2025 19:34:52 +0100 Subject: [PATCH 4/5] Experimenting with PsiElementEvaluator [skip ci] --- .../hass/plugin/language/HassAutomation.kt | 2 +- .../plugin/language/HassElementEvaluator.kt | 60 ++++++++++++++++++ .../hass/plugin/psi/HassElementEvaluator.kt | 62 ------------------- .../hass/plugin/psi/HassEntityReference.kt | 4 +- .../plugin/services/HassDataRepository.kt | 8 ++- .../kotlin/it/casaricci/hass/plugin/utils.kt | 15 +++++ src/main/resources/META-INF/plugin.xml | 2 +- 7 files changed, 84 insertions(+), 69 deletions(-) create mode 100644 src/main/kotlin/it/casaricci/hass/plugin/language/HassElementEvaluator.kt delete mode 100644 src/main/kotlin/it/casaricci/hass/plugin/psi/HassElementEvaluator.kt diff --git a/src/main/kotlin/it/casaricci/hass/plugin/language/HassAutomation.kt b/src/main/kotlin/it/casaricci/hass/plugin/language/HassAutomation.kt index 12c5ccc..9adb68a 100644 --- a/src/main/kotlin/it/casaricci/hass/plugin/language/HassAutomation.kt +++ b/src/main/kotlin/it/casaricci/hass/plugin/language/HassAutomation.kt @@ -11,7 +11,7 @@ import org.jetbrains.yaml.psi.YAMLScalar import org.jetbrains.yaml.psi.impl.YAMLKeyValueImpl /** - * Wraps the "alias" key value element of an automation, giving the element [PsiNamedElement] title. + * Wraps the "alias" key value element of an automation, giving the element [PsiNamedElement] features. */ class HassAutomation(private val element: YAMLScalar) : PsiNamedElement, YAMLScalar by element { diff --git a/src/main/kotlin/it/casaricci/hass/plugin/language/HassElementEvaluator.kt b/src/main/kotlin/it/casaricci/hass/plugin/language/HassElementEvaluator.kt new file mode 100644 index 0000000..fc4717e --- /dev/null +++ b/src/main/kotlin/it/casaricci/hass/plugin/language/HassElementEvaluator.kt @@ -0,0 +1,60 @@ +package it.casaricci.hass.plugin.language + +import com.intellij.codeInsight.TargetElementEvaluatorEx2 +import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiNamedElement +import com.intellij.psi.util.parentOfType +import it.casaricci.hass.plugin.isAutomation +import org.jetbrains.yaml.psi.YAMLScalar + +class HassElementEvaluator : TargetElementEvaluatorEx2() { + + /** + * This method will be called for elements that don't inherit from [com.intellij.psi.PsiNamedElement]. + * @return an actual [com.intellij.psi.PsiNamedElement] that can be named and used as a reference + */ + override fun getNamedElement(element: PsiElement): PsiElement? { + // handle text values (e.g. property values) + val textElement = element.parentOfType() + if (textElement != null) { + return wrapElement(textElement) + } + + return null + } + + /** + * This method will be called for practically every element selected by the user, so it needs to be very fast. + * @return an element wrapped with a custom class that we can recognize (if available, otherwise the same element + * will be returned) + */ + override fun adjustReferenceOrReferencedElement( + file: PsiFile, + editor: Editor, + offset: Int, + flags: Int, + refElement: PsiElement? + ): PsiElement? { + val wrappedElement = when (refElement) { + is YAMLScalar -> { + wrapElement(refElement) + } + + else -> { + null + } + } + + return wrappedElement ?: super.adjustReferenceOrReferencedElement(file, editor, offset, flags, refElement) + } + + private fun wrapElement(element: YAMLScalar): PsiNamedElement? { + if (isAutomation(element)) { + return HassAutomation(element) + } + return null + } + +} diff --git a/src/main/kotlin/it/casaricci/hass/plugin/psi/HassElementEvaluator.kt b/src/main/kotlin/it/casaricci/hass/plugin/psi/HassElementEvaluator.kt deleted file mode 100644 index e7328f3..0000000 --- a/src/main/kotlin/it/casaricci/hass/plugin/psi/HassElementEvaluator.kt +++ /dev/null @@ -1,62 +0,0 @@ -package it.casaricci.hass.plugin.psi - -import com.intellij.codeInsight.TargetElementEvaluatorEx2 -import com.intellij.openapi.diagnostic.thisLogger -import com.intellij.openapi.editor.Editor -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiFile -import com.intellij.psi.util.parentOfType -import it.casaricci.hass.plugin.HASS_AUTOMATION_NAME_PROPERTY -import it.casaricci.hass.plugin.HassKnownDomains -import it.casaricci.hass.plugin.language.HassAutomation -import org.jetbrains.yaml.psi.YAMLKeyValue -import org.jetbrains.yaml.psi.YAMLScalar -import org.jetbrains.yaml.psi.YAMLSequence -import org.jetbrains.yaml.psi.YAMLSequenceItem - -// TODO refactor this a little maybe and avoid expensive operations when possible -class HassElementEvaluator : TargetElementEvaluatorEx2() { - override fun getNamedElement(element: PsiElement): PsiElement? { - thisLogger().trace("NAMED: $element") - val automation: YAMLScalar? = getAutomationInfoLeaf(element) - if (automation != null) { - return HassAutomation(automation) - } - - return super.getNamedElement(element) - } - - override fun adjustReferenceOrReferencedElement( - file: PsiFile, - editor: Editor, - offset: Int, - flags: Int, - refElement: PsiElement? - ): PsiElement? { - thisLogger().trace("ADJUST-REFERENCED-ELEM: $refElement (flags=$flags)") - if (refElement is YAMLScalar) { - val automation = getAutomationInfo(refElement) - if (automation != null) { - return HassAutomation(refElement) - } - } - return super.adjustReferenceOrReferencedElement(file, editor, offset, flags, refElement) - } - - private fun getAutomationInfoLeaf(element: PsiElement): YAMLScalar? { - return getAutomationInfo(element.parentOfType()) - } - - private fun getAutomationInfo(element: YAMLScalar?): YAMLScalar? { - val keyValue = element?.parentOfType() - if (keyValue?.keyText == HASS_AUTOMATION_NAME_PROPERTY && - keyValue.parentOfType() - ?.parentOfType() - ?.parentOfType()?.keyText == HassKnownDomains.AUTOMATION - ) { - return element - } - - return null - } -} diff --git a/src/main/kotlin/it/casaricci/hass/plugin/psi/HassEntityReference.kt b/src/main/kotlin/it/casaricci/hass/plugin/psi/HassEntityReference.kt index 6c43789..94c6d2c 100644 --- a/src/main/kotlin/it/casaricci/hass/plugin/psi/HassEntityReference.kt +++ b/src/main/kotlin/it/casaricci/hass/plugin/psi/HassEntityReference.kt @@ -84,9 +84,9 @@ class HassEntityReference( private fun handleAutomation(module: Module, entityName: String): Array { val service = HassDataRepository.getInstance(module.project) return service.getAutomations(module).filter { - it.valueText == entityName + it.textValue == entityName } - .mapNotNull { result -> result.value?.let { PsiElementResolveResult(it) } } + .map { result -> PsiElementResolveResult(result) } .toTypedArray() } diff --git a/src/main/kotlin/it/casaricci/hass/plugin/services/HassDataRepository.kt b/src/main/kotlin/it/casaricci/hass/plugin/services/HassDataRepository.kt index e5315d4..6c8d81f 100644 --- a/src/main/kotlin/it/casaricci/hass/plugin/services/HassDataRepository.kt +++ b/src/main/kotlin/it/casaricci/hass/plugin/services/HassDataRepository.kt @@ -23,9 +23,10 @@ import org.jetbrains.yaml.YAMLUtil import org.jetbrains.yaml.psi.YAMLFile import org.jetbrains.yaml.psi.YAMLKeyValue import org.jetbrains.yaml.psi.YAMLMapping +import org.jetbrains.yaml.psi.YAMLScalar import org.jetbrains.yaml.psi.YAMLSequence -private val AUTOMATIONS_CACHE = Key>>("HASS_AUTOMATIONS_CACHE") +private val AUTOMATIONS_CACHE = Key>>("HASS_AUTOMATIONS_CACHE") private val ACTIONS_CACHE = Key>>("HASS_ACTIONS_CACHE") private val ENTITIES_CACHE = Key>>("HASS_ENTITIES_CACHE") private val SECRETS_CACHE = Key>>("HASS_SECRETS_CACHE") @@ -142,7 +143,7 @@ class HassDataRepository(private val project: Project) { /** * List of all automations. Uses local data only. */ - fun getAutomations(module: Module): Collection { + fun getAutomations(module: Module): Collection { return CachedValuesManager.getManager(project).getCachedValue( module, AUTOMATIONS_CACHE, @@ -150,6 +151,7 @@ class HassDataRepository(private val project: Project) { CachedValueProvider.Result.create(buildList { for (yamlFile in findAllYamlPsiFiles(module)) { // TODO is there a more efficient way to do this? This seems like an overkill... + // TODO this logic should be centralized somewhere maybe? YAMLUtil.getQualifiedKeyInFile(yamlFile, HassKnownDomains.AUTOMATION)?.let { automationBlock -> automationBlock.childrenOfType().firstOrNull()?.let { automations -> addAll( @@ -158,7 +160,7 @@ class HassDataRepository(private val project: Project) { automation.childrenOfType().first().keyValues .firstOrNull { automationProperty -> automationProperty.keyText == HASS_AUTOMATION_NAME_PROPERTY - } + }?.value as? YAMLScalar }) } } diff --git a/src/main/kotlin/it/casaricci/hass/plugin/utils.kt b/src/main/kotlin/it/casaricci/hass/plugin/utils.kt index 48d410c..54ca9e4 100644 --- a/src/main/kotlin/it/casaricci/hass/plugin/utils.kt +++ b/src/main/kotlin/it/casaricci/hass/plugin/utils.kt @@ -7,11 +7,14 @@ import com.intellij.openapi.util.NlsSafe import com.intellij.openapi.util.text.StringUtilRt import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement +import com.intellij.psi.util.parentOfType import com.intellij.util.io.CountingGZIPInputStream import org.apache.commons.io.input.CountingInputStream import org.jetbrains.yaml.YAMLFileType import org.jetbrains.yaml.psi.YAMLKeyValue import org.jetbrains.yaml.psi.YAMLScalar +import org.jetbrains.yaml.psi.YAMLSequence +import org.jetbrains.yaml.psi.YAMLSequenceItem import java.io.InputStream // TODO this should start from configuration.yaml and walk all includes (in order to filter out unwanted files) @@ -55,6 +58,18 @@ fun isScriptDefinition(element: PsiElement): Boolean { (element.parentMapping?.parent as YAMLKeyValue).keyText == HassKnownDomains.SCRIPT } +fun isAutomation(element: PsiElement?): Boolean { + if (element is YAMLScalar) { + val keyValue = element.parentOfType() + return (keyValue?.keyText == HASS_AUTOMATION_NAME_PROPERTY && + keyValue.parentOfType() + ?.parentOfType() + ?.parentOfType()?.keyText == HassKnownDomains.AUTOMATION + ) + } + return false +} + /** * An [InputStream] that updates a [ProgressIndicator] while being read. * To be used with [com.intellij.util.io.HttpRequests], handles also [CountingGZIPInputStream] for compressed streams. diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 16bc2e4..6b20002 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -15,7 +15,7 @@ implementation="it.casaricci.hass.plugin.psi.HassReferenceContributor"/> + implementationClass="it.casaricci.hass.plugin.language.HassElementEvaluator"/> From 5fe51704b30ed7044a1007ce19797d4e8b5c9643 Mon Sep 17 00:00:00 2001 From: Daniele Ricci Date: Tue, 4 Feb 2025 19:44:41 +0100 Subject: [PATCH 5/5] Optimize some stuff [skip ci] --- .../HassAutomationFindUsagesProvider.kt | 9 --------- .../HassScriptFindUsagesProvider.kt | 20 +++++++++++-------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassAutomationFindUsagesProvider.kt b/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassAutomationFindUsagesProvider.kt index 8b6f7d9..9aec74d 100644 --- a/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassAutomationFindUsagesProvider.kt +++ b/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassAutomationFindUsagesProvider.kt @@ -2,7 +2,6 @@ package it.casaricci.hass.plugin.findUsages import com.intellij.lang.cacheBuilder.WordsScanner import com.intellij.lang.findUsages.FindUsagesProvider -import com.intellij.openapi.diagnostic.thisLogger import com.intellij.psi.PsiElement import com.intellij.psi.PsiNamedElement import it.casaricci.hass.plugin.MyBundle @@ -16,7 +15,6 @@ class HassAutomationFindUsagesProvider : FindUsagesProvider { } override fun canFindUsagesFor(element: PsiElement): Boolean { - thisLogger().trace("canFindUsageFor: $element/" + element::class.simpleName) return isMyElement(element) } @@ -44,12 +42,5 @@ class HassAutomationFindUsagesProvider : FindUsagesProvider { private fun isMyElement(element: PsiElement): Boolean { return element is HassAutomation - /* TODO I believe above statement is enough -- if (element is YAMLMapping) { - return element.parentOfType() - ?.parentOfType() - ?.parentOfType()?.keyText == HassKnownDomains.AUTOMATION - } - return false - */ } } diff --git a/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassScriptFindUsagesProvider.kt b/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassScriptFindUsagesProvider.kt index 477cc5f..ee1b3cf 100644 --- a/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassScriptFindUsagesProvider.kt +++ b/src/main/kotlin/it/casaricci/hass/plugin/findUsages/HassScriptFindUsagesProvider.kt @@ -3,11 +3,9 @@ package it.casaricci.hass.plugin.findUsages import com.intellij.lang.cacheBuilder.WordsScanner import com.intellij.lang.findUsages.FindUsagesProvider import com.intellij.psi.PsiElement -import it.casaricci.hass.plugin.HassKnownDomains import it.casaricci.hass.plugin.MyBundle import it.casaricci.hass.plugin.isScriptDefinition import org.jetbrains.yaml.YAMLWordsScanner -import org.jetbrains.yaml.psi.YAMLKeyValue class HassScriptFindUsagesProvider : FindUsagesProvider { @@ -30,19 +28,25 @@ class HassScriptFindUsagesProvider : FindUsagesProvider { return "" } - // FIXME this is not used I think because element is never wrapped + /** + * Called but return value is not used probably because YAML plugin takes precedence. + */ override fun getDescriptiveName(element: PsiElement): String { - if (isMyElement(element)) { + // since this is not used (for now), avoid losing time doing useless stuff + /*if (isMyElement(element)) { return element.text - } + }*/ return "" } - // FIXME this is not used I think because element is never wrapped + /** + * Actually never called because YAML plugin takes precedence. + */ override fun getNodeText(element: PsiElement, useFullName: Boolean): String { - if (isMyElement(element)) { + // since this is not used (for now), avoid losing time doing useless stuff + /*if (isMyElement(element)) { return element.text + ":" - } + }*/ return "" }