diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java index 94563bfb211f..653b884dfe35 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java @@ -46,6 +46,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -385,25 +386,39 @@ public class CodeCompletionHandlerBase { WatchingInsertionContext context = null; try { StatisticsUpdate update = StatisticsUpdate.collectStatisticChanges(item); - context = insertItemHonorBlockSelection(indicator, item, completionChar, items, update); + context = insertItemHonorBlockSelection(indicator, item, completionChar, update); update.trackStatistics(context); } finally { afterItemInsertion(indicator, context == null ? null : context.getLaterRunnable()); } - } - private static WatchingInsertionContext insertItemHonorBlockSelection(CompletionProgressIndicator indicator, + public void handleCompletionElementSelected(CompletionParameters parameters, + @NotNull LookupElement item, + char completionChar) { + WatchingInsertionContext context = null; + try { + StatisticsUpdate update = StatisticsUpdate.collectStatisticChanges(item); + context = insertItemHonorBlockSelection((CompletionProcessEx) parameters.getProcess(), item, completionChar, update); + update.trackStatistics(context); + } + finally { + if (context != null && context.getLaterRunnable() != null) { + context.getLaterRunnable().run(); + } + } + } + + private static WatchingInsertionContext insertItemHonorBlockSelection(CompletionProcessEx indicator, LookupElement item, char completionChar, - List items, StatisticsUpdate update) { final Editor editor = indicator.getEditor(); final int caretOffset = indicator.getCaret().getOffset(); final int idEndOffset = indicator.getOffsetMap().containsOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET) ? - indicator.getIdentifierEndOffset() : + indicator.getOffsetMap().getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET) : CompletionInitializationContext.calcDefaultIdentifierEnd(editor, caretOffset); final int idEndOffsetDelta = idEndOffset - caretOffset; @@ -424,7 +439,7 @@ public class CodeCompletionHandlerBase { if (idEnd > targetEditor.getDocument().getTextLength()) { idEnd = targetCaretOffset; // no replacement by Tab when offsets gone wrong for some reason } - WatchingInsertionContext currentContext = insertItem(indicator, item, completionChar, items, update, + WatchingInsertionContext currentContext = insertItem(indicator.getLookup(), item, completionChar, update, targetEditor, targetFile, targetCaretOffset, idEnd, targetOffsets.getOffsets()); @@ -433,18 +448,19 @@ public class CodeCompletionHandlerBase { private OffsetsInFile findInjectedOffsetsIfAny(Caret caret) { if (!wasInjected) return topLevelOffsets; - - PsiDocumentManager.getInstance(indicator.getProject()).commitDocument(hostEditor.getDocument()); + + PsiDocumentManager.getInstance(topLevelOffsets.getFile().getProject()).commitDocument(hostEditor.getDocument()); return topLevelOffsets.toInjectedIfAny(caret.getOffset()); } }); context = lastContext.get(); } else { - context = insertItem(indicator, item, completionChar, items, update, editor, PsiUtilBase.getPsiFileInEditor(editor, indicator.getProject()), caretOffset, + PsiFile psiFile = PsiUtilBase.getPsiFileInEditor(editor, indicator.getProject()); + context = insertItem(indicator.getLookup(), item, completionChar, update, editor, psiFile, caretOffset, idEndOffset, indicator.getOffsetMap()); } if (context.shouldAddCompletionChar()) { - WriteAction.run(() -> addCompletionChar(context, item, editor, indicator, completionChar)); + WriteAction.run(() -> addCompletionChar(context, item, editor, completionChar)); } return context; } @@ -470,15 +486,15 @@ public class CodeCompletionHandlerBase { } } - private static WatchingInsertionContext insertItem(final CompletionProgressIndicator indicator, - final LookupElement item, - final char completionChar, - List items, - final StatisticsUpdate update, - final Editor editor, - final PsiFile psiFile, - final int caretOffset, - final int idEndOffset, final OffsetMap offsetMap) { + private static WatchingInsertionContext insertItem(@Nullable final Lookup lookup, + final LookupElement item, + final char completionChar, + final StatisticsUpdate update, + final Editor editor, + final PsiFile psiFile, + final int caretOffset, + final int idEndOffset, + final OffsetMap offsetMap) { editor.getCaretModel().moveToOffset(caretOffset); int initialStartOffset = Math.max(0, caretOffset - item.getLookupString().length()); @@ -486,7 +502,9 @@ public class CodeCompletionHandlerBase { offsetMap.addOffset(CompletionInitializationContext.SELECTION_END_OFFSET, caretOffset); offsetMap.addOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET, idEndOffset); - WatchingInsertionContext context = new WatchingInsertionContext(offsetMap, psiFile, completionChar, items, editor); + WatchingInsertionContext context = new WatchingInsertionContext(offsetMap, psiFile, completionChar, + lookup != null ? lookup.getItems() : Collections.emptyList(), + editor); ApplicationManager.getApplication().runWriteAction(() -> { try { if (caretOffset < idEndOffset && completionChar == Lookup.REPLACE_SELECT_CHAR) { @@ -496,7 +514,7 @@ public class CodeCompletionHandlerBase { assert context.getStartOffset() >= 0 : "stale startOffset: was " + initialStartOffset + "; selEnd=" + caretOffset + "; idEnd=" + idEndOffset + "; file=" + context.getFile(); assert context.getTailOffset() >= 0 : "stale tail: was " + initialStartOffset + "; selEnd=" + caretOffset + "; idEnd=" + idEndOffset + "; file=" + context.getFile(); - Project project = indicator.getProject(); + Project project = psiFile.getProject(); if (item.requiresCommittedDocuments()) { PsiDocumentManager.getInstance(project).commitAllDocuments(); } @@ -509,13 +527,15 @@ public class CodeCompletionHandlerBase { EditorModificationUtil.scrollToCaret(editor); }); - update.addSparedChars(indicator, item, context); + if (lookup != null) { + update.addSparedChars(lookup, item, context); + } return context; } private static void addCompletionChar(WatchingInsertionContext context, LookupElement item, - Editor editor, CompletionProgressIndicator indicator, char completionChar) { + Editor editor, char completionChar) { if (!context.getOffsetMap().containsOffset(InsertionContext.TAIL_OFFSET)) { LOG.info("tailOffset<0 after inserting " + item + " of " + item.getClass() + "; invalidated at: " + context.invalidateTrace + "\n--------"); } @@ -526,7 +546,7 @@ public class CodeCompletionHandlerBase { context.getEditor().getCaretModel().moveToOffset(context.getTailOffset()); } if (context.getCompletionChar() == Lookup.COMPLETE_STATEMENT_SELECT_CHAR) { - Language language = PsiUtilBase.getLanguageInEditor(editor, indicator.getProject()); + Language language = PsiUtilBase.getLanguageInEditor(editor, context.getFile().getProject()); if (language != null) { for (SmartEnterProcessor processor : SmartEnterProcessors.INSTANCE.allForLanguage(language)) { if (processor.processAfterCompletion(editor, context.getFile())) break; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProcessBase.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProcessBase.java index 452f5c468bb0..30d3526a9b77 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProcessBase.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProcessBase.java @@ -1,8 +1,12 @@ // Copyright 2000-2018 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.Lookup; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.openapi.Disposable; +import com.intellij.openapi.editor.Caret; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; import com.intellij.patterns.ElementPattern; import org.jetbrains.annotations.NotNull; @@ -19,10 +23,40 @@ public class CompletionProcessBase implements CompletionProcessEx, Disposable { protected final Object myLock = new String("CompletionProgressIndicator"); protected OffsetsInFile myHostOffsets; private CompletionParameters myParameters; + private final Caret myCaret; + private final OffsetMap myOffsetMap; public CompletionProcessBase(CompletionInitializationContext context) { myInvocationCount = context.getInvocationCount(); myHostOffsets = ((CompletionInitializationContextImpl) context).getHostOffsets(); + myCaret = context.getCaret(); + myOffsetMap = context.getOffsetMap(); + } + + @Override + public Project getProject() { + return myParameters.getOriginalFile().getProject(); + } + + @Override + public Editor getEditor() { + return myParameters.getEditor(); + } + + @Override + public Caret getCaret() { + return myCaret; + } + + @Override + public OffsetMap getOffsetMap() { + return myOffsetMap; + } + + @Nullable + @Override + public Lookup getLookup() { + return null; } @Override diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProcessEx.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProcessEx.java index ba2301c2b4c8..01a719579051 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProcessEx.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProcessEx.java @@ -1,8 +1,12 @@ // Copyright 2000-2018 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.Lookup; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.openapi.Disposable; +import com.intellij.openapi.editor.Caret; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; import com.intellij.patterns.ElementPattern; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -14,7 +18,15 @@ import java.util.function.Supplier; * @author yole */ interface CompletionProcessEx extends CompletionProcess { + Project getProject(); + Editor getEditor(); + Caret getCaret(); + OffsetMap getOffsetMap(); OffsetsInFile getHostOffsets(); + + @Nullable + Lookup getLookup(); + void registerChildDisposable(@NotNull Supplier child); void itemSelected(LookupElement item, char aChar); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java index aca227d4e6e9..7a29c79a6bc6 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java @@ -626,7 +626,7 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement } @NotNull - Caret getCaret() { + public Caret getCaret() { return myCaret; } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/StatisticsUpdate.kt b/platform/lang-impl/src/com/intellij/codeInsight/completion/StatisticsUpdate.kt index 1b23d95913f4..55c61129fd54 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/StatisticsUpdate.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/StatisticsUpdate.kt @@ -1,7 +1,8 @@ -// Copyright 2000-2017 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. +// Copyright 2000-2018 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.google.common.annotations.VisibleForTesting +import com.intellij.codeInsight.lookup.Lookup import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupEvent import com.intellij.featureStatistics.FeatureUsageTracker @@ -24,7 +25,7 @@ class StatisticsUpdate override fun dispose() {} - fun addSparedChars(indicator: CompletionProgressIndicator, item: LookupElement, context: InsertionContext) { + fun addSparedChars(lookup: Lookup, item: LookupElement, context: InsertionContext) { val textInserted: String if (context.offsetMap.containsOffset(CompletionInitializationContext.START_OFFSET) && context.offsetMap.containsOffset(InsertionContext.TAIL_OFFSET) && @@ -35,7 +36,7 @@ class StatisticsUpdate textInserted = item.lookupString } val withoutSpaces = StringUtil.replace(textInserted, listOf(" ", "\t", "\n"), listOf("", "", "")) - var spared = withoutSpaces.length - indicator.lookup.itemPattern(item).length + var spared = withoutSpaces.length - lookup.itemPattern(item).length val completionChar = context.completionChar if (!LookupEvent.isSpecialCompletionChar(completionChar) && withoutSpaces.contains(completionChar.toString())) { spared-- 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 647a9eb59ee6..1130bdc840c6 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 @@ -287,6 +287,13 @@ public final class CompletionServiceImpl extends CompletionService { return new CompletionLookupArrangerImpl(parameters); } + @SuppressWarnings("unused") + public void handleCompletionItemSelected(CompletionParameters parameters, LookupElement lookupElement, char completionChar) { + CodeCompletionHandlerBase handler = + CodeCompletionHandlerBase.createHandler(parameters.getCompletionType(), true, parameters.isAutoPopup(), true); + handler.handleCompletionElementSelected(parameters, lookupElement, completionChar); + } + public static boolean isStartMatch(LookupElement element, WeighingContext context) { return getItemMatcher(element, context).isStartMatch(element); }