mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-05 01:50:56 +07:00
IDEA-343642 cleanup
GitOrigin-RevId: da90d7c3e004c824694278b5aba85156f59f5856
This commit is contained in:
committed by
intellij-monorepo-bot
parent
5ac2c35ccf
commit
a00dc47229
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.completion;
|
||||
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
@@ -41,7 +41,7 @@ import java.util.List;
|
||||
* A more generic way is to override default {@link #fillCompletionVariants(CompletionParameters, CompletionResultSet)} implementation
|
||||
* and provide your own. It's easier to debug, but harder to write.<p>
|
||||
*
|
||||
* Q: How do I get automatic lookup element filtering by prefix?<br>
|
||||
* Q: How do I get an automatic lookup element filtering by prefix?<br>
|
||||
* A: When you return variants from reference ({@link PsiReference#getVariants()}), the filtering will be done
|
||||
* automatically, with prefix taken as the reference text from its start ({@link PsiReference#getRangeInElement()}) to
|
||||
* the caret position.
|
||||
@@ -64,12 +64,12 @@ import java.util.List;
|
||||
* Q: I'm not satisfied that completion just inserts the item's lookup string on item selection. How to make it write something else?<br>
|
||||
* A: See {@link LookupElement#handleInsert(InsertionContext)}.<p>
|
||||
*
|
||||
* Q: What if I select item with TAB key?<br>
|
||||
* A: Semantics is, that the identifier that you're standing inside gets removed completely, and then the lookup string is inserted. You can change
|
||||
* Q: What if I select item with a TAB key?<br>
|
||||
* A: Semantics is that the identifier that you're standing inside gets removed completely, and then the lookup string is inserted. You can change
|
||||
* the deleting range end offset, do it in {@link CompletionContributor#beforeCompletion(CompletionInitializationContext)}
|
||||
* by putting new offset to {@link CompletionInitializationContext#getOffsetMap()} as {@link CompletionInitializationContext#IDENTIFIER_END_OFFSET}.<p>
|
||||
*
|
||||
* Q: I know more about my environment than the IDE does, and I can swear that those 239 variants it suggests me in some place aren't all that relevant,
|
||||
* Q: I know more about my environment than the IDE does, and I can swear that those 239 variants it suggests me in some places aren't all that relevant,
|
||||
* so I'd be happy to filter out 42 of them. How do I do this?<br>
|
||||
* A: This is a bit harder than just adding variants. First, you should invoke
|
||||
* {@link CompletionResultSet#runRemainingContributors(CompletionParameters, Consumer)}.
|
||||
@@ -82,7 +82,7 @@ import java.util.List;
|
||||
*
|
||||
* Q: How are lookup elements sorted?<br>
|
||||
* A: Basically in lexicographic order, ascending, by lookup string ({@link LookupElement#getLookupString()}).
|
||||
* Also, there's a number of "weigher" extensions under "completion" key (see {@link CompletionWeigher}) that bubble up the most relevant
|
||||
* Also, there are a number of "weigher" extensions under "completion" key (see {@link CompletionWeigher}) that bubble up the most relevant
|
||||
* items. To control lookup elements order you may implement {@link CompletionWeigher} or use {@link PrioritizedLookupElement}.<br>
|
||||
* To debug the order of the completion items use '<code>Dump lookup element weights to log</code>' action when the completion lookup is
|
||||
* shown (Ctrl+Alt+Shift+W / Cmd+Alt+Shift+W), the action also copies the debug info to the Clipboard.<p>
|
||||
@@ -193,7 +193,7 @@ public abstract class CompletionContributor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the completion is finished quickly, lookup hasn't been shown and gives possibility to auto-insert some item (typically - the only one).
|
||||
* Called when the completion is finished quickly, lookup hasn't been shown and gives the possibility to auto-insert some item (typically - the only one).
|
||||
*/
|
||||
public @Nullable AutoCompletionDecision handleAutoCompletionPossibility(@NotNull AutoCompletionContext context) {
|
||||
return null;
|
||||
@@ -212,7 +212,7 @@ public abstract class CompletionContributor {
|
||||
* Invoked in a read action in parallel to the completion process. Used to calculate the replacement offset
|
||||
* (see {@link CompletionInitializationContext#setReplacementOffset(int)})
|
||||
* if it takes too much time to spend it in {@link #beforeCompletion(CompletionInitializationContext)},
|
||||
* e.g. doing {@link com.intellij.psi.PsiFile#findReferenceAt(int)}
|
||||
* e.g., doing {@link com.intellij.psi.PsiFile#findReferenceAt(int)}
|
||||
*
|
||||
* Guaranteed to be invoked before any lookup element is selected
|
||||
*
|
||||
@@ -221,10 +221,11 @@ public abstract class CompletionContributor {
|
||||
public void duringCompletion(@NotNull CompletionInitializationContext context) {
|
||||
}
|
||||
|
||||
public static @NotNull List<CompletionContributor> forParameters(final @NotNull CompletionParameters parameters) {
|
||||
public static @NotNull List<CompletionContributor> forParameters(@NotNull CompletionParameters parameters) {
|
||||
return ReadAction.compute(() -> {
|
||||
PsiElement position = parameters.getPosition();
|
||||
return forLanguageHonorDumbness(PsiUtilCore.getLanguageAtOffset(position.getContainingFile(), parameters.getOffset()), position.getProject());
|
||||
return forLanguageHonorDumbness(PsiUtilCore.getLanguageAtOffset(position.getContainingFile(), parameters.getOffset()),
|
||||
position.getProject());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.completion;
|
||||
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
@@ -17,27 +17,27 @@ import java.util.LinkedHashSet;
|
||||
* match them against specified
|
||||
* {@link PrefixMatcher} and give them to special {@link Consumer}
|
||||
* for further processing, which usually means
|
||||
* they will sooner or later appear in completion list. If they don't, there must be some {@link CompletionContributor}
|
||||
* up the invocation stack that filters them out.
|
||||
*
|
||||
* they will sooner or later appear in a completion list.
|
||||
* If they don't, there must be some {@link CompletionContributor} up the invocation stack that filters them out.
|
||||
* <p>
|
||||
* If you want to change the matching prefix, use {@link #withPrefixMatcher(PrefixMatcher)} or {@link #withPrefixMatcher(String)}
|
||||
* to obtain another {@link CompletionResultSet} and give your lookup elements to that one.
|
||||
*/
|
||||
public abstract class CompletionResultSet implements Consumer<LookupElement> {
|
||||
private final PrefixMatcher myPrefixMatcher;
|
||||
private final Consumer<? super CompletionResult> myConsumer;
|
||||
private final PrefixMatcher prefixMatcher;
|
||||
private final java.util.function.Consumer<? super CompletionResult> consumer;
|
||||
protected final CompletionService myCompletionService = CompletionService.getCompletionService();
|
||||
protected final CompletionContributor myContributor;
|
||||
protected final CompletionContributor contributor;
|
||||
private boolean myStopped;
|
||||
|
||||
protected CompletionResultSet(final PrefixMatcher prefixMatcher, Consumer<? super CompletionResult> consumer, CompletionContributor contributor) {
|
||||
myPrefixMatcher = prefixMatcher;
|
||||
myConsumer = consumer;
|
||||
myContributor = contributor;
|
||||
protected CompletionResultSet(final PrefixMatcher prefixMatcher, java.util.function.Consumer<? super CompletionResult> consumer, CompletionContributor contributor) {
|
||||
this.prefixMatcher = prefixMatcher;
|
||||
this.consumer = consumer;
|
||||
this.contributor = contributor;
|
||||
}
|
||||
|
||||
protected Consumer<? super CompletionResult> getConsumer() {
|
||||
return myConsumer;
|
||||
protected java.util.function.Consumer<? super CompletionResult> getConsumer() {
|
||||
return consumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,18 +52,18 @@ public abstract class CompletionResultSet implements Consumer<LookupElement> {
|
||||
public abstract void addElement(final @NotNull LookupElement element);
|
||||
|
||||
public void passResult(@NotNull CompletionResult result) {
|
||||
myConsumer.consume(result);
|
||||
consumer.accept(result);
|
||||
}
|
||||
|
||||
public void startBatch() {
|
||||
if (myConsumer instanceof BatchConsumer) {
|
||||
((BatchConsumer<?>)myConsumer).startBatch();
|
||||
if (consumer instanceof BatchConsumer) {
|
||||
((BatchConsumer<?>)consumer).startBatch();
|
||||
}
|
||||
}
|
||||
|
||||
public void endBatch() {
|
||||
if (myConsumer instanceof BatchConsumer) {
|
||||
((BatchConsumer<?>)myConsumer).endBatch();
|
||||
if (consumer instanceof BatchConsumer) {
|
||||
((BatchConsumer<?>)consumer).endBatch();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public abstract class CompletionResultSet implements Consumer<LookupElement> {
|
||||
* Otherwise, when the lookup is shown, most relevant elements processed to that moment are put to the top
|
||||
* and remain there even if more relevant elements appear later.
|
||||
* These "first" elements may differ from completion invocation to completion invocation due to performance fluctuations,
|
||||
* resulting in varying preselected item in completion and worse user experience. Using {@code addAllElements}
|
||||
* resulting in varying preselected items in completion and worse user experience. Using {@code addAllElements}
|
||||
* instead of {@link #addElement(LookupElement)} helps to avoid that.
|
||||
*/
|
||||
public void addAllElements(final @NotNull Iterable<? extends LookupElement> elements) {
|
||||
@@ -106,13 +106,13 @@ public abstract class CompletionResultSet implements Consumer<LookupElement> {
|
||||
|
||||
/**
|
||||
* @return A result set with the same prefix, but the lookup strings will be matched case-insensitively. Their lookup strings will
|
||||
* remain as they are though, so upon insertion the prefix case will be changed.
|
||||
* remain as they are though, so upon insertion, the prefix case will be changed.
|
||||
*/
|
||||
@Contract(pure = true)
|
||||
public abstract @NotNull CompletionResultSet caseInsensitive();
|
||||
|
||||
public @NotNull PrefixMatcher getPrefixMatcher() {
|
||||
return myPrefixMatcher;
|
||||
return prefixMatcher;
|
||||
}
|
||||
|
||||
public boolean isStopped() {
|
||||
@@ -147,7 +147,7 @@ public abstract class CompletionResultSet implements Consumer<LookupElement> {
|
||||
if (stop) {
|
||||
stopHere();
|
||||
}
|
||||
myCompletionService.getVariantsFromContributors(parameters, myContributor, getPrefixMatcher(), new BatchConsumer<>() {
|
||||
myCompletionService.getVariantsFromContributors(parameters, contributor, getPrefixMatcher(), new BatchConsumer<>() {
|
||||
@Override
|
||||
public void startBatch() {
|
||||
CompletionResultSet.this.startBatch();
|
||||
|
||||
@@ -26,7 +26,7 @@ import java.util.ArrayList;
|
||||
public class BaseCompletionService extends CompletionService {
|
||||
private static final Logger LOG = Logger.getInstance(BaseCompletionService.class);
|
||||
|
||||
protected @Nullable CompletionProcess myApiCompletionProcess;
|
||||
protected @Nullable CompletionProcess apiCompletionProcess;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static final Key<CompletionContributor> LOOKUP_ELEMENT_CONTRIBUTOR = Key.create("lookup element contributor");
|
||||
@@ -43,12 +43,12 @@ public class BaseCompletionService extends CompletionService {
|
||||
|
||||
@Override
|
||||
public void performCompletion(@NotNull CompletionParameters parameters, @NotNull Consumer<? super CompletionResult> consumer) {
|
||||
myApiCompletionProcess = parameters.getProcess();
|
||||
apiCompletionProcess = parameters.getProcess();
|
||||
try {
|
||||
super.performCompletion(parameters, consumer);
|
||||
}
|
||||
finally {
|
||||
myApiCompletionProcess = null;
|
||||
apiCompletionProcess = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,8 +56,8 @@ public class BaseCompletionService extends CompletionService {
|
||||
public void setAdvertisementText(@Nullable @NlsContexts.PopupAdvertisement String text) {
|
||||
if (text == null) return;
|
||||
|
||||
if (myApiCompletionProcess instanceof CompletionProcessEx) {
|
||||
((CompletionProcessEx)myApiCompletionProcess).addAdvertisement(text, null);
|
||||
if (apiCompletionProcess instanceof CompletionProcessEx) {
|
||||
((CompletionProcessEx)apiCompletionProcess).addAdvertisement(text, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,21 +88,21 @@ public class BaseCompletionService extends CompletionService {
|
||||
|
||||
@Override
|
||||
public @Nullable CompletionProcess getCurrentCompletion() {
|
||||
return myApiCompletionProcess;
|
||||
return apiCompletionProcess;
|
||||
}
|
||||
|
||||
protected static class BaseCompletionResultSet extends CompletionResultSet {
|
||||
protected final CompletionParameters myParameters;
|
||||
protected CompletionSorter mySorter;
|
||||
protected final CompletionParameters parameters;
|
||||
protected CompletionSorter sorter;
|
||||
protected final @Nullable BaseCompletionService.BaseCompletionResultSet myOriginal;
|
||||
private int myItemCounter = 0;
|
||||
private int itemCounter = 0;
|
||||
|
||||
protected BaseCompletionResultSet(Consumer<? super CompletionResult> consumer, PrefixMatcher prefixMatcher,
|
||||
protected BaseCompletionResultSet(java.util.function.Consumer<? super CompletionResult> consumer, PrefixMatcher prefixMatcher,
|
||||
CompletionContributor contributor, CompletionParameters parameters,
|
||||
@Nullable CompletionSorter sorter, @Nullable BaseCompletionService.BaseCompletionResultSet original) {
|
||||
super(prefixMatcher, consumer, contributor);
|
||||
myParameters = parameters;
|
||||
mySorter = sorter;
|
||||
this.parameters = parameters;
|
||||
this.sorter = sorter;
|
||||
myOriginal = original;
|
||||
}
|
||||
|
||||
@@ -111,18 +111,18 @@ public class BaseCompletionService extends CompletionService {
|
||||
ProgressManager.checkCanceled();
|
||||
if (!element.isValid()) {
|
||||
LOG.error("Invalid lookup element: " + element + " of " + element.getClass() +
|
||||
" in " + myParameters.getOriginalFile() + " of " + myParameters.getOriginalFile().getClass());
|
||||
" in " + parameters.getOriginalFile() + " of " + parameters.getOriginalFile().getClass());
|
||||
return;
|
||||
}
|
||||
|
||||
mySorter = mySorter == null ? getCompletionService().defaultSorter(myParameters, getPrefixMatcher()) : mySorter;
|
||||
sorter = sorter == null ? getCompletionService().defaultSorter(parameters, getPrefixMatcher()) : sorter;
|
||||
|
||||
CompletionResult matched = CompletionResult.wrap(element, getPrefixMatcher(), mySorter);
|
||||
CompletionResult matched = CompletionResult.wrap(element, getPrefixMatcher(), sorter);
|
||||
if (matched != null) {
|
||||
element.putUserData(LOOKUP_ELEMENT_CONTRIBUTOR, myContributor);
|
||||
element.putUserData(LOOKUP_ELEMENT_CONTRIBUTOR, contributor);
|
||||
element.putUserData(LOOKUP_ELEMENT_RESULT_ADD_TIMESTAMP_MILLIS, System.currentTimeMillis());
|
||||
element.putUserData(LOOKUP_ELEMENT_RESULT_SET_ORDER, myItemCounter);
|
||||
myItemCounter += 1;
|
||||
element.putUserData(LOOKUP_ELEMENT_RESULT_SET_ORDER, itemCounter);
|
||||
itemCounter += 1;
|
||||
passResult(matched);
|
||||
}
|
||||
}
|
||||
@@ -132,7 +132,7 @@ public class BaseCompletionService extends CompletionService {
|
||||
if (matcher.equals(getPrefixMatcher())) {
|
||||
return this;
|
||||
}
|
||||
return new BaseCompletionResultSet(getConsumer(), matcher, myContributor, myParameters, mySorter, this);
|
||||
return new BaseCompletionResultSet(getConsumer(), matcher, contributor, parameters, sorter, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -153,7 +153,7 @@ public class BaseCompletionService extends CompletionService {
|
||||
|
||||
@Override
|
||||
public @NotNull CompletionResultSet withRelevanceSorter(@NotNull CompletionSorter sorter) {
|
||||
return new BaseCompletionResultSet(getConsumer(), getPrefixMatcher(), myContributor, myParameters, sorter, this);
|
||||
return new BaseCompletionResultSet(getConsumer(), getPrefixMatcher(), contributor, parameters, sorter, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,10 +19,7 @@ import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.actionSystem.OverridingAction;
|
||||
import com.intellij.openapi.actionSystem.impl.ActionManagerImpl;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.application.WriteAction;
|
||||
import com.intellij.openapi.application.*;
|
||||
import com.intellij.openapi.application.ex.ApplicationManagerEx;
|
||||
import com.intellij.openapi.command.CommandProcessor;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
@@ -46,6 +43,7 @@ import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.platform.diagnostic.telemetry.TelemetryManager;
|
||||
import com.intellij.platform.diagnostic.telemetry.helpers.TraceKt;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.impl.source.PostprocessReformattingAspect;
|
||||
@@ -71,7 +69,6 @@ import java.util.Objects;
|
||||
|
||||
import static com.intellij.codeInsight.completion.CompletionThreadingKt.checkForExceptions;
|
||||
import static com.intellij.codeInsight.util.CodeCompletionKt.CodeCompletion;
|
||||
import static com.intellij.platform.diagnostic.telemetry.helpers.TraceKt.runWithSpan;
|
||||
import static com.intellij.psi.stubs.StubInconsistencyReporter.SourceOfCheck.DeliberateAdditionalCheckInCompletion;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@@ -192,12 +189,13 @@ public class CodeCompletionHandlerBase {
|
||||
long startingTime = System.currentTimeMillis();
|
||||
Runnable initCmd = () -> {
|
||||
WriteAction.run(() -> EditorUtil.fillVirtualSpaceUntilCaret(editor));
|
||||
CompletionInitializationContextImpl context = withTimeout(calcSyncTimeOut(startingTime), () ->
|
||||
CompletionInitializationUtil.createCompletionInitializationContext(project, editor, caret, invocationCount, completionType));
|
||||
CompletionInitializationContextImpl context = withTimeout(calcSyncTimeOut(startingTime), () -> {
|
||||
return CompletionInitializationUtil.createCompletionInitializationContext(project, editor, caret, invocationCount, completionType);
|
||||
});
|
||||
|
||||
boolean hasValidContext = context != null;
|
||||
if (!hasValidContext) {
|
||||
final PsiFile psiFile = PsiUtilBase.getPsiFileInEditor(caret, project);
|
||||
PsiFile psiFile = PsiUtilBase.getPsiFileInEditor(caret, project);
|
||||
context = new CompletionInitializationContextImpl(editor, caret, psiFile, completionType, invocationCount);
|
||||
}
|
||||
|
||||
@@ -225,19 +223,21 @@ public class CodeCompletionHandlerBase {
|
||||
int time,
|
||||
boolean hasModifiers,
|
||||
@NotNull Caret caret) {
|
||||
runWithSpan(completionTracer, "invokeCompletion", (span) -> {
|
||||
span.setAttribute("project", project.getName());
|
||||
span.setAttribute("caretOffset", caret.hasSelection() ? caret.getSelectionStart() : caret.getOffset());
|
||||
|
||||
invokeCompletion(project, editor, time, hasModifiers, caret);
|
||||
});
|
||||
TraceKt.useWithScopeBlocking(
|
||||
completionTracer.spanBuilder("invokeCompletion")
|
||||
.setAttribute("project", project.getName())
|
||||
.setAttribute("caretOffset", caret.hasSelection() ? caret.getSelectionStart() : caret.getOffset()),
|
||||
span -> {
|
||||
invokeCompletion(project, editor, time, hasModifiers, caret);
|
||||
return null;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private static void checkNoWriteAccess() {
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
if (ApplicationManager.getApplication().isWriteAccessAllowed()) {
|
||||
throw new AssertionError("Completion should not be invoked inside write action");
|
||||
}
|
||||
Application app = ApplicationManager.getApplication();
|
||||
if (!app.isUnitTestMode() && app.isWriteAccessAllowed()) {
|
||||
throw new AssertionError("Completion should not be invoked inside write action");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,18 +263,19 @@ public class CodeCompletionHandlerBase {
|
||||
}
|
||||
|
||||
private void doComplete(CompletionInitializationContextImpl initContext, boolean hasModifiers, boolean isValidContext, long startingTime) {
|
||||
final Editor editor = initContext.getEditor();
|
||||
Editor editor = initContext.getEditor();
|
||||
CompletionAssertions.checkEditorValid(editor);
|
||||
|
||||
LookupImpl lookup = obtainLookup(editor, initContext.getProject());
|
||||
|
||||
CompletionPhase phase = CompletionServiceImpl.getCompletionPhase();
|
||||
if (phase instanceof CompletionPhase.CommittingDocuments) {
|
||||
if (phase instanceof CompletionPhase.CommittingDocuments p) {
|
||||
if (phase.indicator != null) {
|
||||
phase.indicator.closeAndFinish(false);
|
||||
}
|
||||
((CompletionPhase.CommittingDocuments)phase).replaced = true;
|
||||
} else {
|
||||
p.replaced = true;
|
||||
}
|
||||
else {
|
||||
CompletionServiceImpl.assertPhase(CompletionPhase.NoCompletion.getClass());
|
||||
}
|
||||
|
||||
@@ -305,7 +306,8 @@ public class CodeCompletionHandlerBase {
|
||||
if (synchronous) {
|
||||
phase = new CompletionPhase.BgCalculation(indicator);
|
||||
indicator.showLookup();
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
phase = new CompletionPhase.CommittingDocuments(indicator, InjectedLanguageEditorUtil.getTopLevelEditor(indicator.getEditor()), null);
|
||||
}
|
||||
CompletionServiceImpl.setCompletionPhase(phase);
|
||||
@@ -339,7 +341,10 @@ public class CodeCompletionHandlerBase {
|
||||
|
||||
int timeout = calcSyncTimeOut(startingTime);
|
||||
if (indicator.blockingWaitForFinish(timeout)) {
|
||||
checkForExceptions(future);
|
||||
if (ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
//noinspection TestOnlyProblems
|
||||
checkForExceptions(future);
|
||||
}
|
||||
try {
|
||||
indicator.getLookup().refreshUi(true, false);
|
||||
completionFinished(indicator, hasModifiers);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.completion;
|
||||
|
||||
import com.intellij.codeInsight.completion.impl.CompletionServiceImpl;
|
||||
@@ -227,6 +227,7 @@ public abstract class CompletionPhase implements Disposable {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final class Synchronous extends CompletionPhase {
|
||||
public Synchronous(CompletionProgressIndicator indicator) {
|
||||
super(indicator);
|
||||
@@ -239,6 +240,7 @@ public abstract class CompletionPhase implements Disposable {
|
||||
return time;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class BgCalculation extends CompletionPhase {
|
||||
boolean modifiersChanged = false;
|
||||
private final @NotNull ClientId ownerId = ClientId.getCurrent();
|
||||
|
||||
@@ -85,22 +85,22 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
@NotNull
|
||||
private final Caret myCaret;
|
||||
@Nullable private CompletionParameters myParameters;
|
||||
private final CodeCompletionHandlerBase myHandler;
|
||||
private final CodeCompletionHandlerBase handler;
|
||||
private final CompletionLookupArrangerImpl myArranger;
|
||||
private final CompletionType myCompletionType;
|
||||
private final int myInvocationCount;
|
||||
private OffsetsInFile myHostOffsets;
|
||||
private final LookupImpl myLookup;
|
||||
private final MergingUpdateQueue myQueue;
|
||||
private final LookupImpl lookup;
|
||||
private final MergingUpdateQueue queue;
|
||||
private final Update myUpdate = new Update("update") {
|
||||
@Override
|
||||
public void run() {
|
||||
updateLookup();
|
||||
myQueue.setMergingTimeSpan(ourShowPopupGroupingTime);
|
||||
queue.setMergingTimeSpan(ourShowPopupGroupingTime);
|
||||
}
|
||||
};
|
||||
private final Semaphore myFreezeSemaphore = new Semaphore(1);
|
||||
private final Semaphore myFinishSemaphore = new Semaphore(1);
|
||||
private final Semaphore freezeSemaphore = new Semaphore(1);
|
||||
private final Semaphore finishSemaphore = new Semaphore(1);
|
||||
@NotNull private final OffsetMap myOffsetMap;
|
||||
private final Set<Pair<Integer, ElementPattern<String>>> myRestartingPrefixConditions = ConcurrentHashMap.newKeySet();
|
||||
private final LookupListener myLookupListener = new LookupListener() {
|
||||
@@ -116,7 +116,7 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
private static int ourShowPopupGroupingTime = 300;
|
||||
private static int ourShowPopupAfterFirstItemGroupingTime = 100;
|
||||
|
||||
private volatile int myCount;
|
||||
private volatile int count;
|
||||
private enum LookupAppearancePolicy {
|
||||
/**
|
||||
* The default strategy for lookup appearance.
|
||||
@@ -143,7 +143,7 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
private boolean myLookupUpdated;
|
||||
private final PropertyChangeListener myLookupManagerListener;
|
||||
private final Queue<Runnable> myAdvertiserChanges = new ConcurrentLinkedQueue<>();
|
||||
private final List<CompletionResult> myDelayedMiddleMatches = new ArrayList<>();
|
||||
private final List<CompletionResult> delayedMiddleMatches = new ArrayList<>();
|
||||
private final int myStartCaret;
|
||||
private final CompletionThreadingBase myThreading;
|
||||
private final Object myLock = ObjectUtils.sentinel("CompletionProgressIndicator");
|
||||
@@ -161,24 +161,24 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
boolean hasModifiers, @NotNull LookupImpl lookup) {
|
||||
myEditor = editor;
|
||||
myCaret = caret;
|
||||
myHandler = handler;
|
||||
this.handler = handler;
|
||||
myCompletionType = handler.completionType;
|
||||
myInvocationCount = invocationCount;
|
||||
myOffsetMap = offsetMap;
|
||||
myHostOffsets = hostOffsets;
|
||||
myLookup = lookup;
|
||||
this.lookup = lookup;
|
||||
myStartCaret = myEditor.getCaretModel().getOffset();
|
||||
myThreading = ApplicationManager.getApplication().isWriteAccessAllowed() || myHandler.isTestingCompletionQualityMode()
|
||||
myThreading = ApplicationManager.getApplication().isWriteAccessAllowed() || this.handler.isTestingCompletionQualityMode()
|
||||
? new SyncCompletion()
|
||||
: new AsyncCompletion(editor.getProject());
|
||||
|
||||
myAdvertiserChanges.offer(() -> myLookup.getAdvertiser().clearAdvertisements());
|
||||
myAdvertiserChanges.offer(() -> this.lookup.getAdvertiser().clearAdvertisements());
|
||||
|
||||
myArranger = new CompletionLookupArrangerImpl(this);
|
||||
myLookup.setArranger(myArranger);
|
||||
this.lookup.setArranger(myArranger);
|
||||
|
||||
myLookup.addLookupListener(myLookupListener);
|
||||
myLookup.setCalculating(true);
|
||||
this.lookup.addLookupListener(myLookupListener);
|
||||
this.lookup.setCalculating(true);
|
||||
|
||||
myEmptyCompletionNotifier = LightEdit.owns(editor.getProject()) ? LightEditUtil.createEmptyCompletionNotifier() :
|
||||
new ProjectEmptyCompletionNotifier();
|
||||
@@ -191,7 +191,7 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
};
|
||||
LookupManager.getInstance(getProject()).addPropertyChangeListener(myLookupManagerListener);
|
||||
|
||||
myQueue = new MergingUpdateQueue("completion lookup progress", ourShowPopupAfterFirstItemGroupingTime, true, myEditor.getContentComponent());
|
||||
queue = new MergingUpdateQueue("completion lookup progress", ourShowPopupAfterFirstItemGroupingTime, true, myEditor.getContentComponent());
|
||||
|
||||
ThreadingAssertions.assertEventDispatchThread();
|
||||
|
||||
@@ -208,7 +208,7 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
|
||||
setMergeCommand();
|
||||
|
||||
myHandler.lookupItemSelected(this, lookupItem, completionChar, myLookup.getItems());
|
||||
handler.lookupItemSelected(this, lookupItem, completionChar, lookup.getItems());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -230,7 +230,7 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
LookupFocusDegree degree = CodeInsightSettings.getInstance().isSelectAutopopupSuggestionsByChars()
|
||||
? LookupFocusDegree.FOCUSED
|
||||
: LookupFocusDegree.SEMI_FOCUSED;
|
||||
myLookup.setLookupFocusDegree(degree);
|
||||
lookup.setLookupFocusDegree(degree);
|
||||
}
|
||||
addDefaultAdvertisements(parameters);
|
||||
|
||||
@@ -338,14 +338,17 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
|
||||
|
||||
void scheduleAdvertising(CompletionParameters parameters) {
|
||||
if (myLookup.isAvailableToUser()) {
|
||||
if (lookup.isAvailableToUser()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DumbModeAccessType.RELIABLE_DATA_ONLY.ignoreDumbMode(() -> {
|
||||
for (CompletionContributor contributor : CompletionContributor.forParameters(parameters)) {
|
||||
if (!myLookup.isCalculating() && !myLookup.isVisible()) return;
|
||||
if (!lookup.isCalculating() && !lookup.isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
//noinspection deprecation
|
||||
@SuppressWarnings("removal")
|
||||
String s = contributor.advertise(parameters);
|
||||
if (s != null) {
|
||||
addAdvertisement(s, null);
|
||||
@@ -393,7 +396,7 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
@Override
|
||||
@NotNull
|
||||
public LookupImpl getLookup() {
|
||||
return myLookup;
|
||||
return lookup;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -424,7 +427,7 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
|
||||
private void openLookupLater() {
|
||||
ApplicationManager.getApplication()
|
||||
.invokeLater(this::showLookup, obj -> myLookup.getShownTimestampMillis() != 0L || myLookup.isLookupDisposed());
|
||||
.invokeLater(this::showLookup, obj -> lookup.getShownTimestampMillis() != 0L || lookup.isLookupDisposed());
|
||||
}
|
||||
|
||||
void withSingleUpdate(Runnable action) {
|
||||
@@ -442,28 +445,28 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
}
|
||||
|
||||
if (!myLookupUpdated) {
|
||||
if (myLookup.getAdvertisements().isEmpty() && !isAutopopupCompletion() && !DumbService.isDumb(getProject())) {
|
||||
DefaultCompletionContributor.addDefaultAdvertisements(myLookup, myHasPsiElements);
|
||||
if (lookup.getAdvertisements().isEmpty() && !isAutopopupCompletion() && !DumbService.isDumb(getProject())) {
|
||||
DefaultCompletionContributor.addDefaultAdvertisements(lookup, myHasPsiElements);
|
||||
}
|
||||
myLookup.getAdvertiser().showRandomText();
|
||||
lookup.getAdvertiser().showRandomText();
|
||||
}
|
||||
|
||||
boolean justShown = false;
|
||||
if (!myLookup.isShown()) {
|
||||
if (!lookup.isShown()) {
|
||||
if (hideAutopopupIfMeaningless()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!myLookup.showLookup()) {
|
||||
if (!lookup.showLookup()) {
|
||||
return;
|
||||
}
|
||||
justShown = true;
|
||||
}
|
||||
myLookupUpdated = true;
|
||||
myLookup.refreshUi(true, justShown);
|
||||
lookup.refreshUi(true, justShown);
|
||||
hideAutopopupIfMeaningless();
|
||||
if (justShown) {
|
||||
myLookup.ensureSelectionVisible(true);
|
||||
lookup.ensureSelectionVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,10 +475,10 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
return false;
|
||||
}
|
||||
if (isAutopopupCompletion()) {
|
||||
if (myCount == 0) {
|
||||
if (count == 0) {
|
||||
return false;
|
||||
}
|
||||
if (myLookup.isCalculating() && Registry.is("ide.completion.delay.autopopup.until.completed")) {
|
||||
if (lookup.isCalculating() && Registry.is("ide.completion.delay.autopopup.until.completed")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -486,7 +489,7 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
if (!isRunning()) return;
|
||||
ProgressManager.checkCanceled();
|
||||
|
||||
if (!myHandler.isTestingMode()) {
|
||||
if (!handler.isTestingMode()) {
|
||||
ApplicationManager.getApplication().assertIsNonDispatchThread();
|
||||
}
|
||||
|
||||
@@ -502,7 +505,7 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
return;
|
||||
}
|
||||
|
||||
boolean allowMiddleMatches = myCount > BaseCompletionLookupArranger.MAX_PREFERRED_COUNT * 2;
|
||||
boolean allowMiddleMatches = count > BaseCompletionLookupArranger.MAX_PREFERRED_COUNT * 2;
|
||||
if (allowMiddleMatches) {
|
||||
addDelayedMiddleMatches();
|
||||
}
|
||||
@@ -511,8 +514,8 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
if (item.isStartMatch() || allowMiddleMatches) {
|
||||
addItemToLookup(item);
|
||||
} else {
|
||||
synchronized (myDelayedMiddleMatches) {
|
||||
myDelayedMiddleMatches.add(item);
|
||||
synchronized (delayedMiddleMatches) {
|
||||
delayedMiddleMatches.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -520,41 +523,42 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
private void addItemToLookup(CompletionResult item) {
|
||||
Ref<Boolean> stopRef = new Ref<>(Boolean.FALSE);
|
||||
DumbModeAccessType.RELIABLE_DATA_ONLY.ignoreDumbMode(() -> {
|
||||
stopRef.set(!myLookup.addItem(item.getLookupElement(), item.getPrefixMatcher()));
|
||||
stopRef.set(!lookup.addItem(item.getLookupElement(), item.getPrefixMatcher()));
|
||||
});
|
||||
|
||||
if (stopRef.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
myArranger.setLastLookupPrefix(myLookup.getAdditionalPrefix());
|
||||
myArranger.setLastLookupPrefix(lookup.getAdditionalPrefix());
|
||||
|
||||
//noinspection NonAtomicOperationOnVolatileField
|
||||
myCount++; // invoked from a single thread
|
||||
count++; // invoked from a single thread
|
||||
|
||||
if (myCount == myUnfreezeAfterNItems) {
|
||||
if (count == myUnfreezeAfterNItems) {
|
||||
showLookupAsSoonAsPossible();
|
||||
}
|
||||
|
||||
if (myLookupAppearancePolicy == LookupAppearancePolicy.ON_FIRST_POSSIBILITY && myLookup.getShownTimestampMillis() == 0L) {
|
||||
AppExecutorUtil.getAppScheduledExecutorService().schedule(myFreezeSemaphore::up, 0, TimeUnit.MILLISECONDS);
|
||||
if (myLookupAppearancePolicy == LookupAppearancePolicy.ON_FIRST_POSSIBILITY && lookup.getShownTimestampMillis() == 0L) {
|
||||
AppExecutorUtil.getAppScheduledExecutorService().schedule(freezeSemaphore::up, 0, TimeUnit.MILLISECONDS);
|
||||
openLookupLater();
|
||||
} else {
|
||||
if (myCount == 1) {
|
||||
AppExecutorUtil.getAppScheduledExecutorService().schedule(myFreezeSemaphore::up, ourInsertSingleItemTimeSpan, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
else {
|
||||
if (count == 1) {
|
||||
AppExecutorUtil.getAppScheduledExecutorService().schedule(freezeSemaphore::up, ourInsertSingleItemTimeSpan, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
myQueue.queue(myUpdate);
|
||||
queue.queue(myUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* In certain cases we add the first batch of items almost at once and want to show lookup directly after they are added.
|
||||
* In certain cases, we add the first batch of items almost at once and want to show lookup directly after they are added.
|
||||
* It makes sense to set this number when you get all your items from one contributor, and you have full control over the completion
|
||||
* process. So you can, for example, set this number in the beginning of `addCompletions()` in your completion provider and reset it
|
||||
* when the completion is over.
|
||||
* Example: Completion provider receives first 100 items at once after significant delay (200+ ms) and adds them all together. If you
|
||||
* don't set this value, and you continue adding results in your completion provider (for example you get your next batch of results in
|
||||
* 500ms) the popup might be shown with significant delay.
|
||||
* don't set this value, and you continue adding results in your completion provider (for example, you get your next batch of results in
|
||||
* 500ms), the popup might be shown with significant delay.
|
||||
*
|
||||
* @param number The Number of items in lookup which trigger the popup. -1 means this functionality is disabled. Also specifying 0 makes
|
||||
* no sense since we can't add 0 items.
|
||||
@@ -563,13 +567,14 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
myUnfreezeAfterNItems = number;
|
||||
}
|
||||
|
||||
|
||||
void addDelayedMiddleMatches() {
|
||||
ArrayList<CompletionResult> delayed;
|
||||
synchronized (myDelayedMiddleMatches) {
|
||||
if (myDelayedMiddleMatches.isEmpty()) return;
|
||||
delayed = new ArrayList<>(myDelayedMiddleMatches);
|
||||
myDelayedMiddleMatches.clear();
|
||||
synchronized (delayedMiddleMatches) {
|
||||
if (delayedMiddleMatches.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
delayed = new ArrayList<>(delayedMiddleMatches);
|
||||
delayedMiddleMatches.clear();
|
||||
}
|
||||
for (CompletionResult item : delayed) {
|
||||
ProgressManager.checkCanceled();
|
||||
@@ -578,18 +583,18 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
}
|
||||
|
||||
public void closeAndFinish(boolean hideLookup) {
|
||||
if (!myLookup.isLookupDisposed()) {
|
||||
if (!lookup.isLookupDisposed()) {
|
||||
Lookup lookup = LookupManager.getActiveLookup(myEditor);
|
||||
if (lookup != null && lookup != myLookup && ClientId.isCurrentlyUnderLocalId()) {
|
||||
if (lookup != null && lookup != this.lookup && ClientId.isCurrentlyUnderLocalId()) {
|
||||
LOG.error("lookup changed: " + lookup + "; " + this);
|
||||
}
|
||||
}
|
||||
myLookup.removeLookupListener(myLookupListener);
|
||||
lookup.removeLookupListener(myLookupListener);
|
||||
finishCompletionProcess(true);
|
||||
CompletionServiceImpl.assertPhase(CompletionPhase.NoCompletion.getClass());
|
||||
|
||||
if (hideLookup) {
|
||||
myLookup.hideLookup(true);
|
||||
lookup.hideLookup(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,20 +602,21 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
cancel();
|
||||
|
||||
ThreadingAssertions.assertEventDispatchThread();
|
||||
Disposer.dispose(myQueue);
|
||||
Disposer.dispose(queue);
|
||||
LookupManager.getInstance(getProject()).removePropertyChangeListener(myLookupManagerListener);
|
||||
|
||||
CompletionServiceImpl
|
||||
.assertPhase(CompletionPhase.BgCalculation.class, CompletionPhase.ItemsCalculated.class, CompletionPhase.Synchronous.class,
|
||||
CompletionPhase.CommittingDocuments.class);
|
||||
CompletionServiceImpl.assertPhase(CompletionPhase.BgCalculation.class,
|
||||
CompletionPhase.ItemsCalculated.class,
|
||||
CompletionPhase.Synchronous.class,
|
||||
CompletionPhase.CommittingDocuments.class);
|
||||
|
||||
CompletionProgressIndicator currentCompletion = CompletionServiceImpl.getCurrentCompletionProgressIndicator();
|
||||
LOG.assertTrue(currentCompletion == this, currentCompletion + "!=" + this);
|
||||
|
||||
CompletionPhase oldPhase = CompletionServiceImpl.getCompletionPhase();
|
||||
if (oldPhase instanceof CompletionPhase.CommittingDocuments) {
|
||||
if (oldPhase instanceof CompletionPhase.CommittingDocuments p) {
|
||||
LOG.assertTrue(oldPhase.indicator != null, oldPhase);
|
||||
((CompletionPhase.CommittingDocuments)oldPhase).replaced = true;
|
||||
p.replaced = true;
|
||||
}
|
||||
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
|
||||
if (disposeOffsetMap) {
|
||||
@@ -652,13 +658,14 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
}
|
||||
|
||||
boolean blockingWaitForFinish(int timeoutMs) {
|
||||
if (myHandler.isTestingMode() && !TestModeFlags.is(CompletionAutoPopupHandler.ourTestingAutopopup)) {
|
||||
if (!myFinishSemaphore.waitFor(100 * 1000)) {
|
||||
if (handler.isTestingMode() && !TestModeFlags.is(CompletionAutoPopupHandler.ourTestingAutopopup)) {
|
||||
if (!finishSemaphore.waitFor(100 * 1000)) {
|
||||
throw new AssertionError("Too long completion");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (myFreezeSemaphore.waitFor(timeoutMs)) {
|
||||
|
||||
if (freezeSemaphore.waitFor(timeoutMs)) {
|
||||
// the completion is really finished, now we may auto-insert or show lookup
|
||||
return !isRunning() && !isCanceled();
|
||||
}
|
||||
@@ -669,18 +676,20 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
public void stop() {
|
||||
super.stop();
|
||||
|
||||
myQueue.cancelAllUpdates();
|
||||
myFreezeSemaphore.up();
|
||||
myFinishSemaphore.up();
|
||||
queue.cancelAllUpdates();
|
||||
freezeSemaphore.up();
|
||||
finishSemaphore.up();
|
||||
|
||||
ModalityUiUtil.invokeLaterIfNeeded(myQueue.getModalityState(), () -> {
|
||||
final CompletionPhase phase = CompletionServiceImpl.getCompletionPhase();
|
||||
if (!(phase instanceof CompletionPhase.BgCalculation) || phase.indicator != this) return;
|
||||
ModalityUiUtil.invokeLaterIfNeeded(queue.getModalityState(), () -> {
|
||||
CompletionPhase phase = CompletionServiceImpl.getCompletionPhase();
|
||||
if (!(phase instanceof CompletionPhase.BgCalculation) || phase.indicator != this) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.assertTrue(!getProject().isDisposed(), "project disposed");
|
||||
|
||||
if (myEditor.isDisposed()) {
|
||||
myLookup.hideLookup(false);
|
||||
lookup.hideLookup(false);
|
||||
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
|
||||
return;
|
||||
}
|
||||
@@ -689,20 +698,21 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
LOG.assertTrue(((EditorWindow)myEditor).getInjectedFile().isValid(), "injected file !valid");
|
||||
LOG.assertTrue(((DocumentWindow)myEditor.getDocument()).isValid(), "docWindow !valid");
|
||||
}
|
||||
PsiFile file = myLookup.getPsiFile();
|
||||
PsiFile file = lookup.getPsiFile();
|
||||
LOG.assertTrue(file == null || file.isValid(), "file !valid");
|
||||
|
||||
myLookup.setCalculating(false);
|
||||
lookup.setCalculating(false);
|
||||
|
||||
if (myCount == 0) {
|
||||
myLookup.hideLookup(false);
|
||||
if (!isAutopopupCompletion()) {
|
||||
final CompletionProgressIndicator current = CompletionServiceImpl.getCurrentCompletionProgressIndicator();
|
||||
if (count == 0) {
|
||||
lookup.hideLookup(false);
|
||||
if (isAutopopupCompletion()) {
|
||||
CompletionServiceImpl.setCompletionPhase(new CompletionPhase.EmptyAutoPopup(myEditor, myRestartingPrefixConditions));
|
||||
}
|
||||
else {
|
||||
CompletionProgressIndicator current = CompletionServiceImpl.getCurrentCompletionProgressIndicator();
|
||||
LOG.assertTrue(current == null, current + "!=" + this);
|
||||
|
||||
handleEmptyLookup(!((CompletionPhase.BgCalculation)phase).modifiersChanged);
|
||||
} else {
|
||||
CompletionServiceImpl.setCompletionPhase(new CompletionPhase.EmptyAutoPopup(myEditor, myRestartingPrefixConditions));
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -715,31 +725,30 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
}
|
||||
|
||||
private boolean hideAutopopupIfMeaningless() {
|
||||
if (!myLookup.isLookupDisposed() && isAutopopupCompletion() && !myLookup.isSelectionTouched() && !myLookup.isCalculating()) {
|
||||
myLookup.refreshUi(true, false);
|
||||
final List<LookupElement> items = myLookup.getItems();
|
||||
if (lookup.isLookupDisposed() || !isAutopopupCompletion() || lookup.isSelectionTouched() || lookup.isCalculating()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (LookupElement item : items) {
|
||||
if (!isAlreadyInTheEditor(item)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.isValid() && item.isWorthShowingInAutoPopup()) {
|
||||
return false;
|
||||
}
|
||||
lookup.refreshUi(true, false);
|
||||
for (LookupElement item : lookup.getItems()) {
|
||||
if (!isAlreadyInTheEditor(item)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
myLookup.hideLookup(false);
|
||||
LOG.assertTrue(CompletionServiceImpl.getCompletionService().getCurrentCompletion() == null);
|
||||
CompletionServiceImpl.setCompletionPhase(new CompletionPhase.EmptyAutoPopup(myEditor, myRestartingPrefixConditions));
|
||||
return true;
|
||||
if (item.isValid() && item.isWorthShowingInAutoPopup()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
lookup.hideLookup(false);
|
||||
LOG.assertTrue(CompletionServiceImpl.getCompletionService().getCurrentCompletion() == null);
|
||||
CompletionServiceImpl.setCompletionPhase(new CompletionPhase.EmptyAutoPopup(myEditor, myRestartingPrefixConditions));
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isAlreadyInTheEditor(LookupElement item) {
|
||||
Editor editor = myLookup.getEditor();
|
||||
int start = editor.getCaretModel().getOffset() - myLookup.itemPattern(item).length();
|
||||
Editor editor = lookup.getEditor();
|
||||
int start = editor.getCaretModel().getOffset() - lookup.itemPattern(item).length();
|
||||
Document document = editor.getDocument();
|
||||
return start >= 0 && StringUtil.startsWith(document.getImmutableCharSequence().subSequence(start, document.getTextLength()),
|
||||
item.getLookupString());
|
||||
@@ -773,7 +782,7 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isAutopopupCompletion() && !myLookup.mayBeNoticed()) {
|
||||
if (isAutopopupCompletion() && !lookup.mayBeNoticed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -839,7 +848,7 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
@Override
|
||||
public void scheduleRestart() {
|
||||
ThreadingAssertions.assertEventDispatchThread();
|
||||
if (myHandler.isTestingMode() && !TestModeFlags.is(CompletionAutoPopupHandler.ourTestingAutopopup)) {
|
||||
if (handler.isTestingMode() && !TestModeFlags.is(CompletionAutoPopupHandler.ourTestingAutopopup)) {
|
||||
closeAndFinish(false);
|
||||
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
|
||||
new CodeCompletionHandlerBase(myCompletionType, false, false, true).invokeCompletion(getProject(), myEditor, myInvocationCount);
|
||||
@@ -868,7 +877,7 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CompletionProgressIndicator[count=" +
|
||||
myCount +
|
||||
count +
|
||||
",phase=" +
|
||||
CompletionServiceImpl.getCompletionPhase() +
|
||||
"]@" +
|
||||
@@ -883,7 +892,7 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
LOG.assertTrue(!isAutopopupCompletion());
|
||||
|
||||
CompletionParameters parameters = getParameters();
|
||||
if (myHandler.invokedExplicitly && parameters != null) {
|
||||
if (handler.invokedExplicitly && parameters != null) {
|
||||
LightweightHint hint = showErrorHint(getProject(), getEditor(), getNoSuggestionsMessage(parameters));
|
||||
if (awaitSecondInvocation) {
|
||||
CompletionServiceImpl.setCompletionPhase(new CompletionPhase.NoSuggestionsHint(hint, this));
|
||||
@@ -976,9 +985,9 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp
|
||||
|
||||
@Override
|
||||
public void addAdvertisement(@NotNull String text, @Nullable Icon icon) {
|
||||
myAdvertiserChanges.offer(() -> myLookup.addAdvertisement(text, icon));
|
||||
myAdvertiserChanges.offer(() -> lookup.addAdvertisement(text, icon));
|
||||
|
||||
myQueue.queue(myUpdate);
|
||||
queue.queue(myUpdate);
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
package com.intellij.codeInsight.completion
|
||||
|
||||
import com.intellij.codeWithMe.ClientId
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.ex.ApplicationManagerEx
|
||||
import com.intellij.openapi.components.ComponentManagerEx
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
@@ -17,6 +16,7 @@ import com.intellij.util.concurrency.Semaphore
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import java.util.concurrent.LinkedBlockingQueue
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@@ -150,10 +150,10 @@ internal class AsyncCompletion(project: Project?) : CompletionThreadingBase() {
|
||||
batchList.add(result)
|
||||
}
|
||||
else {
|
||||
queue.offer(Computable {
|
||||
queue.offer {
|
||||
tryReadOrCancel(indicator) { indicator.addItem(result) }
|
||||
true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -180,11 +180,10 @@ internal class AsyncCompletion(project: Project?) : CompletionThreadingBase() {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("SSBasedInspection")
|
||||
@TestOnly
|
||||
internal fun checkForExceptions(future: Deferred<Unit>) {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
@Suppress("SSBasedInspection")
|
||||
runBlocking {
|
||||
future.await()
|
||||
}
|
||||
runBlocking {
|
||||
future.await()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.completion
|
||||
|
||||
import com.intellij.codeInsight.lookup.LookupElement
|
||||
@@ -11,7 +11,7 @@ import org.jetbrains.annotations.ApiStatus
|
||||
class FilteringResultSet(
|
||||
private val base: CompletionResultSet,
|
||||
private val filter: (CompletionContributor) -> Boolean
|
||||
) : CompletionResultSet(base.prefixMatcher, base.consumer, base.myContributor) {
|
||||
) : CompletionResultSet(base.prefixMatcher, base.consumer, base.contributor) {
|
||||
override fun addElement(element: LookupElement) {
|
||||
base.addElement(element)
|
||||
}
|
||||
@@ -64,7 +64,7 @@ class FilteringResultSet(
|
||||
consumer.consume(result)
|
||||
}
|
||||
}
|
||||
myCompletionService.getVariantsFromContributors(parameters, myContributor, prefixMatcher, batchConsumer, customSorter, filter)
|
||||
myCompletionService.getVariantsFromContributors(parameters, contributor, prefixMatcher, batchConsumer, customSorter, filter)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.completion
|
||||
|
||||
import com.intellij.codeInsight.completion.addingPolicy.ElementsAddingPolicy
|
||||
@@ -10,7 +10,7 @@ import org.jetbrains.annotations.ApiStatus
|
||||
class PolicyObeyingResultSet(
|
||||
private val originalResult: CompletionResultSet,
|
||||
private val policyHolder: () -> ElementsAddingPolicy
|
||||
) : CompletionResultSet(originalResult.prefixMatcher, originalResult.consumer, originalResult.myContributor) {
|
||||
) : CompletionResultSet(originalResult.prefixMatcher, originalResult.consumer, originalResult.contributor) {
|
||||
|
||||
override fun addElement(element: LookupElement) {
|
||||
policyHolder().addElement(originalResult, element)
|
||||
|
||||
@@ -26,12 +26,11 @@ import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.patterns.ElementPattern
|
||||
import com.intellij.platform.diagnostic.telemetry.TelemetryManager
|
||||
import com.intellij.platform.diagnostic.telemetry.helpers.runWithSpan
|
||||
import com.intellij.platform.diagnostic.telemetry.helpers.useWithScopeBlocking
|
||||
import com.intellij.psi.Weigher
|
||||
import com.intellij.util.Consumer
|
||||
import com.intellij.util.ExceptionUtil
|
||||
import com.intellij.util.concurrency.ThreadingAssertions
|
||||
import io.opentelemetry.api.trace.Span
|
||||
import kotlin.concurrent.Volatile
|
||||
|
||||
private val LOG = logger<CompletionServiceImpl>()
|
||||
@@ -53,13 +52,8 @@ open class CompletionServiceImpl : BaseCompletionService() {
|
||||
@JvmStatic
|
||||
fun assertPhase(vararg possibilities: Class<out CompletionPhase>) {
|
||||
val holder = tryGetClientCompletionService(getAppSession())?.completionPhaseHolder ?: DEFAULT_PHASE_HOLDER
|
||||
assertPhase(holder, *possibilities)
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private fun assertPhase(phaseHolder: CompletionPhaseHolder, vararg possibilities: Class<out CompletionPhase>) {
|
||||
if (!isPhase(phaseHolder.phase, *possibilities)) {
|
||||
reportPhase(phaseHolder)
|
||||
if (!isPhase(holder.phase, *possibilities)) {
|
||||
reportPhase(holder)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,13 +66,11 @@ open class CompletionServiceImpl : BaseCompletionService() {
|
||||
@JvmStatic
|
||||
var completionPhase: CompletionPhase
|
||||
get() {
|
||||
val clientCompletionService = tryGetClientCompletionService(getAppSession())
|
||||
if (clientCompletionService == null) return DEFAULT_PHASE_HOLDER.phase
|
||||
val clientCompletionService = tryGetClientCompletionService(getAppSession()) ?: return DEFAULT_PHASE_HOLDER.phase
|
||||
return clientCompletionService.completionPhase
|
||||
}
|
||||
set(phase) {
|
||||
val clientCompletionService = tryGetClientCompletionService(getAppSession())
|
||||
if (clientCompletionService == null) return
|
||||
val clientCompletionService = tryGetClientCompletionService(getAppSession()) ?: return
|
||||
clientCompletionService.completionPhase = phase
|
||||
}
|
||||
}
|
||||
@@ -89,9 +81,7 @@ open class CompletionServiceImpl : BaseCompletionService() {
|
||||
override fun projectClosing(project: Project) {
|
||||
val sessions = getAppSessions(ClientKind.ALL)
|
||||
for (session in sessions) {
|
||||
val clientCompletionService = tryGetClientCompletionService(session)
|
||||
if (clientCompletionService == null) continue
|
||||
|
||||
val clientCompletionService = tryGetClientCompletionService(session) ?: continue
|
||||
val indicator = clientCompletionService.currentCompletionProgressIndicator
|
||||
if (indicator != null && indicator.project === project) {
|
||||
indicator.closeAndFinish(true)
|
||||
@@ -107,8 +97,7 @@ open class CompletionServiceImpl : BaseCompletionService() {
|
||||
override fun beforePluginUnload(pluginDescriptor: IdeaPluginDescriptor, isUpdate: Boolean) {
|
||||
val sessions = getAppSessions(ClientKind.ALL)
|
||||
for (session in sessions) {
|
||||
val clientCompletionService = tryGetClientCompletionService(session)
|
||||
if (clientCompletionService == null) continue
|
||||
val clientCompletionService = tryGetClientCompletionService(session) ?: continue
|
||||
clientCompletionService.completionPhase = NoCompletion
|
||||
}
|
||||
}
|
||||
@@ -117,44 +106,48 @@ open class CompletionServiceImpl : BaseCompletionService() {
|
||||
|
||||
@Suppress("OVERRIDE_DEPRECATION")
|
||||
override fun setAdvertisementText(text: @NlsContexts.PopupAdvertisement String?) {
|
||||
if (text == null) return
|
||||
val completion = currentCompletionProgressIndicator
|
||||
completion?.addAdvertisement(text, null)
|
||||
if (text == null) {
|
||||
return
|
||||
}
|
||||
currentCompletionProgressIndicator?.addAdvertisement(text, null)
|
||||
}
|
||||
|
||||
override fun createResultSet(parameters: CompletionParameters,
|
||||
consumer: Consumer<in CompletionResult>,
|
||||
contributor: CompletionContributor,
|
||||
matcher: PrefixMatcher): CompletionResultSet {
|
||||
return CompletionResultSetImpl(consumer, matcher, contributor, parameters, null, null)
|
||||
return CompletionResultSetImpl(consumer = consumer,
|
||||
prefixMatcher = matcher,
|
||||
contributor = contributor,
|
||||
parameters = parameters,
|
||||
sorter = null,
|
||||
original = null)
|
||||
}
|
||||
|
||||
override fun getCurrentCompletion(): CompletionProcess? {
|
||||
val indicator = currentCompletionProgressIndicator
|
||||
if (indicator != null) {
|
||||
return indicator
|
||||
currentCompletionProgressIndicator?.let {
|
||||
return it
|
||||
}
|
||||
// TODO we have to move myApiCompletionProcess inside per client service somehow
|
||||
// also shouldn't we delegate here to base method instead of accessing the field of the base class?
|
||||
return if (isCurrentlyUnderLocalId) myApiCompletionProcess else null
|
||||
return if (isCurrentlyUnderLocalId) apiCompletionProcess else null
|
||||
}
|
||||
|
||||
private class CompletionResultSetImpl(consumer: Consumer<in CompletionResult>?, prefixMatcher: PrefixMatcher?,
|
||||
contributor: CompletionContributor?, parameters: CompletionParameters?,
|
||||
sorter: CompletionSorter?, original: CompletionResultSetImpl?) : BaseCompletionResultSet(consumer,
|
||||
prefixMatcher,
|
||||
contributor,
|
||||
parameters,
|
||||
sorter,
|
||||
original) {
|
||||
private class CompletionResultSetImpl(consumer: java.util.function.Consumer<in CompletionResult>?,
|
||||
prefixMatcher: PrefixMatcher?,
|
||||
contributor: CompletionContributor?,
|
||||
parameters: CompletionParameters?,
|
||||
sorter: CompletionSorter?,
|
||||
original: CompletionResultSetImpl?) :
|
||||
BaseCompletionResultSet(consumer, prefixMatcher, contributor, parameters, sorter, original) {
|
||||
override fun addAllElements(elements: Iterable<LookupElement>) {
|
||||
CompletionThreadingBase.withBatchUpdate({ super.addAllElements(elements) }, myParameters.process)
|
||||
CompletionThreadingBase.withBatchUpdate({ super.addAllElements(elements) }, parameters.process)
|
||||
}
|
||||
|
||||
override fun passResult(result: CompletionResult) {
|
||||
val element = result.lookupElement
|
||||
if (element != null && element.getUserData(LOOKUP_ELEMENT_CONTRIBUTOR) == null) {
|
||||
element.putUserData(LOOKUP_ELEMENT_CONTRIBUTOR, myContributor)
|
||||
element.putUserData(LOOKUP_ELEMENT_CONTRIBUTOR, contributor)
|
||||
}
|
||||
super.passResult(result)
|
||||
}
|
||||
@@ -164,11 +157,21 @@ open class CompletionServiceImpl : BaseCompletionService() {
|
||||
return this
|
||||
}
|
||||
|
||||
return CompletionResultSetImpl(consumer, matcher, myContributor, myParameters, mySorter, this)
|
||||
return CompletionResultSetImpl(consumer = consumer,
|
||||
prefixMatcher = matcher,
|
||||
contributor = contributor,
|
||||
parameters = parameters,
|
||||
sorter = sorter,
|
||||
original = this)
|
||||
}
|
||||
|
||||
override fun withRelevanceSorter(sorter: CompletionSorter): CompletionResultSet {
|
||||
return CompletionResultSetImpl(consumer, prefixMatcher, myContributor, myParameters, sorter, this)
|
||||
return CompletionResultSetImpl(consumer = consumer,
|
||||
prefixMatcher = prefixMatcher,
|
||||
contributor = contributor,
|
||||
parameters = parameters,
|
||||
sorter = sorter,
|
||||
original = this)
|
||||
}
|
||||
|
||||
override fun addLookupAdvertisement(text: String) {
|
||||
@@ -176,15 +179,14 @@ open class CompletionServiceImpl : BaseCompletionService() {
|
||||
}
|
||||
|
||||
override fun restartCompletionOnPrefixChange(prefixCondition: ElementPattern<String>) {
|
||||
val process = myParameters.process
|
||||
val process = parameters.process
|
||||
if (process is CompletionProcessBase) {
|
||||
process
|
||||
.addWatchedPrefix(myParameters.offset - prefixMatcher.prefix.length, prefixCondition)
|
||||
process.addWatchedPrefix(parameters.offset - prefixMatcher.prefix.length, prefixCondition)
|
||||
}
|
||||
}
|
||||
|
||||
override fun restartCompletionWhenNothingMatches() {
|
||||
val process = myParameters.process
|
||||
val process = parameters.process
|
||||
if (process is CompletionProgressIndicator) {
|
||||
process.lookup.isStartCompletionWhenNothingMatches = true
|
||||
}
|
||||
@@ -208,14 +210,15 @@ open class CompletionServiceImpl : BaseCompletionService() {
|
||||
}
|
||||
|
||||
override fun getVariantsFromContributor(params: CompletionParameters, contributor: CompletionContributor, result: CompletionResultSet) {
|
||||
runWithSpan(completionTracer, contributor.javaClass.simpleName) { span: Span ->
|
||||
super.getVariantsFromContributor(params, contributor, result)
|
||||
span.setAttribute("avoid_null_value", true)
|
||||
}
|
||||
completionTracer.spanBuilder(contributor.javaClass.simpleName)
|
||||
.setAttribute("avoid_null_value", true)
|
||||
.useWithScopeBlocking {
|
||||
super.getVariantsFromContributor(params, contributor, result)
|
||||
}
|
||||
}
|
||||
|
||||
override fun performCompletion(parameters: CompletionParameters, consumer: Consumer<in CompletionResult>) {
|
||||
runWithSpan(completionTracer, "performCompletion") { span: Span ->
|
||||
completionTracer.spanBuilder("performCompletion").useWithScopeBlocking { span ->
|
||||
val countingConsumer = object : Consumer<CompletionResult> {
|
||||
@JvmField
|
||||
var count: Int = 0
|
||||
@@ -248,10 +251,7 @@ private class ClientCompletionService(private val appSession: ClientAppSession)
|
||||
ThreadingAssertions.assertEventDispatchThread()
|
||||
val oldPhase = this.completionPhase
|
||||
val oldIndicator = oldPhase.indicator
|
||||
if (oldIndicator != null &&
|
||||
phase !is BgCalculation &&
|
||||
oldIndicator.isRunning &&
|
||||
!oldIndicator.isCanceled) {
|
||||
if (oldIndicator != null && phase !is BgCalculation && oldIndicator.isRunning && !oldIndicator.isCanceled) {
|
||||
LOG.error("don't change phase during running completion: oldPhase=$oldPhase")
|
||||
}
|
||||
val wasCompletionRunning = isRunningPhase(oldPhase)
|
||||
@@ -262,8 +262,7 @@ private class ClientCompletionService(private val appSession: ClientAppSession)
|
||||
}
|
||||
|
||||
Disposer.dispose(oldPhase)
|
||||
val phaseTrace = Throwable()
|
||||
completionPhaseHolder = CompletionPhaseHolder(phase = phase, phaseTrace = phaseTrace)
|
||||
completionPhaseHolder = CompletionPhaseHolder(phase = phase, phaseTrace = Throwable())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.testFramework.fixtures;
|
||||
|
||||
import com.intellij.codeInsight.completion.CompletionPhase;
|
||||
@@ -21,7 +22,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class CompletionAutoPopupTester {
|
||||
public final class CompletionAutoPopupTester {
|
||||
private final CodeInsightTestFixture myFixture;
|
||||
|
||||
public CompletionAutoPopupTester(CodeInsightTestFixture fixture) {
|
||||
@@ -44,9 +45,11 @@ public class CompletionAutoPopupTester {
|
||||
}
|
||||
|
||||
public void joinCompletion() {
|
||||
waitPhase(phase -> !(phase instanceof CompletionPhase.CommittingDocuments ||
|
||||
phase instanceof CompletionPhase.Synchronous ||
|
||||
phase instanceof CompletionPhase.BgCalculation));
|
||||
waitPhase(phase -> {
|
||||
return !(phase instanceof CompletionPhase.CommittingDocuments ||
|
||||
phase instanceof CompletionPhase.Synchronous ||
|
||||
phase instanceof CompletionPhase.BgCalculation);
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("UseOfSystemOutOrSystemErr")
|
||||
@@ -112,5 +115,4 @@ public class CompletionAutoPopupTester {
|
||||
joinCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user