From 32b759069f4e1be127c63c70a96ece4a04f6b217 Mon Sep 17 00:00:00 2001 From: peter Date: Tue, 16 Apr 2019 18:19:13 +0200 Subject: [PATCH] avoid expensive PSI copying in completion when the dummy identifier is empty, use this to speed up word completion --- .../completion/CompletionContributor.java | 12 +----- .../completion/CompletionParameters.java | 27 +++++++++++- .../CompletionInitializationUtil.java | 9 +++- .../completion/WordCompletionContributor.java | 42 ++++++++++--------- 4 files changed, 56 insertions(+), 34 deletions(-) diff --git a/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionContributor.java b/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionContributor.java index a07f068710d6..18ea2205d2a9 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionContributor.java +++ b/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionContributor.java @@ -42,14 +42,6 @@ import java.util.List; * A more generic way is to override default {@link #fillCompletionVariants(CompletionParameters, CompletionResultSet)} implementation * and provide your own. It's easier to debug, but harder to write.

* - * Q: What does the {@link CompletionParameters#getPosition()} return?
- * A: When completion is invoked, the file being edited is first copied (the original file can be accessed from {@link com.intellij.psi.PsiFile#getOriginalFile()} - * and {@link CompletionParameters#getOriginalFile()}. Then a special 'dummy identifier' string is inserted to the copied file at caret offset (removing the selection). - * Most often this string is an identifier (see {@link CompletionInitializationContext#DUMMY_IDENTIFIER}). - * This is usually done to guarantee that there'll always be some non-empty element there, which will be easy to describe via {@link ElementPattern}s. - * Also a reference can suddenly appear in that position, which will certainly help invoking its {@link PsiReference#getVariants()}. - * Dummy identifier string can be easily changed in {@link #beforeCompletion(CompletionInitializationContext)} method.

- * * Q: How do I get automatic lookup element filtering by prefix?
* A: When you return variants from reference ({@link PsiReference#getVariants()}), the filtering will be done * automatically, with prefix taken as the reference text from its start ({@link PsiReference#getRangeInElement()}) to @@ -150,7 +142,7 @@ public abstract class CompletionContributor { * If you want to implement this functionality directly by overriding this method, the following is for you. * Always check that parameters match your situation, and that completion type ({@link CompletionParameters#getCompletionType()} * is of your favourite kind. This method is run inside a read action. If you do any long activity non-related to PSI in it, please - * ensure you call {@link com.intellij.openapi.progress.ProgressManager#checkCanceled()} often enough so that the completion process + * ensure you call {@link ProgressManager#checkCanceled()} often enough so that the completion process * can be cancelled smoothly when the user begins to type in the editor. */ public void fillCompletionVariants(@NotNull final CompletionParameters parameters, @NotNull CompletionResultSet result) { @@ -201,7 +193,7 @@ public abstract class CompletionContributor { } /** - * Called when the completion is finished quickly, lookup hasn't been shown and gives possibility to autoinsert some item (typically - the only one). + * Called when the completion is finished quickly, lookup hasn't been shown and gives possibility to auto-insert some item (typically - the only one). */ @Nullable public AutoCompletionDecision handleAutoCompletionPossibility(@NotNull AutoCompletionContext context) { diff --git a/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionParameters.java b/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionParameters.java index 82630859cfb7..3078ee32efd3 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionParameters.java +++ b/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionParameters.java @@ -50,6 +50,23 @@ public final class CompletionParameters { return new CompletionParameters(myPosition, myOriginalFile, myCompletionType, myOffset, newCount, myEditor, myProcess); } + /** + * Return the leaf PSI element in the "completion file" at offset {@link #getOffset()}.

+ * + * "Completion file" is a PSI file used for completion purposes. Most often it's a non-physical copy of the file being edited + * (the original file can be accessed from {@link PsiFile#getOriginalFile()} or {@link #getOriginalFile()}).

+ * + * A special 'dummy identifier' string is inserted to the copied file at caret offset (removing the selection). + * Most often this string is an identifier (see {@link CompletionInitializationContext#DUMMY_IDENTIFIER}). + * It can be changed via {@link CompletionContributor#beforeCompletion(CompletionInitializationContext)} method.

+ * + * Why? This way there'll always be some non-empty element there, which usually reduces the number of + * possible cases to be considered inside a {@link CompletionContributor}. + * Also, even if completion was invoked in the middle of a white space, a reference might appear there after dummy identifier is inserted, + * and its {@link com.intellij.psi.PsiReference#getVariants()} can then be suggested.

+ * + * If the dummy identifier is empty, then the file isn't copied and this method returns whatever is at caret in the original file. + */ @NotNull public PsiElement getPosition() { return myPosition; @@ -60,6 +77,9 @@ public final class CompletionParameters { return myOriginalFile.findElementAt(myPosition.getTextRange().getStartOffset()); } + /** + * @return the file being edited, possibly injected, where code completion was invoked. + */ @NotNull public PsiFile getOriginalFile() { return myOriginalFile; @@ -70,14 +90,17 @@ public final class CompletionParameters { return myCompletionType; } + /** + * @return the offset (relative to the file) where code completion was invoked. + */ public int getOffset() { return myOffset; } /** * @return - * 0 for autopopup - * 1 for explicitly invoked completion + * 0 for autopopup
+ * 1 for explicitly invoked completion
* >1 for next completion invocations when one lookup is already active */ public int getInvocationCount() { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionInitializationUtil.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionInitializationUtil.java index 169b2460e04e..5c01664f68a4 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionInitializationUtil.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionInitializationUtil.java @@ -116,6 +116,9 @@ public class CompletionInitializationUtil { CompletionProcessEx indicator, OffsetsInFile topLevelOffsets) { CompletionAssertions.checkEditorValid(initContext.getEditor()); + if (initContext.getDummyIdentifier().isEmpty()) { + return topLevelOffsets; + } Editor hostEditor = InjectedLanguageUtil.getTopLevelEditor(initContext.getEditor()); OffsetMap hostMap = topLevelOffsets.getOffsets(); @@ -141,7 +144,9 @@ public class CompletionInitializationUtil { OffsetsInFile translatedOffsets = hostCopyOffsets.toInjectedIfAny(hostStartOffset); if (translatedOffsets != hostCopyOffsets) { PsiFile injected = translatedOffsets.getFile(); - if (injected instanceof PsiFileImpl && InjectedLanguageManager.getInstance(originalFile.getProject()).isInjectedFragment(originalFile)) { + if (originalFile != injected && + injected instanceof PsiFileImpl && + InjectedLanguageManager.getInstance(originalFile.getProject()).isInjectedFragment(originalFile)) { ((PsiFileImpl)injected).setOriginalFile(originalFile); } DocumentWindow documentWindow = InjectedLanguageUtil.getDocumentWindow(injected); @@ -158,7 +163,7 @@ public class CompletionInitializationUtil { @NotNull private static PsiElement findCompletionPositionLeaf(OffsetsInFile offsets, int offset, PsiFile originalFile) { PsiElement insertedElement = offsets.getFile().findElementAt(offset); - if (insertedElement == null && offsets.getFile().getTextLength() == 0) { + if (insertedElement == null && offsets.getFile().getTextLength() == offset) { insertedElement = PsiTreeUtil.getDeepestLast(offsets.getFile()); } CompletionAssertions.assertCompletionPositionPsiConsistent(offsets, offset, originalFile, insertedElement); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/WordCompletionContributor.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/WordCompletionContributor.java index c29c1e1a1978..6ceaa27bf8aa 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/WordCompletionContributor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/WordCompletionContributor.java @@ -27,10 +27,8 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.patterns.ElementPattern; import com.intellij.psi.*; import com.intellij.psi.impl.cache.impl.id.IdTableBuilding; -import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.HashSet; @@ -44,6 +42,17 @@ import static com.intellij.patterns.PlatformPatterns.psiElement; */ public class WordCompletionContributor extends CompletionContributor implements DumbAware { + @Override + public void beforeCompletion(@NotNull CompletionInitializationContext context) { + if (context.getCompletionType() == CompletionType.BASIC && isWordCompletionDefinitelyEnabled(context.getFile())) { + context.setDummyIdentifier(""); + } + } + + private static boolean isWordCompletionDefinitelyEnabled(@NotNull PsiFile file) { + return DumbService.isDumb(file.getProject()) || file instanceof PsiPlainTextFile; + } + @Override public void fillCompletionVariants(@NotNull final CompletionParameters parameters, @NotNull final CompletionResultSet result) { if (parameters.getCompletionType() == CompletionType.BASIC && shouldPerformWordCompletion(parameters)) { @@ -54,7 +63,7 @@ public class WordCompletionContributor extends CompletionContributor implements public static void addWordCompletionVariants(CompletionResultSet result, final CompletionParameters parameters, Set excludes) { final Set realExcludes = new HashSet<>(excludes); for (String exclude : excludes) { - String[] words = exclude.split("[ \\.-]"); + String[] words = exclude.split("[ .-]"); if (words.length > 0 && StringUtil.isNotEmpty(words[0])) { realExcludes.add(words[0]); } @@ -120,19 +129,16 @@ public class WordCompletionContributor extends CompletionContributor implements } private static boolean shouldPerformWordCompletion(CompletionParameters parameters) { - final PsiElement insertedElement = parameters.getPosition(); - final boolean dumb = DumbService.getInstance(insertedElement.getProject()).isDumb(); - if (dumb) { - return true; - } - if (parameters.getInvocationCount() == 0) { return false; } + PsiElement insertedElement = parameters.getPosition(); + PsiFile file = insertedElement.getContainingFile(); + if (isWordCompletionDefinitelyEnabled(file)) { + return true; + } - - final PsiFile file = insertedElement.getContainingFile(); final CompletionData data = CompletionUtil.getCompletionDataByElement(insertedElement, file); if (data != null) { Set toAdd = new HashSet<>(); @@ -155,8 +161,7 @@ public class WordCompletionContributor extends CompletionContributor implements ASTNode textContainer = element != null ? element.getNode() : null; while (textContainer != null) { - final IElementType elementType = textContainer.getElementType(); - if (LanguageWordCompletion.INSTANCE.isEnabledIn(elementType) || elementType == PlainTextTokenTypes.PLAIN_TEXT) { + if (LanguageWordCompletion.INSTANCE.isEnabledIn(textContainer.getElementType())) { return true; } textContainer = textContainer.getTreeParent(); @@ -164,19 +169,16 @@ public class WordCompletionContributor extends CompletionContributor implements return false; } - public static Set getAllWords(final PsiElement context, final int offset) { + private static Set getAllWords(PsiElement context, int offset) { final Set words = new LinkedHashSet<>(); if (StringUtil.isEmpty(CompletionUtil.findJavaIdentifierPrefix(context, offset))) { return words; } final CharSequence chars = context.getContainingFile().getViewProvider().getContents(); // ?? - IdTableBuilding.scanWords(new IdTableBuilding.ScanWordProcessor() { - @Override - public void run(final CharSequence chars, @Nullable char[] charsArray, final int start, final int end) { - if (start > offset || offset > end) { - words.add(chars.subSequence(start, end).toString()); - } + IdTableBuilding.scanWords((chars1, charsArray, start, end) -> { + if (start > offset || offset > end) { + words.add(chars1.subSequence(start, end).toString()); } }, chars, 0, chars.length()); return words;