mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-20 13:31:28 +07:00
API for handling selected completion item actually works; added test
IDEA-CR-31904
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
class A extends Base{
|
||||
int _field;
|
||||
void _method(){}
|
||||
|
||||
void foo(){
|
||||
int _local1;
|
||||
int _local2;
|
||||
_field
|
||||
int _local3;
|
||||
}
|
||||
}
|
||||
|
||||
class Base{
|
||||
void _baseMethod(){}
|
||||
int _baseField;
|
||||
|
||||
private void privateMethod(){}
|
||||
private int privateField;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// 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.java.codeInsight.completion;
|
||||
|
||||
import com.intellij.JavaTestUtil;
|
||||
import com.intellij.codeInsight.completion.CompletionLookupArranger;
|
||||
import com.intellij.codeInsight.completion.CompletionParameters;
|
||||
import com.intellij.codeInsight.completion.CompletionService;
|
||||
import com.intellij.codeInsight.completion.CompletionType;
|
||||
import com.intellij.codeInsight.completion.impl.CompletionServiceImpl;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.openapi.command.WriteCommandAction;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public class CompletionServiceTest extends LightCodeInsightFixtureTestCase {
|
||||
@Override
|
||||
protected String getBasePath() {
|
||||
return JavaTestUtil.getRelativeJavaTestDataPath() + "/codeInsight/completion/normal/";
|
||||
}
|
||||
|
||||
public void testCompletionServiceSimple() {
|
||||
myFixture.configureByFile("Simple.java");
|
||||
CompletionServiceImpl service = (CompletionServiceImpl)CompletionService.getCompletionService();
|
||||
CompletionParameters parameters = service.createCompletionParameters(getProject(),
|
||||
myFixture.getEditor(),
|
||||
myFixture.getEditor().getCaretModel().getPrimaryCaret(),
|
||||
1,
|
||||
CompletionType.BASIC);
|
||||
CompletionLookupArranger arranger = service.createLookupArranger(parameters);
|
||||
service.performCompletion(parameters, result -> arranger.addElement(result));
|
||||
Pair<List<LookupElement>, Integer> items = arranger.arrangeItems();
|
||||
LookupElement element = ContainerUtil.find(items.first, item -> item.getLookupString().equals("_field"));
|
||||
WriteCommandAction.runWriteCommandAction(getProject(), () -> service.handleCompletionItemSelected(parameters, element, arranger.itemMatcher(element), '\n'));
|
||||
myFixture.checkResultByFile("Simple_afterCompletionService.java");
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ class CompletionInitializationContextImpl extends CompletionInitializationContex
|
||||
myHostOffsets = new OffsetsInFile(file, getOffsetMap()).toTopLevelFile();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
OffsetsInFile getHostOffsets() {
|
||||
return myHostOffsets;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ package com.intellij.codeInsight.completion;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.codeInsight.lookup.LookupElementPresentation;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -17,10 +18,20 @@ public interface CompletionLookupArranger {
|
||||
* Adds an element to be arranged.
|
||||
* @param presentation The presentation of the element (rendered with {@link LookupElement#renderElement(LookupElementPresentation)}
|
||||
*/
|
||||
void addElement(LookupElement element,
|
||||
CompletionSorter sorter,
|
||||
PrefixMatcher prefixMatcher,
|
||||
LookupElementPresentation presentation);
|
||||
void addElement(@NotNull LookupElement element,
|
||||
@NotNull CompletionSorter sorter,
|
||||
@NotNull PrefixMatcher prefixMatcher,
|
||||
@NotNull LookupElementPresentation presentation);
|
||||
|
||||
/**
|
||||
* Adds an element to be arranged, along with its prefix matcher.
|
||||
*/
|
||||
void addElement(@NotNull CompletionResult result);
|
||||
|
||||
/**
|
||||
* Returns the prefix matcher registered for the specified element.
|
||||
*/
|
||||
PrefixMatcher itemMatcher(@NotNull LookupElement item);
|
||||
|
||||
/**
|
||||
* Returns the items in the appropriate order and the initial selection.
|
||||
|
||||
@@ -137,15 +137,22 @@ public class CompletionLookupArrangerImpl extends LookupArranger implements Comp
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addElement(LookupElement element,
|
||||
CompletionSorter sorter,
|
||||
PrefixMatcher prefixMatcher,
|
||||
LookupElementPresentation presentation) {
|
||||
public void addElement(@NotNull LookupElement element,
|
||||
@NotNull CompletionSorter sorter,
|
||||
@NotNull PrefixMatcher prefixMatcher,
|
||||
@NotNull LookupElementPresentation presentation) {
|
||||
registerMatcher(element, prefixMatcher);
|
||||
associateSorter(element, (CompletionSorterImpl)sorter);
|
||||
addElement(element, presentation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addElement(@NotNull CompletionResult result) {
|
||||
LookupElementPresentation presentation = new LookupElementPresentation();
|
||||
result.getLookupElement().renderElement(presentation);
|
||||
addElement(result.getLookupElement(), result.getSorter(), result.getPrefixMatcher(), presentation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addElement(LookupElement element, LookupElementPresentation presentation) {
|
||||
StatisticsWeigher.clearBaseStatisticsInfo(element);
|
||||
|
||||
@@ -23,7 +23,9 @@ public class CompletionProcessBase implements CompletionProcessEx, Disposable {
|
||||
protected final Object myLock = new String("CompletionProgressIndicator");
|
||||
protected OffsetsInFile myHostOffsets;
|
||||
private CompletionParameters myParameters;
|
||||
@NotNull
|
||||
private final Caret myCaret;
|
||||
@NotNull
|
||||
private final OffsetMap myOffsetMap;
|
||||
|
||||
public CompletionProcessBase(CompletionInitializationContext context) {
|
||||
@@ -33,21 +35,25 @@ public class CompletionProcessBase implements CompletionProcessEx, Disposable {
|
||||
myOffsetMap = context.getOffsetMap();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Project getProject() {
|
||||
return myParameters.getOriginalFile().getProject();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Editor getEditor() {
|
||||
return myParameters.getEditor();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Caret getCaret() {
|
||||
return myCaret;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public OffsetMap getOffsetMap() {
|
||||
return myOffsetMap;
|
||||
@@ -64,6 +70,7 @@ public class CompletionProcessBase implements CompletionProcessEx, Disposable {
|
||||
return myInvocationCount == 0;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public OffsetsInFile getHostOffsets() {
|
||||
return myHostOffsets;
|
||||
|
||||
@@ -18,10 +18,19 @@ import java.util.function.Supplier;
|
||||
* @author yole
|
||||
*/
|
||||
interface CompletionProcessEx extends CompletionProcess {
|
||||
@NotNull
|
||||
Project getProject();
|
||||
|
||||
@NotNull
|
||||
Editor getEditor();
|
||||
|
||||
@NotNull
|
||||
Caret getCaret();
|
||||
|
||||
@NotNull
|
||||
OffsetMap getOffsetMap();
|
||||
|
||||
@NotNull
|
||||
OffsetsInFile getHostOffsets();
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -98,7 +98,7 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement
|
||||
};
|
||||
private final Semaphore myFreezeSemaphore = new Semaphore(1);
|
||||
private final Semaphore myFinishSemaphore = new Semaphore(1);
|
||||
private final OffsetMap myOffsetMap;
|
||||
@NotNull private final OffsetMap myOffsetMap;
|
||||
private final Set<Pair<Integer, ElementPattern<String>>> myRestartingPrefixConditions = ContainerUtil.newConcurrentSet();
|
||||
private final LookupAdapter myLookupListener = new LookupAdapter() {
|
||||
@Override
|
||||
@@ -125,7 +125,7 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement
|
||||
private final Object myLock = new String("CompletionProgressIndicator");
|
||||
|
||||
CompletionProgressIndicator(Editor editor, @NotNull Caret caret, int invocationCount,
|
||||
CodeCompletionHandlerBase handler, OffsetMap offsetMap, OffsetsInFile hostOffsets,
|
||||
CodeCompletionHandlerBase handler, @NotNull OffsetMap offsetMap, @NotNull OffsetsInFile hostOffsets,
|
||||
boolean hasModifiers, LookupImpl lookup) {
|
||||
myEditor = editor;
|
||||
myCaret = caret;
|
||||
@@ -174,11 +174,13 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement
|
||||
myHandler.lookupItemSelected(this, lookupItem, completionChar, myLookup.getItems());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public OffsetMap getOffsetMap() {
|
||||
return myOffsetMap;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public OffsetsInFile getHostOffsets() {
|
||||
return myHostOffsets;
|
||||
@@ -621,10 +623,12 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement
|
||||
return reused ? Math.max(myInvocationCount + 1, 2) : invocation;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Editor getEditor() {
|
||||
return myEditor;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Caret getCaret() {
|
||||
return myCaret;
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.intellij.codeInsight.completion.impl;
|
||||
|
||||
import com.intellij.codeInsight.completion.*;
|
||||
import com.intellij.codeInsight.lookup.*;
|
||||
import com.intellij.codeInsight.lookup.impl.LookupImpl;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Caret;
|
||||
@@ -288,7 +289,15 @@ public final class CompletionServiceImpl extends CompletionService {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void handleCompletionItemSelected(CompletionParameters parameters, LookupElement lookupElement, char completionChar) {
|
||||
public void handleCompletionItemSelected(CompletionParameters parameters,
|
||||
LookupElement lookupElement,
|
||||
PrefixMatcher prefixMatcher,
|
||||
char completionChar) {
|
||||
|
||||
LookupImpl.insertLookupString(parameters.getPosition().getProject(),
|
||||
parameters.getEditor(),
|
||||
lookupElement,
|
||||
prefixMatcher, prefixMatcher.getPrefix(), prefixMatcher.getPrefix().length());
|
||||
CodeCompletionHandlerBase handler =
|
||||
CodeCompletionHandlerBase.createHandler(parameters.getCompletionType(), true, parameters.isAutoPopup(), true);
|
||||
handler.handleCompletionElementSelected(parameters, lookupElement, completionChar);
|
||||
|
||||
@@ -527,40 +527,49 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable,
|
||||
}
|
||||
|
||||
protected void insertLookupString(LookupElement item, final int prefix) {
|
||||
final String lookupString = getCaseCorrectedLookupString(item);
|
||||
insertLookupString(myProject, getTopLevelEditor(), item, itemMatcher(item), itemPattern(item), prefix);
|
||||
}
|
||||
|
||||
final Editor hostEditor = getTopLevelEditor();
|
||||
public static void insertLookupString(final Project project,
|
||||
Editor editor, LookupElement item,
|
||||
PrefixMatcher matcher, String itemPattern, final int prefixLength) {
|
||||
final String lookupString = getCaseCorrectedLookupString(item, matcher, itemPattern);
|
||||
|
||||
final Editor hostEditor = editor;
|
||||
hostEditor.getCaretModel().runForEachCaret(new CaretAction() {
|
||||
@Override
|
||||
public void perform(Caret caret) {
|
||||
EditorModificationUtil.deleteSelectedText(hostEditor);
|
||||
final int caretOffset = hostEditor.getCaretModel().getOffset();
|
||||
|
||||
int offset = insertLookupInDocumentWindowIfNeeded(caretOffset, prefix, lookupString);
|
||||
int offset = insertLookupInDocumentWindowIfNeeded(project, editor, caretOffset, prefixLength, lookupString);
|
||||
hostEditor.getCaretModel().moveToOffset(offset);
|
||||
hostEditor.getSelectionModel().removeSelection();
|
||||
}
|
||||
});
|
||||
|
||||
myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
|
||||
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
|
||||
}
|
||||
|
||||
private int insertLookupInDocumentWindowIfNeeded(int caretOffset, int prefix, String lookupString) {
|
||||
DocumentWindow document = getInjectedDocument(caretOffset);
|
||||
if (document == null) return insertLookupInDocument(caretOffset, myEditor.getDocument(), prefix, lookupString);
|
||||
PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(document);
|
||||
private static int insertLookupInDocumentWindowIfNeeded(Project project,
|
||||
Editor editor, int caretOffset,
|
||||
int prefix,
|
||||
String lookupString) {
|
||||
DocumentWindow document = getInjectedDocument(project, editor, caretOffset);
|
||||
if (document == null) return insertLookupInDocument(caretOffset, editor.getDocument(), prefix, lookupString);
|
||||
PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(document);
|
||||
int offset = document.hostToInjected(caretOffset);
|
||||
int lookupStart = Math.min(offset, Math.max(offset - prefix, 0));
|
||||
int diff = -1;
|
||||
if (file != null) {
|
||||
List<TextRange> ranges = InjectedLanguageManager.getInstance(myProject)
|
||||
List<TextRange> ranges = InjectedLanguageManager.getInstance(project)
|
||||
.intersectWithAllEditableFragments(file, TextRange.create(lookupStart, offset));
|
||||
if (!ranges.isEmpty()) {
|
||||
diff = ranges.get(0).getStartOffset() - lookupStart;
|
||||
if (ranges.size() == 1 && diff == 0) diff = -1;
|
||||
}
|
||||
}
|
||||
if (diff == -1) return insertLookupInDocument(caretOffset, myEditor.getDocument(), prefix, lookupString);
|
||||
if (diff == -1) return insertLookupInDocument(caretOffset, editor.getDocument(), prefix, lookupString);
|
||||
return document.injectedToHost(
|
||||
insertLookupInDocument(offset, document, prefix - diff, diff == 0 ? lookupString : lookupString.substring(diff))
|
||||
);
|
||||
@@ -576,15 +585,14 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable,
|
||||
return lookupStart + lookupString.length();
|
||||
}
|
||||
|
||||
private String getCaseCorrectedLookupString(LookupElement item) {
|
||||
private static String getCaseCorrectedLookupString(LookupElement item, PrefixMatcher prefixMatcher, String prefix) {
|
||||
String lookupString = item.getLookupString();
|
||||
if (item.isCaseSensitive()) {
|
||||
return lookupString;
|
||||
}
|
||||
|
||||
final String prefix = itemPattern(item);
|
||||
final int length = prefix.length();
|
||||
if (length == 0 || !itemMatcher(item).prefixMatches(prefix)) return lookupString;
|
||||
if (length == 0 || !prefixMatcher.prefixMatches(prefix)) return lookupString;
|
||||
boolean isAllLower = true;
|
||||
boolean isAllUpper = true;
|
||||
boolean sameCase = true;
|
||||
@@ -872,67 +880,6 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable,
|
||||
myPreview.updatePreview(currentItem);
|
||||
}
|
||||
|
||||
public boolean fillInCommonPrefix(boolean explicitlyInvoked) {
|
||||
if (explicitlyInvoked) {
|
||||
setFocusDegree(FocusDegree.FOCUSED);
|
||||
}
|
||||
|
||||
if (explicitlyInvoked && myCalculating) return false;
|
||||
if (!explicitlyInvoked && mySelectionTouched) return false;
|
||||
|
||||
ListModel listModel = getListModel();
|
||||
if (listModel.getSize() <= 1) return false;
|
||||
|
||||
if (listModel.getSize() == 0) return false;
|
||||
|
||||
final LookupElement firstItem = (LookupElement)listModel.getElementAt(0);
|
||||
if (listModel.getSize() == 1 && firstItem instanceof EmptyLookupItem) return false;
|
||||
|
||||
final PrefixMatcher firstItemMatcher = itemMatcher(firstItem);
|
||||
final String oldPrefix = firstItemMatcher.getPrefix();
|
||||
final String presentPrefix = oldPrefix + getAdditionalPrefix();
|
||||
String commonPrefix = getCaseCorrectedLookupString(firstItem);
|
||||
|
||||
for (int i = 1; i < listModel.getSize(); i++) {
|
||||
LookupElement item = (LookupElement)listModel.getElementAt(i);
|
||||
if (item instanceof EmptyLookupItem) return false;
|
||||
if (!oldPrefix.equals(itemMatcher(item).getPrefix())) return false;
|
||||
|
||||
final String lookupString = getCaseCorrectedLookupString(item);
|
||||
final int length = Math.min(commonPrefix.length(), lookupString.length());
|
||||
if (length < commonPrefix.length()) {
|
||||
commonPrefix = commonPrefix.substring(0, length);
|
||||
}
|
||||
|
||||
for (int j = 0; j < length; j++) {
|
||||
if (commonPrefix.charAt(j) != lookupString.charAt(j)) {
|
||||
commonPrefix = lookupString.substring(0, j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (commonPrefix.length() == 0 || commonPrefix.length() < presentPrefix.length()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (commonPrefix.equals(presentPrefix)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < listModel.getSize(); i++) {
|
||||
LookupElement item = (LookupElement)listModel.getElementAt(i);
|
||||
if (!itemMatcher(item).cloneWithPrefix(commonPrefix).prefixMatches(item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
myOffsets.setInitialPrefix(presentPrefix, explicitlyInvoked);
|
||||
|
||||
replacePrefix(presentPrefix, commonPrefix);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void replacePrefix(final String presentPrefix, final String newPrefix) {
|
||||
if (!performGuardedChange(() -> {
|
||||
EditorModificationUtil.deleteSelectedText(myEditor);
|
||||
@@ -978,11 +925,11 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable,
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private DocumentWindow getInjectedDocument(int offset) {
|
||||
PsiFile hostFile = PsiDocumentManager.getInstance(myProject).getPsiFile(myEditor.getDocument());
|
||||
private static DocumentWindow getInjectedDocument(Project project, Editor editor, int offset) {
|
||||
PsiFile hostFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
|
||||
if (hostFile != null) {
|
||||
// inspired by com.intellij.codeInsight.editorActions.TypedHandler.injectedEditorIfCharTypedIsSignificant()
|
||||
List<DocumentWindow> injected = InjectedLanguageManager.getInstance(myProject).getCachedInjectedDocumentsInRange(hostFile, TextRange.create(offset, offset));
|
||||
List<DocumentWindow> injected = InjectedLanguageManager.getInstance(project).getCachedInjectedDocumentsInRange(hostFile, TextRange.create(offset, offset));
|
||||
for (DocumentWindow documentWindow : injected ) {
|
||||
if (documentWindow.isValid() && documentWindow.containsRange(offset, offset)) {
|
||||
return documentWindow;
|
||||
@@ -995,7 +942,7 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable,
|
||||
@Override
|
||||
@NotNull
|
||||
public Editor getEditor() {
|
||||
DocumentWindow documentWindow = getInjectedDocument(myEditor.getCaretModel().getOffset());
|
||||
DocumentWindow documentWindow = getInjectedDocument(myProject, myEditor, myEditor.getCaretModel().getOffset());
|
||||
if (documentWindow != null) {
|
||||
PsiFile injectedFile = PsiDocumentManager.getInstance(myProject).getPsiFile(documentWindow);
|
||||
return InjectedLanguageUtil.getInjectedEditorForInjectedFile(myEditor, injectedFile);
|
||||
|
||||
Reference in New Issue
Block a user