[java-completion] IDEA-314367. Completion for similar methods

GitOrigin-RevId: 7f12554a48a88920f81b093c58af55ed5c47ffc6
This commit is contained in:
Mikhail Pyltsin
2023-02-28 16:57:01 +01:00
committed by intellij-monorepo-bot
parent 8107aa1050
commit ff51cc96fe
9 changed files with 285 additions and 13 deletions

View File

@@ -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<>();

View File

@@ -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);
}
}
}
}

View File

@@ -1,5 +1,5 @@
class C {
void test(java.util.List<String> list) {
list::ma<caret>
list::fi<caret>
}
}

View File

@@ -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();
}
}
""");
}
}

View File

@@ -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:

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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

View 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;
}
}
}