From 518f9c187906e7ffc817b446582114cffeb0a828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yann=20C=C3=A9bron?= Date: Thu, 16 Jan 2025 16:24:02 +0100 Subject: [PATCH] [devkit] Resolve action IDs for `Activate$ToolwindowID$ToolWindow` (IJPL-165055) GitOrigin-RevId: 44ad796d26ce253134675264e169af55bbc6dc1b --- .../impl/ActionOrGroupResolveConverter.java | 24 ++++- .../references/ActionOrGroupIdReference.java | 102 +++++++++++++++--- .../references/ActionOrGroupIdResolveUtil.kt | 35 ++++++ .../references/ExtensionReferenceBase.java | 51 +-------- .../devkit-core/src/util/ExtensionLocator.kt | 80 +++++++++++--- .../xml/actions/ActionComplexHighlighting.xml | 16 +++ ...mlDomInspectionActionHighlightingTest.java | 15 ++- .../ActionReferenceToolWindowHighlighting.kt | 17 +++ .../actionReferenceToolWindowHighlighting.xml | 7 ++ .../codeInsight/KtActionReferenceTest.kt | 50 ++++++++- 10 files changed, 308 insertions(+), 89 deletions(-) create mode 100644 plugins/devkit/devkit-kotlin-tests/testData/codeInsight/actionReference/ActionReferenceToolWindowHighlighting.kt create mode 100644 plugins/devkit/devkit-kotlin-tests/testData/codeInsight/actionReference/actionReferenceToolWindowHighlighting.xml diff --git a/plugins/devkit/devkit-core/src/dom/impl/ActionOrGroupResolveConverter.java b/plugins/devkit/devkit-core/src/dom/impl/ActionOrGroupResolveConverter.java index b3c14da724db..939d3a4a9831 100644 --- a/plugins/devkit/devkit-core/src/dom/impl/ActionOrGroupResolveConverter.java +++ b/plugins/devkit/devkit-core/src/dom/impl/ActionOrGroupResolveConverter.java @@ -36,6 +36,8 @@ import org.jetbrains.idea.devkit.util.PluginRelatedLocatorsUtils; import java.util.*; import static com.intellij.openapi.util.NullableLazyValue.lazyNullable; +import static org.jetbrains.idea.devkit.references.ActionOrGroupIdResolveUtil.ACTIVATE_TOOLWINDOW_ACTION_PREFIX; +import static org.jetbrains.idea.devkit.references.ActionOrGroupIdResolveUtil.ACTIVATE_TOOLWINDOW_ACTION_SUFFIX; public class ActionOrGroupResolveConverter extends ResolvingConverter { @@ -48,13 +50,25 @@ public class ActionOrGroupResolveConverter extends ResolvingConverter getAdditionalVariants(@NotNull ConvertContext context) { if (!isActionsAllowed()) return Collections.emptySet(); - // add executor IDs here as valid results - Set executorIds = new HashSet<>(); - ActionOrGroupIdResolveUtil.processExecutors(context.getProject(), (id, psiClass) -> { - executorIds.add(id); + // add executor/activate toolwindow IDs here as valid results + Project project = context.getProject(); + Set additionalIds = new HashSet<>(); + ActionOrGroupIdResolveUtil.processExecutors(project, (id, psiClass) -> { + additionalIds.add(id); return true; }); - return executorIds; + ActionOrGroupIdResolveUtil.processActivateToolWindowActions(project, extension -> { + additionalIds.add(ACTIVATE_TOOLWINDOW_ACTION_PREFIX + + ActionOrGroupIdResolveUtil.getToolWindowIdValue(extension) + + ACTIVATE_TOOLWINDOW_ACTION_SUFFIX); + return true; + }); + ActionOrGroupIdResolveUtil.processToolWindowId(project, (id, field) -> { + additionalIds.add(ACTIVATE_TOOLWINDOW_ACTION_PREFIX + id + ACTIVATE_TOOLWINDOW_ACTION_SUFFIX); + return true; + }); + + return additionalIds; } @ApiStatus.Internal diff --git a/plugins/devkit/devkit-core/src/references/ActionOrGroupIdReference.java b/plugins/devkit/devkit-core/src/references/ActionOrGroupIdReference.java index ce7296fd9b2b..e029dcff7fba 100644 --- a/plugins/devkit/devkit-core/src/references/ActionOrGroupIdReference.java +++ b/plugins/devkit/devkit-core/src/references/ActionOrGroupIdReference.java @@ -4,8 +4,10 @@ package org.jetbrains.idea.devkit.references; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementBuilder; import com.intellij.execution.Executor; +import com.intellij.ide.actions.QualifiedNameProviderUtil; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; @@ -19,15 +21,18 @@ import com.intellij.util.PairProcessor; import com.intellij.util.SmartList; import com.intellij.util.ThreeState; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.JBIterable; +import com.intellij.util.xml.DomElement; import com.intellij.util.xml.DomTarget; +import com.intellij.util.xml.GenericAttributeValue; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.devkit.DevKitBundle; import org.jetbrains.idea.devkit.dom.ActionOrGroup; +import org.jetbrains.idea.devkit.dom.Extension; import org.jetbrains.idea.devkit.dom.OverrideText; import org.jetbrains.idea.devkit.dom.impl.ActionOrGroupResolveConverter; import org.jetbrains.idea.devkit.dom.index.IdeaPluginRegistrationIndex; +import org.jetbrains.idea.devkit.util.DevKitDomUtil; import org.jetbrains.idea.devkit.util.PluginRelatedLocatorsUtils; import org.jetbrains.uast.UExpression; @@ -35,6 +40,9 @@ import javax.swing.*; import java.util.List; import java.util.Objects; +import static org.jetbrains.idea.devkit.references.ActionOrGroupIdResolveUtil.ACTIVATE_TOOLWINDOW_ACTION_PREFIX; +import static org.jetbrains.idea.devkit.references.ActionOrGroupIdResolveUtil.ACTIVATE_TOOLWINDOW_ACTION_SUFFIX; + final class ActionOrGroupIdReference extends PsiPolyVariantReferenceBase implements PluginConfigReference { private final String myId; @@ -64,10 +72,44 @@ final class ActionOrGroupIdReference extends PsiPolyVariantReferenceBase toolwindowExtension = Ref.create(); + ActionOrGroupIdResolveUtil.processActivateToolWindowActions(project, extension -> { + String idValue = ActionOrGroupIdResolveUtil.getToolWindowIdValue(extension); + if (Comparing.strEqual(toolwindowId, idValue)) { + toolwindowExtension.set(extension); + return false; + } + return true; + }); + if (!toolwindowExtension.isNull()) { + return PsiElementResolveResult.createResults(getDomTargetPsi(toolwindowExtension.get())); + } + + // known (programmatic) ToolWindow IDs + Ref knownToolWindowIdField = Ref.create(); + ActionOrGroupIdResolveUtil.processToolWindowId(project, (s, field) -> { + if (Comparing.strEqual(s, toolwindowId)) { + knownToolWindowIdField.set(field); + return false; + } + return true; + }); + if (!knownToolWindowIdField.isNull()) { + return PsiElementResolveResult.createResults(knownToolWindowIdField.get()); + } + return ResolveResult.EMPTY_ARRAY; + } } // action|group.ActionId..text @@ -92,11 +134,7 @@ final class ActionOrGroupIdReference extends PsiPolyVariantReferenceBase psiElements = - JBIterable.from(processor.getResults()) - .map(actionOrGroup -> { - final DomTarget target = DomTarget.getTarget(actionOrGroup); - return target == null ? null : PomService.convertToPsi(project, target); - }).filter(Objects::nonNull).toList(); + ContainerUtil.mapNotNull(processor.getResults(), actionOrGroup -> getDomTargetPsi(actionOrGroup)); return PsiElementResolveResult.createResults(psiElements); } @@ -113,24 +151,52 @@ final class ActionOrGroupIdReference extends PsiPolyVariantReferenceBase domVariants = - converter.getVariants(getElement().getProject(), ModuleUtilCore.findModuleForPsiElement(getElement())); + Project project = getElement().getProject(); + List domVariants = converter.getVariants(project, ModuleUtilCore.findModuleForPsiElement(getElement())); List domLookupElements = ContainerUtil.map(domVariants, actionOrGroup -> converter.createLookupElement(actionOrGroup)); if (myIsAction == ThreeState.NO) { return domLookupElements.toArray(); } List executorLookupElements = new SmartList<>(); - ActionOrGroupIdResolveUtil.processExecutors(getElement().getProject(), (id, psiClass) -> { + ActionOrGroupIdResolveUtil.processExecutors(project, (id, psiClass) -> { LookupElementBuilder builder = LookupElementBuilder.create(psiClass, id) .bold() - .withIcon(computeIcon(id, psiClass)) - .withTailText(computeTailText(id), true) + .withIcon(computeExecutorIcon(id, psiClass)) + .withTailText(computeExecutorTailText(id), true) .withTypeText(psiClass.getQualifiedName(), true); executorLookupElements.add(builder); return true; }); - return ContainerUtil.concat(domLookupElements, executorLookupElements).toArray(); + + + List activateToolWindowElements = new SmartList<>(); + ActionOrGroupIdResolveUtil.processActivateToolWindowActions(project, extension -> { + GenericAttributeValue factoryClass = DevKitDomUtil.getAttribute(extension, "factoryClass"); + + LookupElementBuilder builder = + LookupElementBuilder.create(getDomTargetPsi(extension), + ACTIVATE_TOOLWINDOW_ACTION_PREFIX + + ActionOrGroupIdResolveUtil.getToolWindowIdValue(extension) + + ACTIVATE_TOOLWINDOW_ACTION_SUFFIX) + .bold() + .withTypeText(factoryClass != null ? factoryClass.getStringValue() : "", true); + activateToolWindowElements.add(builder); + return true; + }); + + ActionOrGroupIdResolveUtil.processToolWindowId(project, (value, field) -> { + LookupElementBuilder builder = + LookupElementBuilder.create(field, + ACTIVATE_TOOLWINDOW_ACTION_PREFIX + value + ACTIVATE_TOOLWINDOW_ACTION_SUFFIX) + .bold() + .withStrikeoutness(field.isDeprecated()) + .withTypeText(QualifiedNameProviderUtil.getQualifiedName(field), true); + activateToolWindowElements.add(builder); + return true; + }); + + return ContainerUtil.concat(domLookupElements, executorLookupElements, activateToolWindowElements).toArray(); } @Override @@ -151,7 +217,13 @@ final class ActionOrGroupIdReference extends PsiPolyVariantReferenceBase) { // not necessarily in the element's resolve scope @@ -40,6 +52,29 @@ internal object ActionOrGroupIdResolveUtil { } } + @JvmStatic + fun processActivateToolWindowActions(project: Project, processor: Processor) { + processExtensionCandidates(project, "com.intellij.toolWindow", processor, null, null) + } + + @JvmStatic + fun getToolWindowIdValue(extension : Extension) : String? { + return extension.id.stringValue?.replace(" ", "") + } + + @JvmStatic + fun processToolWindowId(project: Project, processor: PairProcessor) { + val toolWindowIdClass = + JavaPsiFacade.getInstance(project).findClass("com.intellij.openapi.wm.ToolWindowId", GlobalSearchScope.allScope(project)) + if (toolWindowIdClass == null) return + + for (field in toolWindowIdClass.getFields()) { + val initializer = field.toUElement(UField::class.java)?.uastInitializer ?: continue + val value = initializer.evaluateString() ?: continue + if (!processor.process(value, field)) return + } + } + private fun computeConstantReturnValue( psiClass: PsiClass, methodName: String, diff --git a/plugins/devkit/devkit-core/src/references/ExtensionReferenceBase.java b/plugins/devkit/devkit-core/src/references/ExtensionReferenceBase.java index 39179030f182..f293cc9b23fd 100644 --- a/plugins/devkit/devkit-core/src/references/ExtensionReferenceBase.java +++ b/plugins/devkit/devkit-core/src/references/ExtensionReferenceBase.java @@ -1,18 +1,14 @@ -// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.idea.devkit.references; -import com.intellij.openapi.project.Project; import com.intellij.openapi.util.NlsSafe; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.pom.references.PomService; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiReferenceBase; -import com.intellij.psi.xml.XmlTag; import com.intellij.util.CommonProcessors; import com.intellij.util.Processor; -import com.intellij.util.xml.DomElement; -import com.intellij.util.xml.DomManager; import com.intellij.util.xml.DomTarget; import com.intellij.util.xml.GenericAttributeValue; import com.intellij.util.xml.reflect.DomAttributeChildDescription; @@ -20,12 +16,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.devkit.dom.Extension; import org.jetbrains.idea.devkit.dom.ExtensionPoint; -import org.jetbrains.idea.devkit.dom.index.ExtensionPointIndex; -import org.jetbrains.idea.devkit.util.ExtensionCandidate; import org.jetbrains.idea.devkit.util.ExtensionLocatorKt; -import org.jetbrains.idea.devkit.util.PluginRelatedLocatorsUtils; - -import java.util.List; abstract class ExtensionReferenceBase extends PsiReferenceBase implements PluginConfigReference { @@ -57,7 +48,8 @@ abstract class ExtensionReferenceBase extends PsiReferenceBase imple if (StringUtil.isEmptyOrSpaces(resolveId)) return null; final CommonProcessors.FindProcessor resolveProcessor = new CommonProcessors.FindFirstProcessor<>(); - processCandidates(resolveProcessor, resolveId); + ExtensionLocatorKt.processExtensionCandidates(myElement.getProject(), getExtensionPointFqn(), resolveProcessor, + resolveId, extension -> getNameElement(extension)); final Extension value = resolveProcessor.getFoundValue(); if (value == null) return null; @@ -85,40 +77,7 @@ abstract class ExtensionReferenceBase extends PsiReferenceBase imple } protected void processCandidates(Processor processor) { - processCandidates(processor, null); - } - - /** - * @param extensionPointId To locate a specific extension instance by ID or {@code null} to process all. - */ - private void processCandidates(Processor processor, - @Nullable String extensionPointId) { - final Project project = myElement.getProject(); - - final ExtensionPoint extensionPointDomElement = - ExtensionPointIndex.findExtensionPoint(project, PluginRelatedLocatorsUtils.getCandidatesScope(project), getExtensionPointFqn()); - if (extensionPointDomElement == null) return; - - final List candidates; - if (extensionPointId == null) { - candidates = ExtensionLocatorKt.locateExtensionsByExtensionPoint(extensionPointDomElement); - } - else { - candidates = ExtensionLocatorKt. - locateExtensionsByExtensionPointAndId(extensionPointDomElement, extensionPointId, - extension -> { - final GenericAttributeValue nameElement = getNameElement(extension); - return nameElement != null ? nameElement.getStringValue() : null; - }).findCandidates(); - } - - final DomManager manager = DomManager.getDomManager(project); - for (ExtensionCandidate candidate : candidates) { - final XmlTag element = candidate.pointer.getElement(); - final DomElement domElement = manager.getDomElement(element); - if (domElement instanceof Extension) { - if (!processor.process((Extension)domElement)) return; - } - } + ExtensionLocatorKt.processExtensionCandidates(myElement.getProject(), getExtensionPointFqn(), processor, + null, null); } } diff --git a/plugins/devkit/devkit-core/src/util/ExtensionLocator.kt b/plugins/devkit/devkit-core/src/util/ExtensionLocator.kt index 1fc39ce11e6b..e05e67977899 100644 --- a/plugins/devkit/devkit-core/src/util/ExtensionLocator.kt +++ b/plugins/devkit/devkit-core/src/util/ExtensionLocator.kt @@ -3,21 +3,23 @@ package org.jetbrains.idea.devkit.util import com.intellij.openapi.project.Project import com.intellij.openapi.util.text.StringUtil -import com.intellij.platform.ide.progress.ModalTaskOwner.project import com.intellij.psi.PsiClass import com.intellij.psi.SmartPointerManager -import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.PsiSearchHelper import com.intellij.psi.search.SearchScope import com.intellij.psi.search.UsageSearchContext import com.intellij.psi.util.ClassUtil import com.intellij.psi.xml.XmlTag import com.intellij.util.PerformanceAssertions +import com.intellij.util.Processor import com.intellij.util.SmartList import com.intellij.util.xml.DomManager +import com.intellij.util.xml.GenericAttributeValue import org.jetbrains.idea.devkit.dom.Extension import org.jetbrains.idea.devkit.dom.ExtensionPoint +import org.jetbrains.idea.devkit.dom.index.ExtensionPointIndex import java.util.* +import java.util.function.Function fun locateExtensionsByPsiClass(psiClass: PsiClass): List { return findExtensionsByClassName(psiClass.project, ClassUtil.getJVMClassName(psiClass) ?: return emptyList()) @@ -34,22 +36,62 @@ fun locateExtensionsByExtensionPointAndId(extensionPoint: ExtensionPoint, extens /** * @param extensionIdFunction in case EP is located via custom attribute instead of [Extension.getId]. */ -fun locateExtensionsByExtensionPointAndId(extensionPoint: ExtensionPoint, - extensionId: String, - extensionIdFunction: (Extension) -> String?): ExtensionLocator { +fun locateExtensionsByExtensionPointAndId( + extensionPoint: ExtensionPoint, + extensionId: String, + extensionIdFunction: (Extension) -> String?, +): ExtensionLocator { return ExtensionByExtensionPointLocator(extensionPoint.xmlTag.project, extensionPoint, extensionId, extensionIdFunction) } +/** + * @param extensionPointId To locate a specific extension instance by ID or `null` to process all. + * @param nameElementFunction Returns the DOM `id` element when [extensionPointId] is passed. + */ +fun processExtensionCandidates( + project: Project, + extensionPointFqn: String, + processor: Processor, + extensionPointId: String?, + nameElementFunction: Function?>?, +) { + val extensionPointDomElement = + ExtensionPointIndex.findExtensionPoint(project, PluginRelatedLocatorsUtils.getCandidatesScope(project), extensionPointFqn) + if (extensionPointDomElement == null) return + + val candidates: List + if (extensionPointId == null) { + candidates = locateExtensionsByExtensionPoint(extensionPointDomElement) + } + else { + candidates = locateExtensionsByExtensionPointAndId(extensionPointDomElement, extensionPointId) { extension: Extension -> + val nameElement = nameElementFunction!!.apply(extension) + nameElement?.getStringValue() + }.findCandidates() + } + + val manager = DomManager.getDomManager(project) + for (candidate in candidates) { + val element = candidate.pointer.getElement() + val domElement = manager.getDomElement(element) + if (domElement is Extension) { + if (!processor.process(domElement)) return + } + } +} + // TODO consider converting to a stream-like entity to avoid IDEA-277738, EA-139648, etc. /** * A synchronized collection should be used as an accumulator in callbacks. */ @JvmOverloads -fun processExtensionDeclarations(name: String, - project: Project, - strictMatch: Boolean = true, - scope : SearchScope = PluginRelatedLocatorsUtils.getCandidatesScope(project), - callback: (Extension, XmlTag) -> Boolean) { +fun processExtensionDeclarations( + name: String, + project: Project, + strictMatch: Boolean = true, + scope: SearchScope = PluginRelatedLocatorsUtils.getCandidatesScope(project), + callback: (Extension, XmlTag) -> Boolean, +) { PerformanceAssertions.assertDoesNotAffectHighlighting() val searchWord = name.substringBeforeLast('$') @@ -82,18 +124,22 @@ private fun findExtensionsByClassName(project: Project, className: String): List return result } -internal inline fun processExtensionsByClassName(project: Project, - className: String, - crossinline processor: (XmlTag, ExtensionPoint) -> Boolean) { +internal inline fun processExtensionsByClassName( + project: Project, + className: String, + crossinline processor: (XmlTag, ExtensionPoint) -> Boolean, +) { processExtensionDeclarations(className, project) { extension, tag -> extension.extensionPoint?.let { processor(tag, it) } ?: true } } -internal class ExtensionByExtensionPointLocator(private val project: Project, - extensionPoint: ExtensionPoint, - private val extensionId: String?, - private val extensionIdFunction: (Extension) -> String? = { extension -> extension.id.stringValue }) : ExtensionLocator() { +private class ExtensionByExtensionPointLocator( + private val project: Project, + extensionPoint: ExtensionPoint, + private val extensionId: String?, + private val extensionIdFunction: (Extension) -> String? = { extension -> extension.id.stringValue }, +) : ExtensionLocator() { private val pointQualifiedName = extensionPoint.effectiveQualifiedName private fun processCandidates(processor: (XmlTag) -> Boolean) { diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/registrationProblems/xml/actions/ActionComplexHighlighting.xml b/plugins/devkit/devkit-java-tests/testData/inspections/registrationProblems/xml/actions/ActionComplexHighlighting.xml index 3d9b7ab5ea8f..839756af245d 100644 --- a/plugins/devkit/devkit-java-tests/testData/inspections/registrationProblems/xml/actions/ActionComplexHighlighting.xml +++ b/plugins/devkit/devkit-java-tests/testData/inspections/registrationProblems/xml/actions/ActionComplexHighlighting.xml @@ -114,4 +114,20 @@ MyExecutorId"/> + + + + + + + + + + + + + + ActivateToolWindowIdToolWindow"/> + + \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/PluginXmlDomInspectionActionHighlightingTest.java b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/PluginXmlDomInspectionActionHighlightingTest.java index 42ca76590a72..9c02f7db8643 100644 --- a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/PluginXmlDomInspectionActionHighlightingTest.java +++ b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/PluginXmlDomInspectionActionHighlightingTest.java @@ -58,10 +58,10 @@ public class PluginXmlDomInspectionActionHighlightingTest extends PluginXmlDomIn // fake Executor myFixture.addClass(""" package com.intellij.execution; - public abstract class Executor { - public abstract String getId(); - public abstract String getContextActionId(); - } + public abstract class Executor { + public abstract String getId(); + public abstract String getContextActionId(); + } """); myFixture.addClass(""" public class MyExecutor extends com.intellij.execution.Executor { @@ -76,6 +76,13 @@ public class PluginXmlDomInspectionActionHighlightingTest extends PluginXmlDomIn } } """); + // toolwindow ID + myFixture.addClass(""" + package com.intellij.openapi.wm; + interface ToolWindowId { + String FAVORITES = "ToolWindowIdFromConstants"; + } + """); myFixture.addFileToProject("keymaps/MyKeymap.xml", ""); myFixture.testHighlighting("ActionComplexHighlighting.xml"); diff --git a/plugins/devkit/devkit-kotlin-tests/testData/codeInsight/actionReference/ActionReferenceToolWindowHighlighting.kt b/plugins/devkit/devkit-kotlin-tests/testData/codeInsight/actionReference/ActionReferenceToolWindowHighlighting.kt new file mode 100644 index 000000000000..769d78004166 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/codeInsight/actionReference/ActionReferenceToolWindowHighlighting.kt @@ -0,0 +1,17 @@ +import com.intellij.openapi.actionSystem.ex.ActionUtil + +class ActionReferenceToolWindowHighlighting { + + fun testToolWindowExtensionHighlighting() { + ActionUtil.wrap("ActivateToolWindowIdToolWindow") + ActionUtil.wrap("ActivateToolWindowIdWithSpacesToolWindow") + + ActionUtil.wrap("ActivateINVALID_VALUEToolWindow") + + ActionUtil.getActionGroup("ActivateToolWindowIdToolWindow") + } + + fun testToolWindowToolWindowIdHighlighting() { + ActionUtil.wrap("ActivateToolWindowIdFromConstantsToolWindow") + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/codeInsight/actionReference/actionReferenceToolWindowHighlighting.xml b/plugins/devkit/devkit-kotlin-tests/testData/codeInsight/actionReference/actionReferenceToolWindowHighlighting.xml new file mode 100644 index 000000000000..6878956b3ed1 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/codeInsight/actionReference/actionReferenceToolWindowHighlighting.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/codeInsight/KtActionReferenceTest.kt b/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/codeInsight/KtActionReferenceTest.kt index eca7ed95c69a..33fdde95c35f 100644 --- a/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/codeInsight/KtActionReferenceTest.kt +++ b/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/codeInsight/KtActionReferenceTest.kt @@ -1,6 +1,8 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.idea.devkit.kotlin.codeInsight +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.codeInsight.lookup.LookupElementPresentation import com.intellij.codeInspection.LocalInspectionEP import com.intellij.execution.executors.DefaultRunExecutor import com.intellij.openapi.actionSystem.ActionManager @@ -13,7 +15,6 @@ import com.intellij.ui.components.JBList import com.intellij.util.PathUtil import org.jetbrains.idea.devkit.inspections.UnresolvedPluginConfigReferenceInspection import org.jetbrains.idea.devkit.kotlin.DevkitKtTestsUtil -import kotlin.jvm.java @TestDataPath("\$CONTENT_ROOT/testData/codeInsight/actionReference") class KtActionReferenceTest : JavaCodeInsightFixtureTestCase() { @@ -128,6 +129,46 @@ class KtActionReferenceTest : JavaCodeInsightFixtureTestCase() { myFixture.testHighlighting("ActionReferenceHighlighting.kt") } + fun testActionReferenceToolWindowHighlighting() { + myFixture.enableInspections(UnresolvedPluginConfigReferenceInspection::class.java) + configureToolWindowTest() + + myFixture.testHighlighting("ActionReferenceToolWindowHighlighting.kt") + } + + fun testActionReferenceToolWindowCompletion() { + configureToolWindowTest() + myFixture.configureByText("Caller.kt", """ + fun usage(actionManager: com.intellij.openapi.actionSystem.ActionManager){ + actionManager.getAction("ActivateT") + } + """.trimIndent()) + + assertContainsElements(myFixture.getCompletionVariants("Caller.kt").orEmpty(), + "ActivateToolWindowIdToolWindow", "ActivateToolWindowIdWithSpacesToolWindow", + "ActivateToolWindowIdFromConstantsToolWindow", "ActivateToolWindowIdFromConstants_DeprecatedToolWindow") + + val extension = getLookupElementPresentation("ActivateToolWindowIdToolWindow") + assertTrue(extension.isItemTextBold) + assertEquals("FactoryClass", extension.typeText) + + val deprecated = getLookupElementPresentation("ActivateToolWindowIdFromConstants_DeprecatedToolWindow") + assertTrue(deprecated.isStrikeout) + assertEquals("com.intellij.openapi.wm.ToolWindowId#DEPRECATED", deprecated.typeText) + } + + private fun configureToolWindowTest() { + myFixture.copyFileToProject("actionReferenceToolWindowHighlighting.xml") + myFixture.addClass(""" + package com.intellij.openapi.wm; + interface ToolWindowId { + String FAVORITES = "ToolWindowIdFromConstants"; + @Deprecated + String DEPRECATED = "ToolWindowIdFromConstants_Deprecated"; + } + """.trimIndent()) + } + fun testRenameGroup() { myFixture.createFile("plugin.xml", pluginXmlActions(""" @@ -151,4 +192,9 @@ class KtActionReferenceTest : JavaCodeInsightFixtureTestCase() { private val DLR = '$'.toString() -} \ No newline at end of file + private fun getLookupElementPresentation(lookupString: String): LookupElementPresentation { + val lookupElement: LookupElement? = myFixture.getLookupElements()!!.find({ element: LookupElement -> element.getLookupString() == lookupString }) + assertNotNull(lookupString, lookupElement) + return LookupElementPresentation.renderElement(lookupElement) + } +}