From 65404f7c75b5ca7a054089c82f4a83c5cc580402 Mon Sep 17 00:00:00 2001 From: Dmitry Batrak Date: Thu, 24 Jul 2014 12:49:42 +0400 Subject: [PATCH] IDEA-123396 Multiple carets: offer completion from other caret, if primary caret has no completion variants --- .../JavaPostfixTemplateProvider.java | 2 +- ...lticaretCompletionFromNonPrimaryCaret.java | 4 ++ .../completion/NormalCompletionTest.groovy | 5 ++ .../CompletionInitializationContext.java | 22 ++++----- .../completion/CodeCompletionHandlerBase.java | 47 ++++++++++++++++--- .../codeInsight/lookup/impl/LookupImpl.java | 2 +- 6 files changed, 62 insertions(+), 20 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/completion/normal/MulticaretCompletionFromNonPrimaryCaret.java diff --git a/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/JavaPostfixTemplateProvider.java b/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/JavaPostfixTemplateProvider.java index 148f4bb187db..629d4dd02115 100644 --- a/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/JavaPostfixTemplateProvider.java +++ b/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/JavaPostfixTemplateProvider.java @@ -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())); } } diff --git a/java/java-tests/testData/codeInsight/completion/normal/MulticaretCompletionFromNonPrimaryCaret.java b/java/java-tests/testData/codeInsight/completion/normal/MulticaretCompletionFromNonPrimaryCaret.java new file mode 100644 index 000000000000..077ff80bd5c7 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/MulticaretCompletionFromNonPrimaryCaret.java @@ -0,0 +1,4 @@ +class Foo {{ + System. + System. +}} diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy index 77b4c0cc64a7..f5fc39041106 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy +++ b/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy @@ -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", """ diff --git a/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionInitializationContext.java b/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionInitializationContext.java index 624d60fd8688..2c152b595421 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionInitializationContext.java +++ b/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionInitializationContext.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) { 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 e0a3be5f423d..fcb080dcd277 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java @@ -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 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 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; + } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java index 3a1f04b80414..679c8f46f620 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java @@ -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,