diff --git a/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties b/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties
index 7e45b0d23438..2551d402e2af 100644
--- a/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties
+++ b/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties
@@ -359,6 +359,8 @@ implemented.at.runtime.jamElement=JAM Element implemented at runtime
line.marker.tooltip.extension.declaration={2} declaration in {3}
line.marker.tooltip.extension.point.declaration={2} extension point declaration in {3}
line.marker.tooltip.listener.declaration={2} listener in {3}
+line.marker.tooltip.action.declaration={2} action in {3}
+line.marker.tooltip.action.group.declaration={2} action group in {3}
inspections.usage.uelement.as.psi=Usage of UElement as PsiElement is not recommended
@@ -487,6 +489,8 @@ gutter.related.navigation.popup.description.tooltip=Description
gutter.related.navigation.choose.extension=Choose Extension
gutter.related.navigation.choose.extension.point=Choose Extension Point
gutter.related.navigation.choose.listener=Choose Listener
+gutter.related.navigation.choose.action=Choose Action
+gutter.related.navigation.choose.action.group=Choose Action Group
# plugin.xml resolving
plugin.xml.convert.action.or.group.cannot.resolve=Cannot resolve {0} ''{1}''
diff --git a/plugins/devkit/devkit-core/src/dom/index/IdeaPluginRegistrationIndex.java b/plugins/devkit/devkit-core/src/dom/index/IdeaPluginRegistrationIndex.java
index 296125e6f880..1e819cc3c07c 100644
--- a/plugins/devkit/devkit-core/src/dom/index/IdeaPluginRegistrationIndex.java
+++ b/plugins/devkit/devkit-core/src/dom/index/IdeaPluginRegistrationIndex.java
@@ -23,6 +23,7 @@ import com.intellij.util.io.KeyDescriptor;
import com.intellij.util.xml.DomElement;
import com.intellij.util.xml.DomManager;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.devkit.dom.*;
import org.jetbrains.idea.devkit.dom.index.RegistrationEntry.RegistrationType;
@@ -95,20 +96,24 @@ public class IdeaPluginRegistrationIndex extends PluginXmlIndexBase ContainerUtil.process(value, entry -> !(entry.getRegistrationType() == type)),
+ (file, value) -> ContainerUtil.process(value, entry -> {
+ RegistrationType registrationType = entry.getRegistrationType();
+ if (type == null) {
+ return !(registrationType.isClass());
+ }
+
+ return !(registrationType == type);
+ }),
scope);
}
@@ -149,6 +164,18 @@ public class IdeaPluginRegistrationIndex extends PluginXmlIndexBase processActionOrGroup(project, s, scope, processor));
}
+ public static boolean processActionOrGroupClass(@NotNull Project project,
+ PsiClass actionOrGroupClass,
+ GlobalSearchScope scope,
+ Processor super ActionOrGroup> processor) {
+ String fqn = actionOrGroupClass.getQualifiedName();
+ if (fqn == null) {
+ return true;
+ }
+
+ return doProcessActionOrGroup(project, fqn, scope, EnumSet.of(RegistrationType.ACTION), processor);
+ }
+
public static boolean processActionOrGroup(@NotNull Project project,
@NotNull String actionOrGroupId,
GlobalSearchScope scope,
@@ -173,11 +200,11 @@ public class IdeaPluginRegistrationIndex extends PluginXmlIndexBase types,
Processor super ActionOrGroup> processor) {
- List tags = collectTags(project, actionOrGroupId, scope, types);
+ List tags = collectTags(project, key, scope, types);
return ContainerUtil.process(tags, tag -> {
final DomElement domElement = DomManager.getDomManager(project).getDomElement(tag);
diff --git a/plugins/devkit/devkit-core/src/dom/index/RegistrationEntry.java b/plugins/devkit/devkit-core/src/dom/index/RegistrationEntry.java
index ec2c8a849edd..ab87f031a1d6 100644
--- a/plugins/devkit/devkit-core/src/dom/index/RegistrationEntry.java
+++ b/plugins/devkit/devkit-core/src/dom/index/RegistrationEntry.java
@@ -41,16 +41,26 @@ class RegistrationEntry {
// do not change order
enum RegistrationType {
- ACTION,
+ ACTION(true),
- APPLICATION_COMPONENT,
- PROJECT_COMPONENT,
- MODULE_COMPONENT,
+ APPLICATION_COMPONENT(true),
+ PROJECT_COMPONENT(true),
+ MODULE_COMPONENT(true),
- ACTION_ID,
- ACTION_GROUP_ID,
+ ACTION_ID(false),
+ ACTION_GROUP_ID(false),
- APPLICATION_LISTENER,
- PROJECT_LISTENER
+ APPLICATION_LISTENER(true),
+ PROJECT_LISTENER(true);
+
+ private final boolean myIsClass;
+
+ RegistrationType(boolean isClass) {
+ myIsClass = isClass;
+ }
+
+ boolean isClass() {
+ return myIsClass;
+ }
}
}
diff --git a/plugins/devkit/devkit-core/src/inspections/RegistrationCheckerUtil.java b/plugins/devkit/devkit-core/src/inspections/RegistrationCheckerUtil.java
index 1e64447e6080..fa97d393c0ed 100644
--- a/plugins/devkit/devkit-core/src/inspections/RegistrationCheckerUtil.java
+++ b/plugins/devkit/devkit-core/src/inspections/RegistrationCheckerUtil.java
@@ -1,4 +1,4 @@
-// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.inspections;
import com.intellij.openapi.module.Module;
@@ -177,8 +177,8 @@ final class RegistrationCheckerUtil {
}
if (findAll || myRegistrationType == RegistrationType.ACTION) {
- if (IdeaPluginRegistrationIndex.isRegisteredAction(myPsiClass,
- scope)) {
+ if (IdeaPluginRegistrationIndex.isRegisteredActionOrGroup(myPsiClass,
+ scope)) {
addType(ActionType.ACTION.myClassName);
return false;
}
diff --git a/plugins/devkit/devkit-core/src/navigation/LineMarkerInfoHelper.java b/plugins/devkit/devkit-core/src/navigation/LineMarkerInfoHelper.java
index 497983e7de52..eaf1e0255880 100644
--- a/plugins/devkit/devkit-core/src/navigation/LineMarkerInfoHelper.java
+++ b/plugins/devkit/devkit-core/src/navigation/LineMarkerInfoHelper.java
@@ -2,11 +2,13 @@
package org.jetbrains.idea.devkit.navigation;
import com.intellij.codeInsight.daemon.RelatedItemLineMarkerInfo;
+import com.intellij.codeInsight.navigation.DomGotoRelatedItem;
import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder;
import com.intellij.navigation.GotoRelatedItem;
import com.intellij.openapi.editor.markup.GutterIconRenderer;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
+import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.text.HtmlBuilder;
import com.intellij.openapi.util.text.HtmlChunk;
import com.intellij.openapi.util.text.StringUtil;
@@ -25,13 +27,16 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.PropertyKey;
import org.jetbrains.idea.devkit.DevKitBundle;
import org.jetbrains.idea.devkit.DevKitIcons;
+import org.jetbrains.idea.devkit.dom.ActionOrGroup;
import org.jetbrains.idea.devkit.dom.Extension;
import org.jetbrains.idea.devkit.dom.ExtensionPoint;
import org.jetbrains.idea.devkit.dom.Listeners;
+import org.jetbrains.idea.devkit.util.ActionCandidate;
import org.jetbrains.idea.devkit.util.ListenerCandidate;
import org.jetbrains.idea.devkit.util.PointableCandidate;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
final class LineMarkerInfoHelper {
@@ -39,7 +44,7 @@ final class LineMarkerInfoHelper {
private static final NotNullFunction> CONVERTER =
candidate -> ContainerUtil.createMaybeSingletonList(candidate.pointer.getElement());
private static final NotNullFunction> RELATED_ITEM_PROVIDER =
- candidate -> GotoRelatedItem.createItems(ContainerUtil.createMaybeSingletonList(candidate.pointer.getElement()), "DevKit");
+ candidate -> Collections.singleton(new DomGotoRelatedItem(DomUtil.getDomElement(candidate.pointer.getElement()), "DevKit"));
private static final NullableFunction EXTENSION_NAMER =
createNamer("line.marker.tooltip.extension.declaration", tag -> {
@@ -65,6 +70,18 @@ final class LineMarkerInfoHelper {
return StringUtil.notNullize(((Listeners.Listener)element).getTopicClassName().getStringValue(), "?");
});
+ private static final NotNullFunction ACTION_OR_GROUP_NAME_FUNCTION = tag -> {
+ final DomElement element = DomUtil.getDomElement(tag);
+ if (!(element instanceof ActionOrGroup)) return "?";
+ return StringUtil.notNullize(((ActionOrGroup)element).getId().getStringValue(), "?");
+ };
+
+ private static final NullableFunction ACTION_NAMER =
+ createNamer("line.marker.tooltip.action.declaration", ACTION_OR_GROUP_NAME_FUNCTION);
+
+ private static final NullableFunction ACTION_GROUP_NAMER =
+ createNamer("line.marker.tooltip.action.group.declaration", ACTION_OR_GROUP_NAME_FUNCTION);
+
private LineMarkerInfoHelper() {
}
@@ -89,6 +106,18 @@ final class LineMarkerInfoHelper {
LISTENER_NAMER);
}
+ static RelatedItemLineMarkerInfo> createActionLineMarkerInfo(List extends ActionCandidate> targets, PsiElement element) {
+ return createPluginLineMarkerInfo(targets, element,
+ DevKitBundle.message("gutter.related.navigation.choose.action"),
+ ACTION_NAMER);
+ }
+
+ static RelatedItemLineMarkerInfo> createActionGroupLineMarkerInfo(List extends ActionCandidate> targets, PsiElement element) {
+ return createPluginLineMarkerInfo(targets, element,
+ DevKitBundle.message("gutter.related.navigation.choose.action.group"),
+ ACTION_GROUP_NAMER);
+ }
+
private static @NotNull RelatedItemLineMarkerInfo createPluginLineMarkerInfo(@NotNull List extends PointableCandidate> targets,
@NotNull PsiElement element,
@Nls(capitalization = Nls.Capitalization.Title) String popup,
diff --git a/plugins/devkit/devkit-core/src/navigation/PluginDescriptorDeclarationRelatedItemLineMarkerProvider.java b/plugins/devkit/devkit-core/src/navigation/PluginDescriptorDeclarationRelatedItemLineMarkerProvider.java
index 843543105cab..8c0c6ab0b4f7 100644
--- a/plugins/devkit/devkit-core/src/navigation/PluginDescriptorDeclarationRelatedItemLineMarkerProvider.java
+++ b/plugins/devkit/devkit-core/src/navigation/PluginDescriptorDeclarationRelatedItemLineMarkerProvider.java
@@ -2,10 +2,12 @@
package org.jetbrains.idea.devkit.navigation;
import com.intellij.codeInsight.daemon.RelatedItemLineMarkerInfo;
+import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.util.InheritanceUtil;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.devkit.DevKitBundle;
@@ -20,6 +22,7 @@ import java.util.List;
/**
* Related declaration(s) in {@code plugin.xml} for class registered as:
*
+ * - Action/ActionGroup
* - Listener
* - Extension
*
@@ -48,6 +51,32 @@ public final class PluginDescriptorDeclarationRelatedItemLineMarkerProvider exte
Project project = psiClass.getProject();
GlobalSearchScope candidatesScope = PluginRelatedLocatorsUtils.getCandidatesScope(project);
+ // only EPs are not indexed via IdeaPluginRegistrationIndex
+ if (!IdeaPluginRegistrationIndex.isRegisteredClass(psiClass, candidatesScope)) {
+ List epTargets = ExtensionLocatorKt.locateExtensionsByPsiClass(psiClass);
+ if (!epTargets.isEmpty()) {
+ result.add(LineMarkerInfoHelper.createExtensionLineMarkerInfo(epTargets, identifier));
+ }
+ return;
+ }
+
+ // Action/ActionGroup (most likely)
+ // search all candidates, e.g. EmptyAction/NonTrivialActionGroup is registered multiple times
+ if (IdeaPluginRegistrationIndex.isRegisteredActionOrGroup(psiClass, candidatesScope)) {
+ List targets = new SmartList<>();
+ IdeaPluginRegistrationIndex.processActionOrGroupClass(project, psiClass, candidatesScope, actionOrGroup -> {
+ targets.add(new ActionCandidate(actionOrGroup));
+ return true;
+ });
+ if (InheritanceUtil.isInheritor(psiClass, ActionGroup.class.getName())) {
+ result.add(LineMarkerInfoHelper.createActionGroupLineMarkerInfo(targets, identifier));
+ }
+ else {
+ result.add(LineMarkerInfoHelper.createActionLineMarkerInfo(targets, identifier));
+ }
+ return;
+ }
+
// Listeners: search for _all_ candidates as
// - some listeners are registered on both application- and project-level, e.g. com.intellij.notification.Notifications
// - some classes implement multiple listeners, e.g. com.intellij.notification.impl.widget.NotificationWidgetListener
@@ -58,13 +87,6 @@ public final class PluginDescriptorDeclarationRelatedItemLineMarkerProvider exte
});
if (!listenerTargets.isEmpty()) {
result.add(LineMarkerInfoHelper.createListenerLineMarkerInfo(listenerTargets, identifier));
- return;
- }
-
-
- List epTargets = ExtensionLocatorKt.locateExtensionsByPsiClass(psiClass);
- if (!epTargets.isEmpty()) {
- result.add(LineMarkerInfoHelper.createExtensionLineMarkerInfo(epTargets, identifier));
}
}
}
diff --git a/plugins/devkit/devkit-core/src/util/ActionCandidate.java b/plugins/devkit/devkit-core/src/util/ActionCandidate.java
new file mode 100644
index 000000000000..de3c7bf8e6f0
--- /dev/null
+++ b/plugins/devkit/devkit-core/src/util/ActionCandidate.java
@@ -0,0 +1,13 @@
+// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package org.jetbrains.idea.devkit.util;
+
+import com.intellij.psi.SmartPointerManager;
+import org.jetbrains.idea.devkit.dom.ActionOrGroup;
+
+import java.util.Objects;
+
+public class ActionCandidate extends PointableCandidate {
+ public ActionCandidate(ActionOrGroup actionOrGroup) {
+ super(SmartPointerManager.createPointer(Objects.requireNonNull(actionOrGroup.getXmlTag())));
+ }
+}
diff --git a/plugins/devkit/devkit-java-tests/testData/navigation/actionDeclaration/MyAction.java b/plugins/devkit/devkit-java-tests/testData/navigation/actionDeclaration/MyAction.java
new file mode 100644
index 000000000000..ff63af029b98
--- /dev/null
+++ b/plugins/devkit/devkit-java-tests/testData/navigation/actionDeclaration/MyAction.java
@@ -0,0 +1 @@
+public class MyAction extends com.intellij.openapi.actionSystem.AnAction {}
\ No newline at end of file
diff --git a/plugins/devkit/devkit-java-tests/testData/navigation/actionDeclaration/MyActionGroup.java b/plugins/devkit/devkit-java-tests/testData/navigation/actionDeclaration/MyActionGroup.java
new file mode 100644
index 000000000000..8f4d91e47530
--- /dev/null
+++ b/plugins/devkit/devkit-java-tests/testData/navigation/actionDeclaration/MyActionGroup.java
@@ -0,0 +1 @@
+public class MyActionGroup extends com.intellij.openapi.actionSystem.ActionGroup {}
\ No newline at end of file
diff --git a/plugins/devkit/devkit-java-tests/testData/navigation/actionDeclaration/pluginActionGroupMultipleDeclarations.xml b/plugins/devkit/devkit-java-tests/testData/navigation/actionDeclaration/pluginActionGroupMultipleDeclarations.xml
new file mode 100644
index 000000000000..1b03e090ec4b
--- /dev/null
+++ b/plugins/devkit/devkit-java-tests/testData/navigation/actionDeclaration/pluginActionGroupMultipleDeclarations.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/devkit/devkit-java-tests/testData/navigation/actionDeclaration/pluginActionMultipleDeclarations.xml b/plugins/devkit/devkit-java-tests/testData/navigation/actionDeclaration/pluginActionMultipleDeclarations.xml
new file mode 100644
index 000000000000..ecd76302977f
--- /dev/null
+++ b/plugins/devkit/devkit-java-tests/testData/navigation/actionDeclaration/pluginActionMultipleDeclarations.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/devkit/devkit-java-tests/testData/navigation/actionDeclaration/pluginActionSingleDeclaration.xml b/plugins/devkit/devkit-java-tests/testData/navigation/actionDeclaration/pluginActionSingleDeclaration.xml
new file mode 100644
index 000000000000..c5045350ccb2
--- /dev/null
+++ b/plugins/devkit/devkit-java-tests/testData/navigation/actionDeclaration/pluginActionSingleDeclaration.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/navigation/ActionDeclarationRelatedItemLineMarkerProviderTest.java b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/navigation/ActionDeclarationRelatedItemLineMarkerProviderTest.java
new file mode 100644
index 000000000000..50cd0bd0fb75
--- /dev/null
+++ b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/navigation/ActionDeclarationRelatedItemLineMarkerProviderTest.java
@@ -0,0 +1,91 @@
+// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package org.jetbrains.idea.devkit.navigation;
+
+import com.intellij.codeInsight.daemon.GutterMark;
+import com.intellij.codeInsight.navigation.DomGotoRelatedItem;
+import com.intellij.codeInsight.navigation.NavigationUtil;
+import com.intellij.icons.AllIcons;
+import com.intellij.navigation.GotoRelatedItem;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.testFramework.TestDataPath;
+import com.intellij.testFramework.builders.JavaModuleFixtureBuilder;
+import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase;
+import com.intellij.ui.ColorUtil;
+import com.intellij.ui.components.JBList;
+import com.intellij.util.PathUtil;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.idea.devkit.DevKitIcons;
+import org.jetbrains.idea.devkit.DevkitJavaTestsUtil;
+
+import java.util.List;
+
+@TestDataPath("$CONTENT_ROOT/testData/navigation/actionDeclaration")
+public class ActionDeclarationRelatedItemLineMarkerProviderTest extends JavaCodeInsightFixtureTestCase {
+
+ @Override
+ protected String getBasePath() {
+ return DevkitJavaTestsUtil.TESTDATA_PATH + "navigation/actionDeclaration";
+ }
+
+ @Override
+ protected void tuneFixture(JavaModuleFixtureBuilder moduleBuilder) {
+ String platformEditorJar = PathUtil.getJarPathForClass(AnAction.class);
+ moduleBuilder.addLibrary("platform-editor", platformEditorJar);
+ String platformIdeJar = PathUtil.getJarPathForClass(JBList.class);
+ moduleBuilder.addLibrary("platform-ide", platformIdeJar);
+ String platformUtilJar = PathUtil.getJarPathForClass(AllIcons.class);
+ moduleBuilder.addLibrary("platform-util", platformUtilJar);
+ }
+
+ public void testActionSingleDeclaration() {
+ VirtualFile pluginXmlFile = myFixture.copyFileToProject("pluginActionSingleDeclaration.xml");
+
+ GutterMark gutter = myFixture.findGutter("MyAction.java");
+ DevKitGutterTargetsChecker.checkGutterTargets(gutter,
+ "" +
+ buildTooltipText(pluginXmlFile, 30, "singleDeclaration", "action") +
+ "",
+ DevKitIcons.Gutter.Plugin, "action");
+ }
+
+ public void testActionMultipleDeclarations() {
+ VirtualFile pluginXmlFile = myFixture.copyFileToProject("pluginActionMultipleDeclarations.xml");
+
+ GutterMark gutter = myFixture.findGutter("MyAction.java");
+ DevKitGutterTargetsChecker.checkGutterTargets(gutter,
+ "" +
+ buildTooltipText(pluginXmlFile, 140, "secondDeclaration", "action") +
+ buildTooltipText(pluginXmlFile, 30, "firstDeclaration", "action") +
+ "",
+ DevKitIcons.Gutter.Plugin, "action");
+
+ List relatedItems = NavigationUtil.collectRelatedItems(myFixture.findClass("MyAction"), null);
+ assertSize(2, relatedItems);
+ DomGotoRelatedItem first = assertInstanceOf(relatedItems.get(0), DomGotoRelatedItem.class);
+ assertEquals("DevKit", first.getGroup());
+ assertEquals("firstDeclaration", first.getCustomName());
+ assertEquals(AllIcons.Actions.AddFile, first.getCustomIcon());
+ }
+
+ public void testActionGroupMultipleDeclarations() {
+ VirtualFile pluginXmlFile = myFixture.copyFileToProject("pluginActionGroupMultipleDeclarations.xml");
+
+ GutterMark gutter = myFixture.findGutter("MyActionGroup.java");
+ DevKitGutterTargetsChecker.checkGutterTargets(gutter,
+ "" +
+ buildTooltipText(pluginXmlFile, 112, "secondDeclaration", "action group") +
+ buildTooltipText(pluginXmlFile, 30, "firstDeclaration", "action group") +
+ "",
+ DevKitIcons.Gutter.Plugin, "group");
+ }
+
+ private String buildTooltipText(VirtualFile pluginXmlFile, int expectedTagPosition, String actionId, String actionType) {
+ String pluginXmlPath = pluginXmlFile.getPath();
+
+ String color = ColorUtil.toHex(UIUtil.getInactiveTextColor());
+ return " " + actionId + " " + actionType + " in " + pluginXmlFile.getName() +
+ " [" + getModule().getName() + "]
";
+ }
+}