IDEA-115118 Language/filetype filtering in ID completion variants for 'order' attribute

This commit is contained in:
Yaroslav Pankratyev
2017-11-13 16:39:43 +07:00
parent 1fb51d742f
commit d06bca249c
4 changed files with 186 additions and 30 deletions
@@ -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 { }")