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

GitOrigin-RevId: ccce34749357dbd8b01625cc4b8322ae50445cf4
This commit is contained in:
Mikhail Pyltsin
2023-03-01 09:20:39 +01:00
committed by intellij-monorepo-bot
parent bfa4d04aca
commit 7129c6a6e2
5 changed files with 155 additions and 124 deletions

View File

@@ -25,7 +25,6 @@ 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;
@@ -150,7 +149,15 @@ public class JavaNoVariantsDelegator extends CompletionContributor implements Du
@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());
PsiElement psiElement = element.getPsiElement();
if (!(psiElement instanceof PsiMember psiMember)) {
return null;
}
PsiClass psiClass = psiMember.getContainingClass();
Set<String> tags = MethodTags.tags(lookupString).stream()
.filter(t -> matcher.prefixMatches(t.getName()) && t.getMatcher().test(psiClass))
.map(t->t.getName())
.collect(Collectors.toSet());
if (tags.isEmpty()) {
return null;
}
@@ -316,9 +323,9 @@ public class JavaNoVariantsDelegator extends CompletionContributor implements Du
if (myMatcher.prefixMatches(name)) {
return true;
}
Set<String> tags = MethodTags.tags(name);
for (String tag : tags) {
if (myMatcher.prefixMatches(tag)) {
Set<MethodTags.Tag> tags = MethodTags.tags(name);
for (MethodTags.Tag tag : tags) {
if (myMatcher.prefixMatches(tag.getName())) {
return true;
}
}
@@ -334,6 +341,7 @@ public class JavaNoVariantsDelegator extends CompletionContributor implements Du
static class TagLookupElementDecorator extends LookupElementDecorator<LookupElement> {
@NotNull
private final Set<String> myTags;
protected TagLookupElementDecorator(@NotNull LookupElement delegate, @NotNull Set<String> tags) {
@@ -341,6 +349,7 @@ public class JavaNoVariantsDelegator extends CompletionContributor implements Du
myTags = tags;
}
@NotNull
public Set<String> getTags() {
return myTags;
}
@@ -355,10 +364,20 @@ public class JavaNoVariantsDelegator extends CompletionContributor implements Du
@Override
public void renderElement(@NotNull LookupElementPresentation presentation) {
super.renderElement(presentation);
if (!myTags.isEmpty()) {
if (myTags.size()==1) {
presentation.appendTailText(" " + JavaBundle.message("java.completion.tag") + " ", true);
presentation.appendTailText(myTags.iterator().next(), true, true);
}
else if (myTags.size() > 1) {
presentation.appendTailText(" " + JavaBundle.message("java.completion.tags") + " ", true);
Iterator<String> iterator = myTags.iterator();
String firstTag = iterator.next();
presentation.appendTailText(firstTag, true, true);
while (iterator.hasNext()) {
presentation.appendTailText(", ", true);
presentation.appendTailText(iterator.next(), true, true);
}
}
}
}
}

View File

@@ -0,0 +1,128 @@
// 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.codeInsight.completion;
import com.intellij.psi.CommonClassNames;
import com.intellij.psi.PsiClass;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.NameUtilCore;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.Predicate;
import static com.intellij.codeInsight.completion.MethodTags.Tag.*;
public class MethodTags {
@ApiStatus.Experimental
@NotNull
public static Set<Tag> tags(@Nullable String referenceName) {
if (referenceName == null) {
return Collections.emptySet();
}
String[] strings = NameUtilCore.nameToWords(referenceName);
if (strings.length == 0) {
return Collections.emptySet();
}
Tag[] canBeFirst = getTags(strings[0]);
Set<Tag> result = new HashSet<>();
for (Tag firstPart : canBeFirst) {
StringJoiner joiner = new StringJoiner("");
joiner.add(firstPart.name);
for (int i = 1; i < strings.length; i++) {
String string = strings[i];
joiner.add(string);
}
result.add(of(joiner.toString(), firstPart.matcher));
}
return result;
}
private static Tag[] getTags(String string) {
return switch (string) {
case "add" -> new Tag[]{of("put", childOf(CommonClassNames.JAVA_LANG_ITERABLE)),
of("sum", childOf("java.math.BigDecimal", "java.math.BigInteger")),
of("plus", childOf("java.math.BigDecimal", "java.math.BigInteger"))};
case "append" -> anyFrom("add");
case "apply" -> anyFrom("invoke", "do", "call");
case "assert" -> anyFrom("expect", "verify", "test");
case "build" -> anyFrom("create", "make", "generate");
case "call" -> anyFrom("execute", "run");
case "check" -> anyFrom("test", "match");
case "count" -> anyFrom("size", "length");
case "convert" -> anyFrom("map");
case "create" -> anyFrom("build", "make", "generate");
case "delete" -> anyFrom("remove");
case "do" -> anyFrom("run", "execute", "call");
case "execute" -> anyFrom("run", "do", "call");
case "expect" -> anyFrom("verify", "assert", "test");
case "from" -> anyFrom("of", "parse");
case "generate" -> anyFrom("create", "build");
case "invoke" -> anyFrom("apply", "do", "call");
case "has" -> anyFrom("contains", "check");
case "length" -> anyFrom("size", "count");
case "load" -> anyFrom("read");
case "match" -> anyFrom("test", "check");
case "of" -> anyFrom("parse", "from");
case "parse" -> anyFrom("of", "from");
case "perform" -> anyFrom("execute", "run", "do");
case "persist" -> anyFrom("save");
case "print" -> anyFrom("write");
case "put" -> new Tag[]{of("add", childOf(CommonClassNames.JAVA_UTIL_MAP))};
case "remove" -> anyFrom("delete");
case "run" -> anyFrom("start", "execute", "call");
case "save" -> anyFrom("persist", "write");
case "size" -> anyFrom("length", "count");
case "start" -> anyFrom("call", "execute", "run");
case "subtract" -> anyFrom("minus");
case "test" -> anyFrom("check", "match");
case "validate" -> anyFrom("test", "check");
case "verify" -> anyFrom("expect", "assert", "test");
case "write" -> anyFrom("print");
default -> anyFrom();
};
}
static class Tag {
@NotNull
private final String name;
@NotNull
private final Predicate<PsiClass> matcher;
private Tag(@NotNull String name, @NotNull Predicate<PsiClass> matcher) {
this.name = name;
this.matcher = matcher;
}
@NotNull
String getName() {
return name;
}
@NotNull
Predicate<PsiClass> getMatcher() {
return matcher;
}
static Tag of(@NotNull String name, @NotNull Predicate<PsiClass> matcher) {
return new Tag(name, matcher);
}
static Predicate<PsiClass> any() {
return t -> true;
}
static Predicate<PsiClass> childOf(String... classes) {
return psiClass -> ContainerUtil.exists(classes, clazz -> InheritanceUtil.isInheritor(psiClass, clazz));
}
public static Tag[] anyFrom(String... names) {
return Arrays.stream(names).map(name -> of(name, any())).toArray(Tag[]::new);
}
}
}

View File

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

View File

@@ -1810,3 +1810,4 @@ 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:
java.completion.tags=Tags:

View File

@@ -1,117 +0,0 @@
// 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;
}
}
}