Add API for handling selected item in completion list

This commit is contained in:
Dmitry Jemerov
2018-04-18 16:19:57 +02:00
parent 49875a9c61
commit ea9d4c15ba
6 changed files with 102 additions and 28 deletions
@@ -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<LookupElement> 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<LookupElement> 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;
@@ -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
@@ -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<Disposable> child);
void itemSelected(LookupElement item, char aChar);
@@ -626,7 +626,7 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement
}
@NotNull
Caret getCaret() {
public Caret getCaret() {
return myCaret;
}
@@ -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--
@@ -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);
}