From 2876989890663952e05fedfb122d14d7ee2e33f0 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Sat, 3 Aug 2019 12:49:30 +0700 Subject: [PATCH] Completion in commit window improved (IDEA-CR-50723) 1. Return lookup elements in pure function way 2. TopLevelCompletionContributor renamed to SymbolNameCompletionContributor 3. Added support for Groovy GitOrigin-RevId: 9428d52da9e22aaeb507afa1d86b6704116311c0 --- java/java-impl/src/META-INF/JavaPlugin.xml | 2 +- .../JavaSymbolNameCompletionContributor.java | 70 +++++++++++++++++++ .../JavaTopLevelCompletionContributor.java | 62 ---------------- ...aSymbolNameCompletionContributorTest.java} | 33 +++++---- ...a => SymbolNameCompletionContributor.java} | 14 ++-- .../SymbolNameCompletionContributorEP.java | 17 +++++ .../TopLevelCompletionContributorEP.java | 17 ----- .../impl/CompletionServiceImpl.java | 6 -- .../src/META-INF/LangExtensionPoints.xml | 4 +- .../vcs/CommitCompletionContributor.java | 4 +- plugins/groovy/src/META-INF/plugin.xml | 1 + ...GroovySymbolNameCompletionContributor.java | 42 +++++++++++ 12 files changed, 161 insertions(+), 111 deletions(-) create mode 100644 java/java-impl/src/com/intellij/codeInsight/completion/JavaSymbolNameCompletionContributor.java delete mode 100644 java/java-impl/src/com/intellij/codeInsight/completion/JavaTopLevelCompletionContributor.java rename java/java-tests/testSrc/com/intellij/java/codeInsight/completion/{JavaTopLevelCompletionContributorTest.java => JavaSymbolNameCompletionContributorTest.java} (54%) rename platform/lang-api/src/com/intellij/codeInsight/completion/{TopLevelCompletionContributor.java => SymbolNameCompletionContributor.java} (60%) create mode 100644 platform/lang-api/src/com/intellij/codeInsight/completion/SymbolNameCompletionContributorEP.java delete mode 100644 platform/lang-api/src/com/intellij/codeInsight/completion/TopLevelCompletionContributorEP.java create mode 100644 plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GroovySymbolNameCompletionContributor.java diff --git a/java/java-impl/src/META-INF/JavaPlugin.xml b/java/java-impl/src/META-INF/JavaPlugin.xml index 57abb09dd535..a218d100f9ea 100644 --- a/java/java-impl/src/META-INF/JavaPlugin.xml +++ b/java/java-impl/src/META-INF/JavaPlugin.xml @@ -1221,7 +1221,7 @@ - + diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaSymbolNameCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaSymbolNameCompletionContributor.java new file mode 100644 index 000000000000..3ac387b021b9 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaSymbolNameCompletionContributor.java @@ -0,0 +1,70 @@ +// Copyright 2000-2019 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 com.intellij.codeInsight.completion; + +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.psi.*; +import com.intellij.util.ObjectUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class JavaSymbolNameCompletionContributor implements SymbolNameCompletionContributor { + @NotNull + @Override + public Collection getLookupElements(@NotNull PsiFile file, int invocationCount, @NotNull String prefix) { + PsiClassOwner javaFile = ObjectUtils.tryCast(file, PsiClassOwner.class); + if (javaFile == null) return Collections.emptyList(); + List result = new ArrayList<>(); + for (PsiClass aClass : javaFile.getClasses()) { + String name = aClass.getName(); + if (name != null) { + result.add(LookupElementBuilder.create(name).withIcon(aClass.getIcon(0))); + String infix = getInfix(prefix, name); + String memberPrefix = null; + if (infix != null) { + memberPrefix = prefix.substring(0, name.length() + infix.length()); + } + else if (invocationCount <= 0) continue; + processClassBody(invocationCount, result, aClass, infix, memberPrefix); + } + } + return result; + } + + protected void processClassBody(int invocationCount, List result, PsiElement aClass, String infix, String memberPrefix) { + for (PsiElement child = aClass.getFirstChild(); child != null; child = child.getNextSibling()) { + if (child instanceof PsiField || child instanceof PsiMethod || child instanceof PsiClass) { + String memberName = ((PsiMember)child).getName(); + Icon icon = child.getIcon(0); + if (memberName != null) { + LookupElementBuilder element = LookupElementBuilder.create(memberName).withIcon(icon); + if (invocationCount > 0) { + result.add(element); + } + if (memberPrefix != null) { + if (child instanceof PsiMethod || child instanceof PsiField && !infix.equals("::") || infix.equals(".")) { + result.add(LookupElementBuilder.create(memberPrefix + memberName).withIcon(icon)); + } + } + } + } + } + } + + @Nullable + private static String getInfix(String currentPrefix, String className) { + if (!currentPrefix.startsWith(className)) return null; + for (String infix : new String[]{".", "#", "::"}) { + if (currentPrefix.startsWith(infix, className.length())) { + return infix; + } + } + return null; + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaTopLevelCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaTopLevelCompletionContributor.java deleted file mode 100644 index 3da557f37810..000000000000 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaTopLevelCompletionContributor.java +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2000-2019 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 com.intellij.codeInsight.completion; - -import com.intellij.codeInsight.lookup.LookupElementBuilder; -import com.intellij.psi.*; -import com.intellij.util.ObjectUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -public class JavaTopLevelCompletionContributor implements TopLevelCompletionContributor { - @Override - public void addLookupElements(@NotNull PsiFile file, int invocationCount, @NotNull CompletionResultSet result) { - PsiJavaFile javaFile = ObjectUtils.tryCast(file, PsiJavaFile.class); - if (javaFile == null) return; - PrefixMatcher currentMatcher = result.getPrefixMatcher(); - for (PsiClass aClass : javaFile.getClasses()) { - String name = aClass.getName(); - if (name != null) { - result.addElement(LookupElementBuilder.create(name).withIcon(aClass.getIcon(0))); - String infix = getInfix(currentMatcher.getPrefix(), name); - CompletionResultSet prefixed = null; - if (infix == null) { - if (invocationCount <= 0) continue; - } - else { - String memberPrefix = currentMatcher.getPrefix().substring(name.length() + infix.length()); - prefixed = result.withPrefixMatcher(currentMatcher.cloneWithPrefix(memberPrefix)); - } - for (PsiElement child = aClass.getFirstChild(); child != null; child = child.getNextSibling()) { - if (child instanceof PsiField || child instanceof PsiMethod || child instanceof PsiClass) { - String memberName = ((PsiMember)child).getName(); - Icon icon = child.getIcon(0); - if (memberName != null) { - LookupElementBuilder element = LookupElementBuilder.create(memberName).withIcon(icon); - if (invocationCount > 0) { - result.addElement(element); - } - if (prefixed != null) { - if (child instanceof PsiMethod || child instanceof PsiField && !infix.equals("::") || infix.equals(".")) { - prefixed.addElement(element); - } - } - } - } - } - } - } - } - - @Nullable - private static String getInfix(String currentPrefix, String className) { - if (!currentPrefix.startsWith(className)) return null; - for (String infix : new String[]{".", "#", "::"}) { - if (currentPrefix.startsWith(infix, className.length())) { - return infix; - } - } - return null; - } -} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/JavaTopLevelCompletionContributorTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/JavaSymbolNameCompletionContributorTest.java similarity index 54% rename from java/java-tests/testSrc/com/intellij/java/codeInsight/completion/JavaTopLevelCompletionContributorTest.java rename to java/java-tests/testSrc/com/intellij/java/codeInsight/completion/JavaSymbolNameCompletionContributorTest.java index 04aa14c21ac5..ac8d34b101af 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/JavaTopLevelCompletionContributorTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/JavaSymbolNameCompletionContributorTest.java @@ -1,19 +1,20 @@ // Copyright 2000-2019 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 com.intellij.java.codeInsight.completion; -import com.intellij.codeInsight.completion.CompletionResultSet; -import com.intellij.codeInsight.completion.TopLevelCompletionContributor; -import com.intellij.codeInsight.completion.TopLevelCompletionContributorEP; -import com.intellij.codeInsight.completion.impl.CompletionServiceImpl; +import com.intellij.codeInsight.completion.PlainPrefixMatcher; +import com.intellij.codeInsight.completion.PrefixMatcher; +import com.intellij.codeInsight.completion.SymbolNameCompletionContributor; +import com.intellij.codeInsight.completion.SymbolNameCompletionContributorEP; +import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.lang.java.JavaLanguage; import com.intellij.psi.PsiFile; import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase; +import one.util.streamex.StreamEx; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; +import java.util.Collection; -public class JavaTopLevelCompletionContributorTest extends LightJavaCodeInsightFixtureTestCase { +public class JavaSymbolNameCompletionContributorTest extends LightJavaCodeInsightFixtureTestCase { public void testBasics() { PsiFile file = myFixture.configureByText("Test.java", "class Foo {\n" + " void fooMethod() {}\n" + @@ -25,20 +26,18 @@ public class JavaTopLevelCompletionContributorTest extends LightJavaCodeInsightF "}"); checkCompletion(file, "", 0, "Foo", "Bar"); checkCompletion(file, "F", 1, "Foo", "fooMethod", "fooField", "FooClass"); - checkCompletion(file, "Foo::", 0, "fooMethod"); - checkCompletion(file, "Foo#", 0, "fooMethod", "fooField"); - checkCompletion(file, "Foo.", 0, "fooMethod", "fooField", "FooClass"); + checkCompletion(file, "Foo::", 0, "Foo::fooMethod"); + checkCompletion(file, "Foo#", 0, "Foo#fooMethod", "Foo#fooField"); + checkCompletion(file, "Foo.", 0, "Foo.fooMethod", "Foo.fooField", "Foo.FooClass"); checkCompletion(file, "B", 1, "Bar", "Baz"); } private static void checkCompletion(PsiFile file, String prefix, int invocationCount, String... expected) { - TopLevelCompletionContributor contributor = TopLevelCompletionContributorEP.forLanguage(JavaLanguage.INSTANCE); + SymbolNameCompletionContributor contributor = SymbolNameCompletionContributorEP.forLanguage(JavaLanguage.INSTANCE); assertNotNull(contributor); - List options = new ArrayList<>(); - CompletionResultSet resultSet = CompletionServiceImpl.createResultSetForTest( - option -> options.add(option.getLookupElement().getLookupString())); - resultSet = resultSet.withPrefixMatcher(prefix); - contributor.addLookupElements(file, invocationCount, resultSet); - assertEquals(Arrays.asList(expected), options); + Collection options = contributor.getLookupElements(file, invocationCount, prefix); + PrefixMatcher matcher = new PlainPrefixMatcher(prefix); + assertEquals(Arrays.asList(expected), + StreamEx.of(options).filter(matcher::prefixMatches).map(LookupElement::getLookupString).toList()); } } diff --git a/platform/lang-api/src/com/intellij/codeInsight/completion/TopLevelCompletionContributor.java b/platform/lang-api/src/com/intellij/codeInsight/completion/SymbolNameCompletionContributor.java similarity index 60% rename from platform/lang-api/src/com/intellij/codeInsight/completion/TopLevelCompletionContributor.java rename to platform/lang-api/src/com/intellij/codeInsight/completion/SymbolNameCompletionContributor.java index 60a069ad56d4..8c62f4bcd0cc 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/completion/TopLevelCompletionContributor.java +++ b/platform/lang-api/src/com/intellij/codeInsight/completion/SymbolNameCompletionContributor.java @@ -1,23 +1,29 @@ // Copyright 2000-2019 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 com.intellij.codeInsight.completion; +import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.psi.PsiFile; import org.jetbrains.annotations.NotNull; +import java.util.Collection; + /** * A language-specific completion contributor which provides reasonable amount of symbols declared in given file * (likely only top-level declarations). Such contributor could be used in plain text editors like VCS commit message field * to help users referring to code symbols from the text. * - * @see TopLevelCompletionContributorEP + * @see SymbolNameCompletionContributorEP */ -public interface TopLevelCompletionContributor { +public interface SymbolNameCompletionContributor { /** * Adds lookup elements from given file. * * @param file file to add elements from. * @param invocationCount number of times the completion was invoked (see {@link CompletionParameters#getInvocationCount()}). - * @param result a result object to add elements to (could be already prefixed with current context). + * @param prefix a prefix string. It's not required to return only matches starting with prefix, but this may be used to improve + * performance. + * @return a collection of {@link LookupElement}'s to suggest. */ - void addLookupElements(@NotNull PsiFile file, int invocationCount, @NotNull CompletionResultSet result); + @NotNull + Collection getLookupElements(@NotNull PsiFile file, int invocationCount, @NotNull String prefix); } diff --git a/platform/lang-api/src/com/intellij/codeInsight/completion/SymbolNameCompletionContributorEP.java b/platform/lang-api/src/com/intellij/codeInsight/completion/SymbolNameCompletionContributorEP.java new file mode 100644 index 000000000000..a94e93bace29 --- /dev/null +++ b/platform/lang-api/src/com/intellij/codeInsight/completion/SymbolNameCompletionContributorEP.java @@ -0,0 +1,17 @@ +// Copyright 2000-2019 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 com.intellij.codeInsight.completion; + +import com.intellij.lang.Language; +import com.intellij.lang.LanguageExtension; +import com.intellij.lang.LanguageExtensionPoint; +import com.intellij.openapi.extensions.ExtensionPointName; +import org.jetbrains.annotations.NotNull; + +public class SymbolNameCompletionContributorEP extends LanguageExtensionPoint { + private static final ExtensionPointName EP = new ExtensionPointName<>("com.intellij.completion.toplevel"); + private static final LanguageExtension INSTANCE = new CompletionExtension<>(EP.getName()); + + public static SymbolNameCompletionContributor forLanguage(@NotNull Language language) { + return INSTANCE.forLanguage(language); + } +} diff --git a/platform/lang-api/src/com/intellij/codeInsight/completion/TopLevelCompletionContributorEP.java b/platform/lang-api/src/com/intellij/codeInsight/completion/TopLevelCompletionContributorEP.java deleted file mode 100644 index 4ae2f98066fe..000000000000 --- a/platform/lang-api/src/com/intellij/codeInsight/completion/TopLevelCompletionContributorEP.java +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2000-2019 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 com.intellij.codeInsight.completion; - -import com.intellij.lang.Language; -import com.intellij.lang.LanguageExtension; -import com.intellij.lang.LanguageExtensionPoint; -import com.intellij.openapi.extensions.ExtensionPointName; -import org.jetbrains.annotations.NotNull; - -public class TopLevelCompletionContributorEP extends LanguageExtensionPoint { - private static final ExtensionPointName EP = new ExtensionPointName<>("com.intellij.completion.toplevel"); - private static final LanguageExtension INSTANCE = new CompletionExtension<>(EP.getName()); - - public static TopLevelCompletionContributor forLanguage(@NotNull Language language) { - return INSTANCE.forLanguage(language); - } -} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CompletionServiceImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CompletionServiceImpl.java index c8a6243c6f77..a6eb4aaf47ad 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CompletionServiceImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CompletionServiceImpl.java @@ -20,7 +20,6 @@ import com.intellij.util.Consumer; import com.intellij.util.ExceptionUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; import java.util.ArrayList; @@ -101,11 +100,6 @@ public final class CompletionServiceImpl extends CompletionService { return new CompletionResultSetImpl(consumer, matcher, contributor, parameters, defaultSorter(parameters, matcher), null); } - @TestOnly - public static CompletionResultSet createResultSetForTest(Consumer consumer) { - return new CompletionResultSetImpl(consumer, PrefixMatcher.ALWAYS_TRUE, null, null, new CompletionSorterImpl(new ArrayList<>()), null); - } - @Override public CompletionProcess getCurrentCompletion() { CompletionProgressIndicator indicator = getCurrentCompletionProgressIndicator(); diff --git a/platform/platform-resources/src/META-INF/LangExtensionPoints.xml b/platform/platform-resources/src/META-INF/LangExtensionPoints.xml index 842d4b14b940..35c0f8f69cd3 100644 --- a/platform/platform-resources/src/META-INF/LangExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/LangExtensionPoints.xml @@ -545,8 +545,8 @@ - - + + + getLookupElements(@NotNull PsiFile file, int invocationCount, @NotNull String prefix) { + Collection elements = super.getLookupElements(file, invocationCount, prefix); + if (file instanceof GroovyFile) { + GrMethod[] methods = ((GroovyFile)file).getMethods(); + for (GrMethod method : methods) { + elements.add(LookupElementBuilder.create(method.getName()) + .withTailText(" in "+file.getName(), true).withIcon(method.getIcon(0))); + } + } + return elements; + } + + @Override + protected void processClassBody(int invocationCount, List result, PsiElement aClass, String infix, String memberPrefix) { + if (aClass instanceof GrTypeDefinition) { + GrTypeDefinitionBody body = ((GrTypeDefinition)aClass).getBody(); + if (body != null) { + super.processClassBody(invocationCount, result, body, infix, memberPrefix); + } + } + } +}