IDEA-269636 Devkit: improve <override-text> 'place' code insight

GitOrigin-RevId: 376870875d7bfc74076fe69249b9c8f96dc47514
This commit is contained in:
Yann Cébron
2021-05-19 16:07:16 +00:00
committed by intellij-monorepo-bot
parent 0790ff26fc
commit 801e239dbf
4 changed files with 126 additions and 49 deletions
@@ -1,59 +1,105 @@
// Copyright 2000-2021 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.
package org.jetbrains.idea.devkit.dom;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.codeInspection.dataFlow.StringExpressionHelper;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.AllClassesSearch;
import com.intellij.util.CommonProcessors;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.xml.*;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
public interface OverrideText extends DomElement {
@NotNull
@Required
@Convert(PlaceConverter.class)
GenericAttributeValue<String> getPlace();
@Convert(value = PlaceConverter.class, soft = true)
GenericAttributeValue<PsiField> getPlace();
@NotNull
@Required(false)
@Convert(PlaceConverter.class)
GenericAttributeValue<String> getUseTextOfPlace();
@Convert(value = PlaceConverter.class, soft = true)
GenericAttributeValue<PsiField> getUseTextOfPlace();
@NotNull
@Required(false)
GenericAttributeValue<String> getText();
class PlaceConverter extends ResolvingConverter.StringConverter {
class PlaceConverter extends ResolvingConverter<PsiField> {
@NonNls private static final String PLACES_CLASSNAME_SUFFIX = "Places";
@Override
public @Nullable PsiField fromString(@Nullable String s,
ConvertContext context) {
if (s == null) return null;
CommonProcessors.FindProcessor<PsiField> resolve = new CommonProcessors.FindProcessor<>() {
@Override
protected boolean accept(PsiField field) {
return s.equals(getPlaceName(field));
}
};
processAllActionPlaces(context, resolve);
return resolve.getFoundValue();
}
@Override
public @Nullable String toString(@Nullable PsiField field,
ConvertContext context) {
return field != null ? field.getName() : null;
}
@Override
public @Nullable
LookupElement createLookupElement(PsiField field) {
return LookupElementBuilder.create(field, Objects.requireNonNull(getPlaceName(field)))
.withStrikeoutness(field.isDeprecated())
.withTailText(" (" + field.getName() + ")")
.withTypeText(Objects.requireNonNull(field.getContainingClass()).getName());
}
@NotNull
@Override
public Collection<String> getVariants(ConvertContext context) {
return ContainerUtil.concat(getPlaceNames(context, "com.intellij.openapi.actionSystem.ActionPlaces"),
getPlaceNames(context, "com.intellij.vcs.log.ui.VcsLogActionPlaces"));
public Collection<PsiField> getVariants(ConvertContext context) {
CommonProcessors.CollectProcessor<PsiField> collectProcessor = new CommonProcessors.CollectProcessor<>();
processAllActionPlaces(context, collectProcessor);
return collectProcessor.getResults();
}
@NotNull
private static List<String> getPlaceNames(ConvertContext context, String className) {
final PsiClass actionPlacesClass = DomJavaUtil.findClass(className, context.getInvocationElement());
if (actionPlacesClass == null) return Collections.emptyList();
private static void processAllActionPlaces(ConvertContext context, Processor<PsiField> fieldProcessor) {
final GlobalSearchScope scope = context.getSearchScope();
if (scope == null) return;
return ContainerUtil.mapNotNull(actionPlacesClass.getFields(), field -> {
if (!field.hasModifierProperty(PsiModifier.PUBLIC)) return null;
AllClassesSearch.search(context.getSearchScope(), context.getProject(), s -> s.endsWith(PLACES_CLASSNAME_SUFFIX))
.forEach(psiClass -> {
return ContainerUtil.process(psiClass.getFields(), psiField -> {
if (!psiField.hasModifierProperty(PsiModifier.PUBLIC)) return true;
if (getPlaceName(psiField) == null) return true;
final PsiExpression initializer = field.getInitializer();
if (initializer != null) {
return Pair.getSecond(StringExpressionHelper.evaluateExpression(initializer));
}
return null;
});
return fieldProcessor.process(psiField);
});
});
}
@Nullable
private static String getPlaceName(PsiField field) {
final PsiExpression initializer = field.getInitializer();
return initializer != null ? Pair.getSecond(StringExpressionHelper.evaluateExpression(initializer)) : null;
}
}
}
@@ -0,0 +1,7 @@
<idea-plugin>
<actions>
<action>
<override-text place="<caret>"/>
</action>
</actions>
</idea-plugin>
@@ -0,0 +1,7 @@
<idea-plugin>
<actions>
<action>
<override-text place="Favorites<caret>Popup"/>
</action>
</actions>
</idea-plugin>
@@ -1,5 +1,5 @@
/*
* 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-2021 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.
*/
package org.jetbrains.idea.devkit.codeInsight
@@ -26,6 +26,8 @@ import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.psi.ElementDescriptionUtil
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiField
import com.intellij.psi.PsiReference
import com.intellij.testFramework.PsiTestUtil
import com.intellij.testFramework.TestDataPath
import com.intellij.testFramework.builders.JavaModuleFixtureBuilder
@@ -406,16 +408,9 @@ class PluginXmlFunctionalTest extends JavaCodeInsightFixtureTestCase {
myFixture.configureByFile("languageAttribute.xml")
def lookupElements = myFixture.complete(CompletionType.BASIC).sort { it.lookupString }
assertLookupElement(lookupElements[0], "MyAnonymousLanguageID", "MyLanguage.MySubLanguage")
assertLookupElement(lookupElements[1], "MyLanguageID", "MyLanguage")
}
private static void assertLookupElement(LookupElement element, String lookupString, String typeText) {
def presentation = new LookupElementPresentation()
element.renderElement(presentation)
assertEquals(lookupString, presentation.itemText)
assertEquals(typeText, presentation.typeText)
def lookupElements = myFixture.complete(CompletionType.BASIC)
assertLookupElement(lookupElements, "MyAnonymousLanguageID", null, "MyLanguage.MySubLanguage")
assertLookupElement(lookupElements, "MyLanguageID", null, "MyLanguage")
}
@SuppressWarnings("ComponentNotRegistered")
@@ -431,7 +426,7 @@ class PluginXmlFunctionalTest extends JavaCodeInsightFixtureTestCase {
void testIconAttributeCompletion() {
addIconClasses()
myFixture.configureByFile("iconAttributeCompletion.xml")
Registry.get("ide.completion.variant.limit").setValue("5000", getTestRootDisposable());
Registry.get("ide.completion.variant.limit").setValue("5000", getTestRootDisposable())
myFixture.completeBasic()
List<String> lookupElementStrings = myFixture.getLookupElementStrings()
@@ -664,17 +659,27 @@ public class MyErrorHandler extends ErrorReportSubmitter {}
myFixture.completeBasic()
LookupElement[] lookupElements = myFixture.getLookupElements()
assertActionLookupElement(lookupElements[0], "actionId", " \"ActionId Text\"", "ActionId description")
assertActionLookupElement(lookupElements[1], "actionId.localized", " \"Action Localized Text\"", "Action localized description")
assertActionLookupElement(lookupElements[2], "actionId.missing.localized", null, null)
assertLookupElement(lookupElements, "actionId", " \"ActionId Text\"", "ActionId description")
assertLookupElement(lookupElements, "actionId.localized", " \"Action Localized Text\"", "Action localized description")
assertLookupElement(lookupElements, "actionId.missing.localized", null, null)
}
private static void assertActionLookupElement(LookupElement element, String lookupText, String tailText, String typeText) {
def presentation = new LookupElementPresentation()
element.renderElement(presentation)
assertEquals(lookupText, presentation.itemText)
assertEquals(tailText, presentation.tailText)
assertEquals(typeText, presentation.typeText)
void testActionOverrideTextPlaceCompletion() {
configureByFile()
myFixture.addClass("public interface CustomPlaces { String CUSTOM=\"custom\"; }")
myFixture.completeBasic()
LookupElement[] lookupElements = myFixture.getLookupElements()
assertLookupElement(lookupElements, "FavoritesPopup", " (FAVORITES_VIEW_POPUP)", "ActionPlaces")
assertLookupElement(lookupElements, "custom", " (CUSTOM)", "CustomPlaces")
}
void testActionOverrideTextPlaceResolve() {
configureByFile()
PsiReference reference = myFixture.getReferenceAtCaretPositionWithAssertion()
PsiField resolvedField = assertInstanceOf(reference.resolve(), PsiField.class)
assertEquals("FAVORITES_VIEW_POPUP", resolvedField.getName())
}
void testExtensionPointNameValidity() {
@@ -777,10 +782,6 @@ public class MyErrorHandler extends ErrorReportSubmitter {}
doHighlightingTest("pluginWithSinceBuildGreaterThanUntilBuild.xml")
}
private void doHighlightingTest(String... filePaths) {
myFixture.testHighlighting(true, false, false, filePaths)
}
void testProductDescriptor() {
doHighlightingTest("productDescriptor.xml")
}
@@ -813,4 +814,20 @@ public class MyErrorHandler extends ErrorReportSubmitter {}
void testRedundantServiceInterfaceClass() {
doHighlightingTest("redundantServiceInterfaceClass.xml")
}
private void doHighlightingTest(String... filePaths) {
myFixture.testHighlighting(true, false, false, filePaths)
}
private static void assertLookupElement(LookupElement[] variants, String lookupText, String tailText, String typeText) {
LookupElement lookupElement = variants.find { it.lookupString == lookupText }
assertNotNull(toString(variants, "\n"), lookupElement)
def presentation = new LookupElementPresentation()
lookupElement.renderElement(presentation)
assertEquals(lookupText, presentation.itemText)
assertEquals(tailText, presentation.tailText)
assertEquals(typeText, presentation.typeText)
}
}