diff --git a/plugins/devkit/devkit-core/src/references/MessageBundleReferenceContributor.java b/plugins/devkit/devkit-core/src/references/MessageBundleReferenceContributor.java index 22414e64e6c0..a133caea632c 100644 --- a/plugins/devkit/devkit-core/src/references/MessageBundleReferenceContributor.java +++ b/plugins/devkit/devkit-core/src/references/MessageBundleReferenceContributor.java @@ -28,18 +28,18 @@ import com.intellij.util.*; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.JBIterable; import com.intellij.util.xml.DomTarget; +import com.intellij.util.xml.ElementPresentationManager; import com.intellij.util.xml.GenericAttributeValue; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.devkit.DevKitBundle; -import org.jetbrains.idea.devkit.dom.Action; -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.*; import org.jetbrains.idea.devkit.dom.index.IdeaPluginRegistrationIndex; +import org.jetbrains.idea.devkit.util.DescriptorUtil; import org.jetbrains.idea.devkit.util.PsiUtil; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -58,6 +58,8 @@ public class MessageBundleReferenceContributor extends PsiReferenceContributor { @NonNls private static final String EXPORTABLE_PREFIX = "exportable."; @NonNls private static final String EXPORTABLE_SUFFIX = ".presentable.name"; + @NonNls private static final String PLUGIN = "plugin."; + @Override public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) { registrar.registerReferenceProvider( @@ -74,7 +76,8 @@ public class MessageBundleReferenceContributor extends PsiReferenceContributor { return JBIterable.of( createActionOrGroupIdReference(element, text), createToolwindowIdReference(element, text), - createExportableIdReference(element, text) + createExportableIdReference(element, text), + createPluginIdReference(element, text) ).filter(Objects::nonNull).toArray(PsiReference.EMPTY_ARRAY); } @@ -104,6 +107,14 @@ public class MessageBundleReferenceContributor extends PsiReferenceContributor { String id = text.replace(EXPORTABLE_PREFIX, "").replace(EXPORTABLE_SUFFIX, ""); return new ExportableIdReference(element, id); } + + @Nullable + private PsiReference createPluginIdReference(@NotNull PsiElement element, String text) { + if (!isPluginDescriptionKey(text)) return null; + + String id = StringUtil.substringAfter(StringUtil.substringBefore(text, DESCRIPTION), PLUGIN); + return new PluginIdReference(element, id); + } }); } @@ -128,6 +139,48 @@ public class MessageBundleReferenceContributor extends PsiReferenceContributor { return name.startsWith(TOOLWINDOW_STRIPE_PREFIX); } + private static boolean isPluginDescriptionKey(String name) { + return name.startsWith(PLUGIN) && name.endsWith(DESCRIPTION); + } + + + private static class PluginIdReference extends PsiPolyVariantReferenceBase { + + private PluginIdReference(@NotNull PsiElement element, String id) { + super(element, TextRange.allOf(id).shiftRight(PLUGIN.length())); + } + + @Override + public ResolveResult @NotNull [] multiResolve(boolean incompleteCode) { + List psiElements = new SmartList<>(); + final String id = getValue(); + + final Project project = getElement().getProject(); + for (IdeaPlugin plugin : getRelevantPlugins()) { + if (id.equals(plugin.getPluginId())) { + final DomTarget target = DomTarget.getTarget(plugin); + assert target != null; + psiElements.add(PomService.convertToPsi(project, target)); + } + } + return PsiElementResolveResult.createResults(psiElements); + } + + @Override + public Object @NotNull [] getVariants() { + return ContainerUtil.map2Array(getRelevantPlugins(), LookupElement.class, + plugin -> LookupElementBuilder.create(Objects.requireNonNull(plugin.getPluginId())) + .withPsiElement(plugin.getXmlElement()) + .withTailText(" " + StringUtil.notNullize(plugin.getName().getValue())) + .withIcon(ElementPresentationManager.getIcon(plugin))); + } + + private Collection getRelevantPlugins() { + return ContainerUtil.filter(DescriptorUtil.getPlugins(getElement().getProject(), getElement().getResolveScope()), + plugin -> plugin.hasRealPluginId() && Boolean.TRUE != plugin.getImplementationDetail().getValue()); + } + } + private static final class ActionOrGroupIdReference extends PsiPolyVariantReferenceBase { @@ -310,7 +363,8 @@ public class MessageBundleReferenceContributor extends PsiReferenceContributor { if (isActionOrGroupKey(name) || isExportableKey(name) || - isToolwindowKey(name)) { + isToolwindowKey(name) || + isPluginDescriptionKey(name)) { PsiElement key = property.getFirstChild(); PsiReference[] references = key == null ? PsiReference.EMPTY_ARRAY : key.getReferences(); diff --git a/plugins/devkit/devkit-java-tests/testData/references/messageBundle/PluginDescriptionKeyImplicitUsage.xml b/plugins/devkit/devkit-java-tests/testData/references/messageBundle/PluginDescriptionKeyImplicitUsage.xml new file mode 100644 index 000000000000..c23276ed0cbd --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/references/messageBundle/PluginDescriptionKeyImplicitUsage.xml @@ -0,0 +1,3 @@ + + my.plugin.id + \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/references/messageBundle/PluginDescriptionKeyImplicitUsageBundle.properties b/plugins/devkit/devkit-java-tests/testData/references/messageBundle/PluginDescriptionKeyImplicitUsageBundle.properties new file mode 100644 index 000000000000..8048216597eb --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/references/messageBundle/PluginDescriptionKeyImplicitUsageBundle.properties @@ -0,0 +1,5 @@ +plugin.my.plugin.id.description=Plugin Description Text + +plugin.INVALID_PLUGIN_ID.description=No such plugin ID + +plugin.implementation.id.description=Valid ID but implementation-detail is true \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/references/messageBundle/PluginDescriptionKeyImplicitUsageImplementationDetail.xml b/plugins/devkit/devkit-java-tests/testData/references/messageBundle/PluginDescriptionKeyImplicitUsageImplementationDetail.xml new file mode 100644 index 000000000000..673d16584d02 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/references/messageBundle/PluginDescriptionKeyImplicitUsageImplementationDetail.xml @@ -0,0 +1,3 @@ + + implementation.id + \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/references/MessageBundleReferenceTest.java b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/references/MessageBundleReferenceTest.java index c67c93a257fb..cb9d5de14717 100644 --- a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/references/MessageBundleReferenceTest.java +++ b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/references/MessageBundleReferenceTest.java @@ -31,6 +31,10 @@ public class MessageBundleReferenceTest extends JavaCodeInsightFixtureTestCase { doHighlightImplicitUsagesTest(); } + public void testPluginDescriptionKeyImplicitUsage() { + doHighlightImplicitUsagesTest("PluginDescriptionKeyImplicitUsage.xml"); + } + public void testActionOrGroupImplicitUsage() { doHighlightImplicitUsagesTest("ActionOrGroupImplicitUsage.xml"); } @@ -70,4 +74,12 @@ public class MessageBundleReferenceTest extends JavaCodeInsightFixtureTestCase { myFixture.completeBasic(); assertContainsElements(myFixture.getLookupElementStrings(), "MyStateName"); } + + public void testPluginIdCompletionVariants() { + myFixture.copyFileToProject("PluginDescriptionKeyImplicitUsage.xml"); + myFixture.configureByText("MyBundle.properties", "plugin..description"); + + myFixture.completeBasic(); + assertContainsElements(myFixture.getLookupElementStrings(), "my.plugin.id"); + } } \ No newline at end of file