mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 21:11:28 +07:00
[java-completion] IDEA-314367. Completion for similar methods
GitOrigin-RevId: 7f12554a48a88920f81b093c58af55ed5c47ffc6
This commit is contained in:
committed by
intellij-monorepo-bot
parent
8107aa1050
commit
ff51cc96fe
@@ -7,6 +7,7 @@ import com.intellij.openapi.project.DumbAware;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiTypes;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -29,6 +30,9 @@ public class JavaMethodMergingContributor extends CompletionContributor implemen
|
||||
}
|
||||
|
||||
final LookupElement[] items = context.getItems();
|
||||
if (ContainerUtil.exists(items, t -> t instanceof JavaNoVariantsDelegator.TagLookupElementDecorator)) {
|
||||
return AutoCompletionDecision.SHOW_LOOKUP;
|
||||
}
|
||||
if (items.length > 1) {
|
||||
String commonName = null;
|
||||
final ArrayList<PsiMethod> allMethods = new ArrayList<>();
|
||||
|
||||
@@ -20,11 +20,12 @@ import com.intellij.codeInsight.TailType;
|
||||
import com.intellij.codeInsight.completion.JavaCompletionUtil.JavaLookupElementHighlighter;
|
||||
import com.intellij.codeInsight.completion.impl.BetterPrefixMatcher;
|
||||
import com.intellij.codeInsight.completion.impl.CamelHumpMatcher;
|
||||
import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.codeInsight.lookup.TailTypeDecorator;
|
||||
import com.intellij.codeInsight.lookup.*;
|
||||
import com.intellij.java.JavaBundle;
|
||||
import com.intellij.openapi.project.DumbAware;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.util.text.MethodTags;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.filters.ElementFilter;
|
||||
@@ -37,6 +38,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.intellij.patterns.PsiJavaPatterns.psiElement;
|
||||
|
||||
@@ -108,7 +110,7 @@ public class JavaNoVariantsDelegator extends CompletionContributor implements Du
|
||||
suggestNonImportedClasses(parameters, result, session);
|
||||
return;
|
||||
}
|
||||
|
||||
suggestTagCalls(parameters, result, position);
|
||||
suggestChainedCalls(parameters, result, position);
|
||||
}
|
||||
|
||||
@@ -117,6 +119,46 @@ public class JavaNoVariantsDelegator extends CompletionContributor implements Du
|
||||
}
|
||||
}
|
||||
|
||||
private static void suggestTagCalls(CompletionParameters parameters, CompletionResultSet result, PsiElement position) {
|
||||
if (!Registry.is("java.completion.methods.use.tags")) {
|
||||
return;
|
||||
}
|
||||
PsiElement parent = position.getParent();
|
||||
if (!(parent instanceof PsiReferenceExpression)) {
|
||||
return;
|
||||
}
|
||||
PrefixMatcher prefixMatcher = result.getPrefixMatcher();
|
||||
TagMatcher tagMatcher = new TagMatcher(prefixMatcher);
|
||||
CompletionResultSet tagResultSet = result.withPrefixMatcher(tagMatcher);
|
||||
tagResultSet.runRemainingContributors(parameters, new Consumer<>() {
|
||||
@Override
|
||||
public void consume(CompletionResult result) {
|
||||
LookupElement element = result.getLookupElement();
|
||||
if (element != null && !prefixMatcher.prefixMatches(element) && tagMatcher.prefixMatches(element)) {
|
||||
LookupElement lookupElement = wrapLookup(element, prefixMatcher);
|
||||
if (lookupElement == null) {
|
||||
return;
|
||||
}
|
||||
CompletionResult wrapped = CompletionResult.wrap(lookupElement, result.getPrefixMatcher(),
|
||||
result.getSorter());
|
||||
if (wrapped != null) {
|
||||
tagResultSet.passResult(wrapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static LookupElement wrapLookup(@NotNull LookupElement element, @NotNull PrefixMatcher matcher) {
|
||||
String lookupString = element.getLookupString();
|
||||
Set<String> tags = MethodTags.tags(lookupString).stream().filter(t -> matcher.prefixMatches(t)).collect(Collectors.toSet());
|
||||
if (tags.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return new TagLookupElementDecorator(element, tags);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void suggestCollectionUtilities(CompletionParameters parameters, final CompletionResultSet result, PsiElement position) {
|
||||
if (StringUtil.isNotEmpty(result.getPrefixMatcher().getPrefix())) {
|
||||
for (ExpectedTypeInfo info : JavaSmartCompletionContributor.getExpectedTypes(parameters)) {
|
||||
@@ -259,4 +301,64 @@ public class JavaNoVariantsDelegator extends CompletionContributor implements Du
|
||||
betterMatcher = betterMatcher.improve(plainResult);
|
||||
}
|
||||
}
|
||||
|
||||
private static class TagMatcher extends PrefixMatcher {
|
||||
@NotNull
|
||||
private final PrefixMatcher myMatcher;
|
||||
|
||||
protected TagMatcher(@NotNull PrefixMatcher matcher) {
|
||||
super(matcher.getPrefix());
|
||||
myMatcher = matcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean prefixMatches(@NotNull String name) {
|
||||
if (myMatcher.prefixMatches(name)) {
|
||||
return true;
|
||||
}
|
||||
Set<String> tags = MethodTags.tags(name);
|
||||
for (String tag : tags) {
|
||||
if (myMatcher.prefixMatches(tag)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PrefixMatcher cloneWithPrefix(@NotNull String prefix) {
|
||||
PrefixMatcher matcher = myMatcher.cloneWithPrefix(prefix);
|
||||
return new TagMatcher(matcher);
|
||||
}
|
||||
}
|
||||
|
||||
static class TagLookupElementDecorator extends LookupElementDecorator<LookupElement> {
|
||||
|
||||
private final Set<String> myTags;
|
||||
|
||||
protected TagLookupElementDecorator(@NotNull LookupElement delegate, @NotNull Set<String> tags) {
|
||||
super(delegate);
|
||||
myTags = tags;
|
||||
}
|
||||
|
||||
public Set<String> getTags() {
|
||||
return myTags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAllLookupStrings() {
|
||||
Set<String> all = new HashSet<>(super.getAllLookupStrings());
|
||||
all.addAll(myTags);
|
||||
return all;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderElement(@NotNull LookupElementPresentation presentation) {
|
||||
super.renderElement(presentation);
|
||||
if (!myTags.isEmpty()) {
|
||||
presentation.appendTailText(" " + JavaBundle.message("java.completion.tag") + " ", true);
|
||||
presentation.appendTailText(myTags.iterator().next(), true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class C {
|
||||
void test(java.util.List<String> list) {
|
||||
list::ma<caret>
|
||||
list::fi<caret>
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import com.intellij.openapi.actionSystem.IdeActions;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.impl.NonBlockingReadActionImpl;
|
||||
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.augment.PsiAugmentProvider;
|
||||
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
|
||||
@@ -2877,4 +2878,32 @@ public class NormalCompletionTest extends NormalCompletionTestCase {
|
||||
}
|
||||
""");
|
||||
}
|
||||
@NeedsIndex.Full
|
||||
public void testTagAdd() {
|
||||
Registry.get("java.completion.methods.use.tags").setValue(true);
|
||||
myFixture.configureByText("Test.java", """
|
||||
import java.util.HashSet;
|
||||
|
||||
public abstract class SuperClass {
|
||||
|
||||
void run() {
|
||||
HashSet<Object> objects = new HashSet<>();
|
||||
objects.len<caret>;
|
||||
}
|
||||
}
|
||||
""");
|
||||
myFixture.completeBasic();
|
||||
myFixture.type('\n');
|
||||
myFixture.checkResult("""
|
||||
import java.util.HashSet;
|
||||
|
||||
public abstract class SuperClass {
|
||||
|
||||
void run() {
|
||||
HashSet<Object> objects = new HashSet<>();
|
||||
objects.size();
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1809,3 +1809,4 @@ vm.option.description.diagnostic=Diagnostic
|
||||
vm.option.description.experimental=Experimental
|
||||
hint.text.not.valid.java.identifier=Not a valid Java identifier
|
||||
command.name.replace.type=Replace Type
|
||||
java.completion.tag=Tag:
|
||||
|
||||
@@ -64,11 +64,15 @@ public class LookupElementPresentation {
|
||||
}
|
||||
|
||||
public void appendTailText(@NotNull String text, boolean grayed) {
|
||||
appendTailText(new TextFragment(text, grayed, false, null));
|
||||
appendTailText(new TextFragment(text, grayed, false, false, null));
|
||||
}
|
||||
|
||||
public void appendTailText(@NotNull String text, boolean grayed, boolean highlight) {
|
||||
appendTailText(new TextFragment(text, grayed, false, highlight, null));
|
||||
}
|
||||
|
||||
public void appendTailTextItalic(@NotNull String text, boolean grayed) {
|
||||
appendTailText(new TextFragment(text, grayed, true, null));
|
||||
appendTailText(new TextFragment(text, grayed, true, false, null));
|
||||
}
|
||||
|
||||
private void appendTailText(@NotNull TextFragment fragment) {
|
||||
@@ -84,14 +88,14 @@ public class LookupElementPresentation {
|
||||
public void setTailText(@Nullable String text, boolean grayed) {
|
||||
clearTail();
|
||||
if (text != null) {
|
||||
appendTailText(new TextFragment(text, grayed, false, null));
|
||||
appendTailText(new TextFragment(text, grayed, false, false, null));
|
||||
}
|
||||
}
|
||||
|
||||
public void setTailText(@Nullable String text, @Nullable Color foreground) {
|
||||
clearTail();
|
||||
if (text != null) {
|
||||
appendTailText(new TextFragment(text, false, false, foreground));
|
||||
appendTailText(new TextFragment(text, false, false, false, foreground));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,13 +246,16 @@ public class LookupElementPresentation {
|
||||
public final String text;
|
||||
private final boolean myGrayed;
|
||||
private final boolean myItalic;
|
||||
|
||||
private final boolean myHighlighted;
|
||||
@Nullable private final Color myFgColor;
|
||||
|
||||
private TextFragment(String text, boolean grayed, boolean italic, @Nullable Color fgColor) {
|
||||
private TextFragment(String text, boolean grayed, boolean italic, boolean highlight, @Nullable Color fgColor) {
|
||||
this.text = text;
|
||||
myGrayed = grayed;
|
||||
myItalic = italic;
|
||||
myFgColor = fgColor;
|
||||
myHighlighted = highlight;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -269,6 +276,10 @@ public class LookupElementPresentation {
|
||||
return myItalic;
|
||||
}
|
||||
|
||||
public boolean isHighlighted() {
|
||||
return myHighlighted;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Color getForegroundColor() {
|
||||
return myFgColor;
|
||||
|
||||
@@ -205,7 +205,7 @@ public final class LookupCellRenderer implements ListCellRenderer<LookupElement>
|
||||
|
||||
myTailComponent.clear();
|
||||
if (isSelected || allowedWidth >= 0) {
|
||||
setTailTextLabel(isSelected, presentation, grayedForeground, isSelected ? getMaxWidth() : allowedWidth, nonFocusedSelection,
|
||||
setTailTextLabel(item, isSelected, presentation, grayedForeground, isSelected ? getMaxWidth() : allowedWidth, nonFocusedSelection,
|
||||
getRealFontMetrics(item, false, CUSTOM_TAIL_FONT));
|
||||
}
|
||||
|
||||
@@ -277,7 +277,7 @@ public final class LookupCellRenderer implements ListCellRenderer<LookupElement>
|
||||
return myMaxWidth;
|
||||
}
|
||||
|
||||
private void setTailTextLabel(boolean isSelected,
|
||||
private void setTailTextLabel(LookupElement item, boolean isSelected,
|
||||
LookupElementPresentation presentation,
|
||||
Color foreground,
|
||||
int allowedWidth,
|
||||
@@ -291,7 +291,12 @@ public final class LookupCellRenderer implements ListCellRenderer<LookupElement>
|
||||
|
||||
String trimmed = trimLabelText(fragment.text, allowedWidth, fontMetrics);
|
||||
int fragmentStyle = fragment.isItalic() ? style | SimpleTextAttributes.STYLE_ITALIC : style;
|
||||
myTailComponent.append(trimmed, new SimpleTextAttributes(fragmentStyle, getTailTextColor(isSelected, fragment, foreground, nonFocusedSelection)));
|
||||
if (fragment.isHighlighted()) {
|
||||
renderItemName(item, foreground, fragmentStyle, trimmed, myTailComponent);
|
||||
}
|
||||
else {
|
||||
myTailComponent.append(trimmed, new SimpleTextAttributes(fragmentStyle, getTailTextColor(isSelected, fragment, foreground, nonFocusedSelection)));
|
||||
}
|
||||
allowedWidth -= getStringWidth(trimmed, fontMetrics);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1839,6 +1839,9 @@ java.folding.icons.for.control.flow.description=Add folding icons to the gutter
|
||||
java.find.usages.always.use.top.hierarchy.methods=true
|
||||
java.find.usages.always.use.top.hierarchy.methods.description=IDE by default uses top level hierarchy methods as find usage targets
|
||||
|
||||
java.completion.methods.use.tags=true
|
||||
java.completion.methods.use.tags.description=Use additional tags for methods for completion
|
||||
|
||||
marketplace.certificate.signature.check=true
|
||||
marketplace.certificate.signature.check.description=Verify signatures for plugins downloaded from plugins.jetbrains.com
|
||||
custom-repository.certificate.signature.check=false
|
||||
|
||||
117
platform/util/src/com/intellij/openapi/util/text/MethodTags.java
Normal file
117
platform/util/src/com/intellij/openapi/util/text/MethodTags.java
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.openapi.util.text;
|
||||
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.text.NameUtilCore;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
public class MethodTags {
|
||||
@NotNull
|
||||
public static Set<String> tags(@Nullable String referenceName) {
|
||||
if (referenceName == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
String[] strings = NameUtilCore.nameToWords(referenceName);
|
||||
if (strings.length == 0) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
String[] canBeFirst = getTags(strings[0]);
|
||||
Set<String> result = new HashSet<>();
|
||||
for (String firstPart : canBeFirst) {
|
||||
StringJoiner joiner = new StringJoiner("");
|
||||
joiner.add(firstPart);
|
||||
for (int i = 1; i < strings.length; i++) {
|
||||
String string = strings[i];
|
||||
joiner.add(string);
|
||||
}
|
||||
result.add(joiner.toString());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicateBranchesInSwitch")
|
||||
private static String[] getTags(String string) {
|
||||
switch (string) {
|
||||
case "add":
|
||||
return new String[]{"put", "sum"};
|
||||
case "append":
|
||||
return new String[]{"add"};
|
||||
case "apply":
|
||||
return new String[]{"invoke", "do", "call"};
|
||||
case "assert":
|
||||
return new String[]{"expect", "verify", "test"};
|
||||
case "call":
|
||||
return new String[]{"execute", "run"};
|
||||
case "check":
|
||||
return new String[]{"test", "match"};
|
||||
case "count":
|
||||
return new String[]{"size", "length"};
|
||||
case "convert":
|
||||
return new String[]{"map"};
|
||||
case "create":
|
||||
return new String[]{"build", "make", "generate"};
|
||||
case "delete":
|
||||
return new String[]{"remove"};
|
||||
case "do":
|
||||
return new String[]{"run", "execute", "call"};
|
||||
case "execute":
|
||||
return new String[]{"run", "do", "call"};
|
||||
case "expect":
|
||||
return new String[]{"verify", "assert", "test"};
|
||||
case "from":
|
||||
return new String[]{"of", "parse"};
|
||||
case "generate":
|
||||
return new String[]{"create", "build"};
|
||||
case "invoke":
|
||||
return new String[]{"apply", "do", "call"};
|
||||
case "has":
|
||||
return new String[]{"contains", "check"};
|
||||
case "length":
|
||||
return new String[]{"size"};
|
||||
case "load":
|
||||
return new String[]{"read"};
|
||||
case "match":
|
||||
return new String[]{"test", "check"};
|
||||
case "minus":
|
||||
return new String[]{"subtract"};
|
||||
case "of":
|
||||
return new String[]{"parse", "from"};
|
||||
case "parse":
|
||||
return new String[]{"of", "from"};
|
||||
case "perform":
|
||||
return new String[]{"execute", "run", "do"};
|
||||
case "persist":
|
||||
return new String[]{"save"};
|
||||
case "print":
|
||||
return new String[]{"write"};
|
||||
case "put":
|
||||
return new String[]{"add"};
|
||||
case "remove":
|
||||
return new String[]{"delete"};
|
||||
case "run":
|
||||
return new String[]{"start", "execute", "call"};
|
||||
case "save":
|
||||
return new String[]{"persist", "write"};
|
||||
case "size":
|
||||
return new String[]{"length"};
|
||||
case "start":
|
||||
return new String[]{"call", "execute", "run"};
|
||||
case "test":
|
||||
return new String[]{"check", "match"};
|
||||
case "validate":
|
||||
return new String[]{"test", "check"};
|
||||
case "verify":
|
||||
return new String[]{"expect", "assert", "test"};
|
||||
case "write":
|
||||
return new String[]{"print"};
|
||||
default:
|
||||
return ArrayUtil.EMPTY_STRING_ARRAY;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user