IDEA-123396 Multiple carets: offer completion from other caret, if primary caret has no completion variants

This commit is contained in:
Dmitry Batrak
2014-07-24 12:50:28 +04:00
parent 9410d3c862
commit 65404f7c75
6 changed files with 62 additions and 20 deletions
@@ -144,6 +144,6 @@ public class JavaPostfixTemplateProvider implements PostfixTemplateProvider {
}
private static boolean isSemicolonNeeded(@NotNull PsiFile file, @NotNull Editor editor) {
return JavaCompletionContributor.semicolonNeeded(editor, file, CompletionInitializationContext.calcStartOffset(editor));
return JavaCompletionContributor.semicolonNeeded(editor, file, CompletionInitializationContext.calcStartOffset(editor.getCaretModel().getCurrentCaret()));
}
}
@@ -0,0 +1,4 @@
class Foo {{
System.<caret>
System.<caret>
}}
@@ -1423,6 +1423,11 @@ class XInternalError {}
checkResult()
}
public void testMulticaretCompletionFromNonPrimaryCaret() {
configure()
myFixture.assertPreferredCompletionItems(0, "arraycopy")
}
public void "test complete lowercase class name"() {
myFixture.addClass("package foo; public class myClass {}")
myFixture.configureByText "a.java", """
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2010 JetBrains s.r.o.
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,8 +16,8 @@
package com.intellij.codeInsight.completion;
import com.intellij.lang.Language;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiUtilBase;
@@ -45,26 +45,24 @@ public class CompletionInitializationContext {
private final OffsetMap myOffsetMap;
private String myDummyIdentifier = DUMMY_IDENTIFIER;
public CompletionInitializationContext(final Editor editor, final PsiFile file, final CompletionType completionType, int invocationCount) {
public CompletionInitializationContext(final Editor editor, final Caret caret, final PsiFile file, final CompletionType completionType, int invocationCount) {
myEditor = editor;
myFile = file;
myCompletionType = completionType;
myInvocationCount = invocationCount;
myOffsetMap = new OffsetMap(editor.getDocument());
myOffsetMap.addOffset(START_OFFSET, calcStartOffset(editor));
myOffsetMap.addOffset(SELECTION_END_OFFSET, calcSelectionEnd(editor));
myOffsetMap.addOffset(IDENTIFIER_END_OFFSET, calcDefaultIdentifierEnd(editor, calcSelectionEnd(editor)));
myOffsetMap.addOffset(START_OFFSET, calcStartOffset(caret));
myOffsetMap.addOffset(SELECTION_END_OFFSET, calcSelectionEnd(caret));
myOffsetMap.addOffset(IDENTIFIER_END_OFFSET, calcDefaultIdentifierEnd(editor, calcSelectionEnd(caret)));
}
private static int calcSelectionEnd(Editor editor) {
final SelectionModel selectionModel = editor.getSelectionModel();
return selectionModel.hasSelection() ? selectionModel.getSelectionEnd() : editor.getCaretModel().getOffset();
private static int calcSelectionEnd(Caret caret) {
return caret.hasSelection() ? caret.getSelectionEnd() : caret.getOffset();
}
public static int calcStartOffset(Editor editor) {
final SelectionModel selectionModel = editor.getSelectionModel();
return selectionModel.hasSelection() ? selectionModel.getSelectionStart() : editor.getCaretModel().getOffset();
public static int calcStartOffset(Caret caret) {
return caret.hasSelection() ? caret.getSelectionStart() : caret.getOffset();
}
static int calcDefaultIdentifierEnd(Editor editor, int startFrom) {
@@ -71,6 +71,8 @@ import java.util.concurrent.atomic.AtomicReference;
public class CodeCompletionHandlerBase {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.CodeCompletionHandlerBase");
private static final Key<Boolean> CARET_PROCESSED = Key.create("CodeCompletionHandlerBase.caretProcessed");
@NotNull private final CompletionType myCompletionType;
final boolean invokedExplicitly;
final boolean synchronous;
@@ -108,6 +110,13 @@ public class CodeCompletionHandlerBase {
}
public final void invokeCompletion(@NotNull final Project project, @NotNull final Editor editor, int time, boolean hasModifiers, boolean restarted) {
clearCaretMarkers(editor);
invokeCompletion(project, editor, time, hasModifiers, restarted, editor.getCaretModel().getPrimaryCaret());
}
public final void invokeCompletion(@NotNull final Project project, @NotNull final Editor editor, int time, boolean hasModifiers, boolean restarted, @NotNull final Caret caret) {
markCaretAsProcessed(caret);
if (invokedExplicitly) {
CompletionLookupArranger.applyLastCompletionStatisticsUpdate();
}
@@ -162,12 +171,12 @@ public class CodeCompletionHandlerBase {
PsiDocumentManager.getInstance(project).commitAllDocuments();
CompletionAssertions.checkEditorValid(editor);
final PsiFile psiFile = PsiUtilBase.getPsiFileInEditor(editor, project);
final PsiFile psiFile = PsiUtilBase.getPsiFileInEditor(caret, project);
assert psiFile != null : "no PSI file: " + FileDocumentManager.getInstance().getFile(editor.getDocument());
psiFile.putUserData(PsiFileEx.BATCH_REFERENCE_PROCESSING, Boolean.TRUE);
CompletionAssertions.assertCommitSuccessful(editor, psiFile);
initializationContext[0] = runContributorsBeforeCompletion(editor, psiFile, invocationCount);
initializationContext[0] = runContributorsBeforeCompletion(editor, psiFile, invocationCount, caret);
}
};
ApplicationManager.getApplication().runWriteAction(runnable);
@@ -186,9 +195,9 @@ public class CodeCompletionHandlerBase {
insertDummyIdentifier(initializationContext[0], hasModifiers, invocationCount);
}
private CompletionInitializationContext runContributorsBeforeCompletion(Editor editor, PsiFile psiFile, int invocationCount) {
private CompletionInitializationContext runContributorsBeforeCompletion(Editor editor, PsiFile psiFile, int invocationCount, Caret caret) {
final Ref<CompletionContributor> current = Ref.create(null);
CompletionInitializationContext context = new CompletionInitializationContext(editor, psiFile, myCompletionType, invocationCount) {
CompletionInitializationContext context = new CompletionInitializationContext(editor, caret, psiFile, myCompletionType, invocationCount) {
CompletionContributor dummyIdentifierChanger;
@Override
@@ -400,8 +409,15 @@ public class CodeCompletionHandlerBase {
final LookupElement[] items, boolean hasModifiers) {
if (items.length == 0) {
LookupManager.getInstance(indicator.getProject()).hideActiveLookup();
indicator.handleEmptyLookup(true);
checkNotSync(indicator, items);
Caret nextCaret = getNextCaretToProcess(indicator.getEditor());
if (nextCaret != null) {
invokeCompletion(indicator.getProject(), indicator.getEditor(), indicator.getParameters().getInvocationCount(), hasModifiers, false, nextCaret);
}
else {
indicator.handleEmptyLookup(true);
checkNotSync(indicator, items);
}
return;
}
@@ -866,4 +882,23 @@ public class CodeCompletionHandlerBase {
}
};
}
private static void clearCaretMarkers(@NotNull Editor editor) {
for (Caret caret : editor.getCaretModel().getAllCarets()) {
caret.putUserData(CARET_PROCESSED, null);
}
}
private static void markCaretAsProcessed(@NotNull Caret caret) {
caret.putUserData(CARET_PROCESSED, Boolean.TRUE);
}
private static Caret getNextCaretToProcess(@NotNull Editor editor) {
for (Caret caret : editor.getCaretModel().getAllCarets()) {
if (caret.getUserData(CARET_PROCESSED) == null) {
return caret;
}
}
return null;
}
}
@@ -566,7 +566,7 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable,
public void perform(Caret caret) {
EditorModificationUtil.deleteSelectedText(hostEditor);
final int caretOffset = hostEditor.getCaretModel().getOffset();
int lookupStart = caretOffset - prefix;
int lookupStart = Math.max(caretOffset - prefix, 0);
int len = hostEditor.getDocument().getTextLength();
LOG.assertTrue(lookupStart >= 0 && lookupStart <= len,