mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-10-01 09:03:31 +07:00
IDEA-115118 Language/filetype filtering in ID completion variants for 'order' attribute
This commit is contained in:
@@ -6,18 +6,24 @@ import com.intellij.codeInsight.daemon.EmptyResolveMessageProvider;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.codeInsight.lookup.LookupElementBuilder;
|
||||
import com.intellij.icons.AllIcons;
|
||||
import com.intellij.lang.LanguageExtensionPoint;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.extensions.LoadingOrder;
|
||||
import com.intellij.openapi.fileTypes.FileTypeExtensionPoint;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.pom.references.PomService;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.util.ReferenceSetBase;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.HashMap;
|
||||
import com.intellij.util.xml.*;
|
||||
import com.intellij.util.xml.reflect.DomAttributeChildDescription;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.idea.devkit.DevKitBundle;
|
||||
@@ -27,7 +33,7 @@ import org.jetbrains.idea.devkit.util.ExtensionLocator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Map;
|
||||
|
||||
public class ExtensionOrderConverter implements CustomReferenceConverter<String> {
|
||||
private static final Logger LOG = Logger.getInstance(ExtensionOrderConverter.class);
|
||||
@@ -206,7 +212,8 @@ public class ExtensionOrderConverter implements CustomReferenceConverter<String>
|
||||
|
||||
ExtensionLocator epLocator = ExtensionLocator.byExtensionPoint(extensionPoint);
|
||||
List<ExtensionCandidate> candidates = epLocator.findCandidates();
|
||||
DomManager domManager = DomManager.getDomManager(getElement().getProject());
|
||||
Project project = getElement().getProject();
|
||||
DomManager domManager = DomManager.getDomManager(project);
|
||||
|
||||
List<Extension> extensionsForThisEp = new ArrayList<>();
|
||||
for (ExtensionCandidate candidate : candidates) {
|
||||
@@ -217,34 +224,9 @@ public class ExtensionOrderConverter implements CustomReferenceConverter<String>
|
||||
}
|
||||
}
|
||||
|
||||
String currentExtensionId = myExtension.getId().getStringValue();
|
||||
List<LookupElement> idCompletionVariantsList = new ArrayList<>();
|
||||
for (Extension e : extensionsForThisEp) {
|
||||
if (Objects.equals(currentExtensionId, e.getId().getStringValue())) {
|
||||
continue; // do not suggest the same extension id
|
||||
}
|
||||
String id = e.getId().getStringValue();
|
||||
if (StringUtil.isNotEmpty(id)) {
|
||||
DomTarget extensionTarget = DomTarget.getTarget(e);
|
||||
PsiElement targetElement;
|
||||
if (extensionTarget != null) {
|
||||
targetElement = PomService.convertToPsi(extensionTarget);
|
||||
}
|
||||
else {
|
||||
// shouldn't happen, fallback for additional safety
|
||||
targetElement = e.getXmlTag();
|
||||
}
|
||||
|
||||
LookupElementBuilder element = LookupElementBuilder.create(targetElement, id);
|
||||
Module module = e.getModule();
|
||||
if (module != null) {
|
||||
element = element.withTypeText(module.getName(), AllIcons.Actions.Module, false);
|
||||
}
|
||||
|
||||
idCompletionVariantsList.add(element);
|
||||
}
|
||||
}
|
||||
return idCompletionVariantsList.toArray(new LookupElement[idCompletionVariantsList.size()]);
|
||||
Map<Extension, String> targetExtensionsWithMarks = filterAndMarkExtensions(extensionsForThisEp, project);
|
||||
List<LookupElement> idCompletionVariants = getLookupElements(targetExtensionsWithMarks);
|
||||
return idCompletionVariants.toArray(new LookupElement[idCompletionVariants.size()]);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -258,5 +240,125 @@ public class ExtensionOrderConverter implements CustomReferenceConverter<String>
|
||||
public boolean isSoft() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private Map<Extension, String> filterAndMarkExtensions(List<Extension> extensionsForThisEp, Project project) {
|
||||
JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(project);
|
||||
GlobalSearchScope resolveScope = getElement().getResolveScope();
|
||||
PsiClass languageEpClass = javaPsiFacade.findClass(LanguageExtensionPoint.class.getCanonicalName(), resolveScope);
|
||||
if (languageEpClass == null) {
|
||||
LOG.error("Cannot find LanguageExtensionPoint class; extension: " + myExtension);
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
PsiClass fileTypeEpClass = javaPsiFacade.findClass(FileTypeExtensionPoint.class.getCanonicalName(), resolveScope);
|
||||
if (fileTypeEpClass == null) {
|
||||
LOG.error("Cannot find FileTypeExtensionPoint class; extension: " + myExtension);
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
String currentExtensionId = myExtension.getId().getStringValue();
|
||||
String currentExtensionLanguage = getSpecificExtensionAttribute(myExtension, languageEpClass, "language");
|
||||
String currentExtensionFileType = getSpecificExtensionAttribute(myExtension, fileTypeEpClass, "filetype");
|
||||
|
||||
Map<Extension, String> result = new HashMap<>();
|
||||
for (Extension extension : extensionsForThisEp) {
|
||||
String id = extension.getId().getStringValue();
|
||||
if (StringUtil.isEmpty(id) || id.equals(currentExtensionId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String extensionMark = null; // to display {language} or {file type}
|
||||
if (currentExtensionLanguage != null) {
|
||||
String language = getSpecificExtensionAttribute(extension, languageEpClass, "language");
|
||||
if (language != null) {
|
||||
if (!language.equalsIgnoreCase("ANY") && !language.equalsIgnoreCase(currentExtensionLanguage)) {
|
||||
continue;
|
||||
}
|
||||
extensionMark = language;
|
||||
}
|
||||
}
|
||||
if (currentExtensionFileType != null) {
|
||||
String fileType = getSpecificExtensionAttribute(extension, fileTypeEpClass, "filetype");
|
||||
if (fileType != null) {
|
||||
if (!currentExtensionFileType.equalsIgnoreCase(fileType)) {
|
||||
continue;
|
||||
}
|
||||
extensionMark = extensionMark != null ? null : fileType; // null if _somehow_ both filetype and language are present
|
||||
}
|
||||
}
|
||||
|
||||
result.put(extension, extensionMark);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<LookupElement> getLookupElements(@NotNull Map<Extension, String> targetExtensionsWithMarks) {
|
||||
List<LookupElement> result = new ArrayList<>(targetExtensionsWithMarks.size());
|
||||
for (Map.Entry<Extension, String> entry : targetExtensionsWithMarks.entrySet()) {
|
||||
Extension extension = entry.getKey();
|
||||
String mark = entry.getValue();
|
||||
|
||||
PsiElement targetElement = getTargetElement(extension);
|
||||
String id = extension.getId().getStringValue();
|
||||
if (StringUtil.isEmpty(id)) {
|
||||
LOG.error("Unexpected empty id in target extension: " + extension);
|
||||
continue;
|
||||
}
|
||||
|
||||
result.add(createLookupElement(targetElement, id, extension.getModule(), mark));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getSpecificExtensionAttribute(@NotNull Extension e,
|
||||
@NotNull PsiClass parentBeanClass,
|
||||
@NotNull String attribute) {
|
||||
ExtensionPoint ep = e.getExtensionPoint();
|
||||
if (ep == null) {
|
||||
return null;
|
||||
}
|
||||
PsiClass beanClass = ep.getBeanClass().getValue();
|
||||
if (beanClass == null) {
|
||||
return null;
|
||||
}
|
||||
if (!beanClass.equals(parentBeanClass) && !beanClass.isInheritor(parentBeanClass, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
DomAttributeChildDescription attributeDescription = e.getGenericInfo().getAttributeChildDescription(attribute);
|
||||
if (attributeDescription == null) {
|
||||
return null;
|
||||
}
|
||||
return attributeDescription.getDomAttributeValue(e).getStringValue();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PsiElement getTargetElement(Extension e) {
|
||||
DomTarget extensionTarget = DomTarget.getTarget(e);
|
||||
if (extensionTarget != null) {
|
||||
return PomService.convertToPsi(extensionTarget);
|
||||
}
|
||||
else {
|
||||
// shouldn't happen, fallback for additional safety
|
||||
return e.getXmlTag();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static LookupElement createLookupElement(@NotNull PsiElement targetElement,
|
||||
@NotNull String id,
|
||||
@Nullable Module module,
|
||||
@Nullable String mark) {
|
||||
LookupElementBuilder element = LookupElementBuilder.create(targetElement, id);
|
||||
if (module != null) {
|
||||
element = element.withTypeText(module.getName(), AllIcons.Actions.Module, false);
|
||||
}
|
||||
if (mark != null) {
|
||||
element = element.withTailText(" {" + mark + "}", true);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<idea-plugin>
|
||||
<id>com.intellij</id>
|
||||
|
||||
<extensionPoints>
|
||||
<!--suppress PluginXmlValidity -->
|
||||
<extensionPoint name="myEp8"
|
||||
beanClass="com.intellij.openapi.fileTypes.FileTypeExtensionPoint"/>
|
||||
</extensionPoints>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<myEp8 id="id1" filetype="JAVA"/>
|
||||
<!--suppress PluginXmlValidity -->
|
||||
<myEp8 id="id2"/>
|
||||
<myEp8 id="id3" filetype="XML"/>
|
||||
<myEp8 id="id4" filetype="JAVA" order="before <caret>"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
@@ -0,0 +1,18 @@
|
||||
<idea-plugin>
|
||||
<id>com.intellij</id>
|
||||
|
||||
<extensionPoints>
|
||||
<!--suppress PluginXmlValidity -->
|
||||
<extensionPoint name="myEp7"
|
||||
beanClass="com.intellij.codeInsight.completion.CompletionContributorEP"/>
|
||||
</extensionPoints>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<myEp7 id="id1" language="JAVA"/>
|
||||
<!--suppress PluginXmlValidity -->
|
||||
<myEp7 id="id2" language="ANY"/>
|
||||
<myEp7 id="id3"/>
|
||||
<myEp7 id="id4" language="XML"/>
|
||||
<myEp7 id="id5" language="JAVA" order="before <caret>"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
@@ -16,11 +16,13 @@
|
||||
package org.jetbrains.idea.devkit.codeInsight
|
||||
|
||||
import com.intellij.codeInsight.TargetElementUtil
|
||||
import com.intellij.codeInsight.completion.CompletionContributorEP
|
||||
import com.intellij.codeInsight.completion.CompletionType
|
||||
import com.intellij.codeInsight.lookup.LookupElement
|
||||
import com.intellij.codeInsight.lookup.LookupElementPresentation
|
||||
import com.intellij.codeInspection.xml.DeprecatedClassUsageInspection
|
||||
import com.intellij.diagnostic.ITNReporter
|
||||
import com.intellij.lang.LanguageExtensionPoint
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.PluginPathManager
|
||||
import com.intellij.openapi.extensions.LoadingOrder
|
||||
@@ -66,6 +68,10 @@ class PluginXmlFunctionalTest extends JavaCodeInsightFixtureTestCase {
|
||||
moduleBuilder.addLibrary("platform-api", platformApiJar)
|
||||
String platformImplJar = PathUtil.getJarPathForClass(ITNReporter.class)
|
||||
moduleBuilder.addLibrary("platform-impl", platformImplJar)
|
||||
String langApiJar = PathUtil.getJarPathForClass(CompletionContributorEP.class)
|
||||
moduleBuilder.addLibrary("lang-api", langApiJar)
|
||||
String coreApiJar = PathUtil.getJarPathForClass(LanguageExtensionPoint.class) // FileTypeExtensionPoint is also there
|
||||
moduleBuilder.addLibrary("core-api", coreApiJar)
|
||||
}
|
||||
|
||||
void testExtensionsHighlighting() {
|
||||
@@ -208,6 +214,7 @@ class PluginXmlFunctionalTest extends JavaCodeInsightFixtureTestCase {
|
||||
myFixture.checkResultByFile(getTestName(false) + "_after.xml")
|
||||
}
|
||||
|
||||
@SuppressWarnings("ComponentNotRegistered")
|
||||
void testShowPackagesInActionClass() {
|
||||
myFixture.addClass("package com.intellij.openapi.actionSystem; public class AnAction { }")
|
||||
myFixture.addClass("package foo.bar; public class BarAction extends com.intellij.openapi.actionSystem.AnAction { }")
|
||||
@@ -218,6 +225,7 @@ class PluginXmlFunctionalTest extends JavaCodeInsightFixtureTestCase {
|
||||
assert myFixture.lookup.advertisements.find { it.contains('to see inheritors of com.intellij.openapi.actionSystem.AnAction') }
|
||||
}
|
||||
|
||||
@SuppressWarnings("ComponentNotRegistered")
|
||||
void testShowAnActionInheritorsOnSmartCompletion() {
|
||||
myFixture.addClass("package com.intellij.openapi.actionSystem; public class AnAction { }")
|
||||
myFixture.addClass("package foo.bar; public class BarAction extends com.intellij.openapi.actionSystem.AnAction { }")
|
||||
@@ -281,6 +289,7 @@ class PluginXmlFunctionalTest extends JavaCodeInsightFixtureTestCase {
|
||||
myFixture.allowTreeAccessForFile(myFixture.copyFileToProject("MyLanguage.java"))
|
||||
}
|
||||
|
||||
@SuppressWarnings("ComponentNotRegistered")
|
||||
void testIconAttribute() {
|
||||
myFixture.addClass("package com.intellij.openapi.actionSystem; public class AnAction { }")
|
||||
myFixture.addClass("package foo; public class FooAction extends com.intellij.openapi.actionSystem.AnAction { }")
|
||||
@@ -396,6 +405,15 @@ class PluginXmlFunctionalTest extends JavaCodeInsightFixtureTestCase {
|
||||
myFixture.testCompletion(getTestName(true) + ".xml", getTestName(true) + "_after.xml")
|
||||
}
|
||||
|
||||
void testOrderAttributeCompletionLanguage() {
|
||||
myFixture.testCompletionVariants(getTestName(true) + ".xml", "id1", "id2", "id3")
|
||||
}
|
||||
|
||||
void testOrderAttributeCompletionFileType() {
|
||||
myFixture.testCompletionVariants(getTestName(true) + ".xml", "id1", "id2")
|
||||
}
|
||||
|
||||
|
||||
private void testHighlightingInIdeaProject(String path) {
|
||||
myFixture.enableInspections(PluginXmlDomInspection.class)
|
||||
PsiUtil.markAsIdeaProject(project, true)
|
||||
@@ -440,6 +458,7 @@ public class MyErrorHandler extends ErrorReportSubmitter {}
|
||||
myFixture.checkResultByFile(getTestName(true) + "_after.xml")
|
||||
}
|
||||
|
||||
@SuppressWarnings("ComponentNotRegistered")
|
||||
void testActionHighlighting() {
|
||||
configureByFile()
|
||||
myFixture.addClass("package com.intellij.openapi.actionSystem; public class AnAction { }")
|
||||
|
||||
Reference in New Issue
Block a user