From 732018d4f281661815ae9c48a90b7d34b26a0085 Mon Sep 17 00:00:00 2001 From: Mikhail Pyltsin Date: Mon, 18 Nov 2024 16:38:51 +0100 Subject: [PATCH] [java-completion] IDEA-363488 Completion for similar methods: propose more often - restart if there are not matched items, but tags can be used GitOrigin-RevId: 9620630c72759450e0c4b2c375da4784bee8f088 --- .../completion/JavaCompletionContributor.java | 7 ++ .../codeInsight/completion/MethodTags.java | 36 +++++-- .../completion/NormalCompletionTest.java | 101 +++++++++++------- 3 files changed, 99 insertions(+), 45 deletions(-) diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java index 42721af555c3..3694974d816c 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java @@ -482,6 +482,13 @@ public final class JavaCompletionContributor extends CompletionContributor imple } else { refSuggestions = completeReference(parameters, parentRef, session, expectedInfos, matcher::prefixMatches); + if (refSuggestions + .stream() + .map(lookupElement -> MethodTags.collectTags(lookupElement, matcher::prefixMatches)) + .anyMatch(t -> t != null && !t.isEmpty())) { + //it is possible to propose some tags, let's try to do this + _result.restartCompletionWhenNothingMatches(); + } } } List filtered = filterReferenceSuggestions(parameters, expectedInfos, refSuggestions); diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/MethodTags.java b/java/java-impl/src/com/intellij/codeInsight/completion/MethodTags.java index 75f4de751b91..2bd54976258f 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/MethodTags.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/MethodTags.java @@ -8,6 +8,7 @@ import com.intellij.codeInsight.lookup.impl.LookupCellRenderer; import com.intellij.java.JavaBundle; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.Strings; import com.intellij.psi.CommonClassNames; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; @@ -36,20 +37,28 @@ public final class MethodTags { if (matcher.value(element.getLookupString())) { return null; } + Set tags = collectTags(element, matcher); + if (tags == null) return null; + if (tags.isEmpty()) { + return null; + } + return new TagLookupElementDecorator(element, tags, prefix, completionType == CompletionType.SMART); + } + + /** + * @return Set of tags, which can be used for this element, can return null if an element is not for PsiMember + */ + static @Nullable Set collectTags(@NotNull LookupElement element, @NotNull Condition matcher) { String lookupString = element.getLookupString(); PsiElement psiElement = element.getPsiElement(); if (!(psiElement instanceof PsiMember psiMember)) { return null; } PsiClass psiClass = psiMember.getContainingClass(); - Set tags = tags(lookupString).stream() + return tags(lookupString).stream() .filter(t -> matcher.value(t.name()) && t.matcher().test(psiClass)) .map(t -> t.name()) .collect(Collectors.toSet()); - if (tags.isEmpty()) { - return null; - } - return new TagLookupElementDecorator(element, tags, prefix, completionType == CompletionType.SMART); } @ApiStatus.Experimental @@ -89,7 +98,22 @@ public final class MethodTags { @Override public void renderElement(@NotNull LookupElementPresentation presentation) { super.renderElement(presentation); - if (myTags.size() == 1) { + String itemText = presentation.getItemText(); + String tailText = presentation.getTailText(); + if (itemText != null && (tailText == null || tailText.isBlank() || (tailText.startsWith("(") && tailText.endsWith(")")))) { + if (tailText == null) { + tailText = ""; + } + String tagMessage = JavaBundle.message("java.completion.tag", myTags.size()); + String fullItemText = itemText + tailText + " " + tagMessage + " "; + String allTags = Strings.join(myTags, ", "); + fullItemText += allTags; + presentation.setItemText(fullItemText); + presentation.decorateItemTextRange(new TextRange(itemText.length(), itemText.length() + tailText.length() + 1 + tagMessage.length()), + LookupElementPresentation.LookupItemDecoration.GRAY); + presentation.setTailText(""); + } + else if (myTags.size() == 1) { presentation.appendTailText(" " + JavaBundle.message("java.completion.tag", myTags.size()) + " ", true); int startOffset = getStartOffset(presentation); String text = myTags.iterator().next(); diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionTest.java index c9fd9839ed9b..29a5b068a04a 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionTest.java @@ -1837,7 +1837,7 @@ public class NormalCompletionTest extends NormalCompletionTestCase { myFixture.complete(CompletionType.BASIC, 2); myFixture.checkResult(""" import foo.myClass; - + class Foo extends myClass """); } @@ -1865,7 +1865,7 @@ public class NormalCompletionTest extends NormalCompletionTestCase { void foo(Foo f) { f. } - } + } """); myFixture.completeBasic(); assertFalse(myFixture.getLookupElementStrings().contains("Inner")); @@ -1929,7 +1929,7 @@ public class NormalCompletionTest extends NormalCompletionTestCase { UIUtil.dispatchAllInvocationEvents(); myFixture.checkResult(""" class Foo {int i; - + public int getI() { return this.i; } @@ -2836,28 +2836,28 @@ public class NormalCompletionTest extends NormalCompletionTestCase { public void testPackagePrivateConstantInInterface() { myFixture.addClass(""" package com.example.x; - + public interface Exposed extends PackPrivate { int value(); } """); myFixture.addClass(""" package com.example.x; - + interface PackPrivate { Exposed CONSTANT = () -> 5; } """); myFixture.configureByText("Main.java", """ package com.example; - + import com.example.x.Exposed; - + public class Main { public static void main(String[] args) { test(CONST); } - + private static void test(Exposed exposed) { } }"""); @@ -2865,14 +2865,14 @@ public class NormalCompletionTest extends NormalCompletionTestCase { myFixture.type('\n'); myFixture.checkResult(""" package com.example; - + import com.example.x.Exposed; - + public class Main { public static void main(String[] args) { test(Exposed.CONSTANT); } - + private static void test(Exposed exposed) { } }"""); @@ -2894,10 +2894,10 @@ public class NormalCompletionTest extends NormalCompletionTestCase { public void testEnumMapNoTypeParams() { myFixture.configureByText("Test.java", """ import java.util.Map; - + public abstract class SuperClass { enum X {A, B, C} - + void run() { Map map = new EnumM } @@ -2908,10 +2908,10 @@ public class NormalCompletionTest extends NormalCompletionTestCase { myFixture.checkResult(""" import java.util.EnumMap; import java.util.Map; - + public abstract class SuperClass { enum X {A, B, C} - + void run() { Map map = new EnumMap<>() } @@ -2923,9 +2923,9 @@ public class NormalCompletionTest extends NormalCompletionTestCase { Registry.get("java.completion.methods.use.tags").setValue(true, getTestRootDisposable()); myFixture.configureByText("Test.java", """ import java.util.HashSet; - + public abstract class SuperClass { - + void run() { HashSet objects = new HashSet<>(); objects.len; @@ -2936,9 +2936,9 @@ public class NormalCompletionTest extends NormalCompletionTestCase { myFixture.type('\n'); myFixture.checkResult(""" import java.util.HashSet; - + public abstract class SuperClass { - + void run() { HashSet objects = new HashSet<>(); objects.size(); @@ -2952,14 +2952,14 @@ public class NormalCompletionTest extends NormalCompletionTestCase { Registry.get("java.completion.methods.use.tags").setValue(true, getTestRootDisposable()); myFixture.configureByText("Test.java", """ import java.util.HashSet; - + public abstract class SuperClass { - + void run() { HashSet objects = new HashSet<>(); objects.len; } - + static void len(HashSet o){} } """); @@ -2967,14 +2967,14 @@ public class NormalCompletionTest extends NormalCompletionTestCase { myFixture.type('\n'); myFixture.checkResult(""" import java.util.HashSet; - + public abstract class SuperClass { - + void run() { HashSet objects = new HashSet<>(); objects.size(); } - + static void len(HashSet o){} } """); @@ -2985,9 +2985,9 @@ public class NormalCompletionTest extends NormalCompletionTestCase { Registry.get("java.completion.methods.use.tags").setValue(true, getTestRootDisposable()); myFixture.configureByText("Test.java", """ import java.util.HashSet; - + public abstract class SuperClass { - + void run() { HashSet objects = new HashSet<>(); objects.le; @@ -3001,6 +3001,29 @@ public class NormalCompletionTest extends NormalCompletionTestCase { assertTrue(ContainerUtil.exists(lookupElements, t -> t.getLookupString().equals("size"))); } + @NeedsIndex.Full + public void testTagAddInvocationRestart() { + Registry.get("java.completion.methods.use.tags").setValue(true, getTestRootDisposable()); + myFixture.configureByText("Test.java", """ + import java.util.HashSet; + + public abstract class SuperClass { + + void run() { + HashSet objects = new HashSet<>(); + objects.; + } + } + """); + myFixture.complete(CompletionType.BASIC, 1); + myFixture.type("l"); + myFixture.type("e"); + myFixture.type("n"); + myFixture.type("g"); + myFixture.type("t"); + assertTrue(ContainerUtil.exists(myFixture.getLookupElements(), t -> t.getLookupString().equals("size"))); + } + @NeedsIndex.Full public void testResolveToSubclassMethod() { myFixture.configureByText("Test.java", """ @@ -3107,9 +3130,9 @@ public class NormalCompletionTest extends NormalCompletionTestCase { public void testNestedImplicitClass() { myFixture.configureByText("Test.java", """ public static class NestedClass{ - + } - + public static void main(String[] args) { NestedCla } @@ -3117,9 +3140,9 @@ public class NormalCompletionTest extends NormalCompletionTestCase { myFixture.complete(CompletionType.BASIC); myFixture.checkResult(""" public static class NestedClass{ - + } - + public static void main(String[] args) { NestedClass } @@ -3132,14 +3155,14 @@ public class NormalCompletionTest extends NormalCompletionTestCase { public static class Nested2ClassMore { } } - - + + public void main(String[] args) { Nested nested = new Nested(); } - + public void t(Nested2ClassMo nested2) { - + } """); myFixture.complete(CompletionType.BASIC); @@ -3149,14 +3172,14 @@ public class NormalCompletionTest extends NormalCompletionTestCase { public static class Nested2ClassMore { } } - - + + public void main(String[] args) { Nested nested = new Nested(); } - + public void t(Nested.Nested2ClassMore nested2) { - + } """); }