/*
* Copyright 2000-2016 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInsight.completion;
import com.intellij.codeInsight.AutoPopupController;
import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.codeInsight.completion.impl.CompletionServiceImpl;
import com.intellij.codeInsight.completion.impl.CompletionSorterImpl;
import com.intellij.codeInsight.editorActions.CompletionAutoPopupHandler;
import com.intellij.codeInsight.hint.EditorHintListener;
import com.intellij.codeInsight.hint.HintManager;
import com.intellij.codeInsight.lookup.*;
import com.intellij.codeInsight.lookup.impl.LookupImpl;
import com.intellij.concurrency.JobScheduler;
import com.intellij.diagnostic.PerformanceWatcher;
import com.intellij.featureStatistics.FeatureUsageTracker;
import com.intellij.injected.editor.DocumentWindow;
import com.intellij.injected.editor.EditorWindow;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.util.ProgressIndicatorBase;
import com.intellij.openapi.progress.util.ProgressWrapper;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.patterns.ElementPattern;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.psi.ReferenceRange;
import com.intellij.ui.GuiUtils;
import com.intellij.ui.LightweightHint;
import com.intellij.util.ObjectUtils;
import com.intellij.util.concurrency.Semaphore;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.ui.update.MergingUpdateQueue;
import com.intellij.util.ui.update.Update;
import consulo.codeInsight.TargetElementUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
/**
* Please don't use this class directly from plugins
*/
@SuppressWarnings("deprecation")
@Deprecated
public class CompletionProgressIndicator extends ProgressIndicatorBase implements CompletionProcess, Disposable {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.CompletionProgressIndicator");
private final Editor myEditor;
@NotNull
private final Caret myCaret;
private final CompletionParameters myParameters;
private final CodeCompletionHandlerBase myHandler;
private final CompletionLookupArranger myArranger;
private OffsetsInFile myHostOffsets;
private final LookupImpl myLookup;
private final MergingUpdateQueue myQueue;
private final Update myUpdate = new Update("update") {
@Override
public void run() {
updateLookup();
myQueue.setMergingTimeSpan(ourShowPopupGroupingTime);
}
};
private final Semaphore myFreezeSemaphore = new Semaphore(1);
private final Semaphore myFinishSemaphore = new Semaphore(1);
private final OffsetMap myOffsetMap;
private final Set<Pair<Integer, ElementPattern<String>>> myRestartingPrefixConditions = ContainerUtil.newConcurrentSet();
private final LookupAdapter myLookupListener = new LookupAdapter() {
@Override
public void lookupCanceled(final LookupEvent event) {
finishCompletionProcess(true);
}
};
private volatile boolean myIsUpdateSuppressed = false;
private static int ourInsertSingleItemTimeSpan = 300;
//temp external setters to make Rider autopopup more reactive
private static int ourShowPopupGroupingTime = 300;
private static int ourShowPopupAfterFirstItemGroupingTime = 100;
private volatile int myCount;
private volatile boolean myHasPsiElements;
private boolean myLookupUpdated;
private final PropertyChangeListener myLookupManagerListener;
private final Queue<Runnable> myAdvertiserChanges = new ConcurrentLinkedQueue<>();
private final List<CompletionResult> myDelayedMiddleMatches = ContainerUtil.newArrayList();
private final int myStartCaret;
private CompletionThreadingBase myStrategy;
public CompletionProgressIndicator(final Editor editor,
@NotNull Caret caret,
CompletionParameters parameters,
CodeCompletionHandlerBase handler,
final OffsetMap offsetMap,
OffsetsInFile hostOffsets,
boolean hasModifiers,
LookupImpl lookup) {
myEditor = editor;
myCaret = caret;
myParameters = parameters;
myHandler = handler;
myOffsetMap = offsetMap;
myHostOffsets = hostOffsets;
myLookup = lookup;
myStartCaret = myEditor.getCaretModel().getOffset();
myAdvertiserChanges.offer(() -> myLookup.getAdvertiser().clearAdvertisements());
myArranger = new CompletionLookupArranger(parameters, this);
myLookup.setArranger(myArranger);
myLookup.addLookupListener(myLookupListener);
myLookup.setCalculating(true);
myLookupManagerListener = evt -> {
if (evt.getNewValue() != null) {
LOG.error("An attempt to change the lookup during completion, phase = " + CompletionServiceImpl.getCompletionPhase());
}
};
LookupManager.getInstance(getProject()).addPropertyChangeListener(myLookupManagerListener);
myQueue = new MergingUpdateQueue("completion lookup progress", ourShowPopupAfterFirstItemGroupingTime, true, myEditor.getContentComponent());
myQueue.setPassThrough(false);
ApplicationManager.getApplication().assertIsDispatchThread();
if (hasModifiers && !ApplicationManager.getApplication().isUnitTestMode()) {
trackModifiers();
}
}
public void itemSelected(@Nullable LookupElement lookupItem, char completionChar) {
boolean dispose = lookupItem == null;
finishCompletionProcess(dispose);
if (dispose) return;
setMergeCommand();
myHandler.lookupItemSelected(this, lookupItem, completionChar, myLookup.getItems());
}
OffsetMap getOffsetMap() {
return myOffsetMap;
}
OffsetsInFile getHostOffsets() {
return myHostOffsets;
}
private int getSelectionEndOffset() {
return getOffsetMap().getOffset(CompletionInitializationContext.SELECTION_END_OFFSET);
}
void duringCompletion(CompletionInitializationContext initContext) {
if (isAutopopupCompletion() && shouldPreselectFirstSuggestion(myParameters)) {
myLookup.setFocusDegree(CodeInsightSettings.getInstance().SELECT_AUTOPOPUP_SUGGESTIONS_BY_CHARS
? LookupImpl.FocusDegree.FOCUSED
: LookupImpl.FocusDegree.SEMI_FOCUSED);
}
addDefaultAdvertisements();
ProgressManager.checkCanceled();
Document document = initContext.getEditor().getDocument();
if (!initContext.getOffsetMap().wasModified(CompletionInitializationContext.IDENTIFIER_END_OFFSET)) {
try {
final int selectionEndOffset = initContext.getSelectionEndOffset();
final PsiReference reference = TargetElementUtil.findReference(myEditor, selectionEndOffset);
if (reference != null) {
final int replacementOffset = findReplacementOffset(selectionEndOffset, reference);
if (replacementOffset > document.getTextLength()) {
LOG.error("Invalid replacementOffset: " + replacementOffset + " returned by reference " + reference + " of " + reference.getClass() +
"; doc=" + document +
"; doc actual=" + (document == initContext.getFile().getViewProvider().getDocument()) +
"; doc committed=" + PsiDocumentManager.getInstance(getProject()).isCommitted(document));
} else {
initContext.setReplacementOffset(replacementOffset);
}
}
}
catch (IndexNotReadyException ignored) {
}
}
for (CompletionContributor contributor : CompletionContributor.forLanguage(initContext.getPositionLanguage())) {
ProgressManager.checkCanceled();
if (DumbService.getInstance(initContext.getProject()).isDumb() && !DumbService.isDumbAware(contributor)) {
continue;
}
contributor.duringCompletion(initContext);
}
if (document instanceof DocumentWindow) {
myHostOffsets = new OffsetsInFile(initContext.getFile(), initContext.getOffsetMap()).toTopLevelFile();
}
}
private void addDefaultAdvertisements() {
if (DumbService.isDumb(getProject())) {
addAdvertisement("The results might be incomplete while indexing is in progress", MessageType.WARNING.getPopupBackground());
return;
}
advertiseTabReplacement();
if (isAutopopupCompletion()) {
if (shouldPreselectFirstSuggestion(myParameters) && !CodeInsightSettings.getInstance().SELECT_AUTOPOPUP_SUGGESTIONS_BY_CHARS) {
advertiseCtrlDot();
}
advertiseCtrlArrows();
}
}
private void advertiseTabReplacement() {
if (CompletionUtil.shouldShowFeature(myParameters, CodeCompletionFeatures.EDITING_COMPLETION_REPLACE) &&
myOffsetMap.getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET) != myOffsetMap.getOffset(CompletionInitializationContext.SELECTION_END_OFFSET)) {
String shortcut = CompletionContributor.getActionShortcut(IdeActions.ACTION_CHOOSE_LOOKUP_ITEM_REPLACE);
if (StringUtil.isNotEmpty(shortcut)) {
addAdvertisement("Use " + shortcut + " to overwrite the current identifier with the chosen variant", null);
}
}
}
private void advertiseCtrlDot() {
if (FeatureUsageTracker
.getInstance().isToBeAdvertisedInLookup(CodeCompletionFeatures.EDITING_COMPLETION_FINISH_BY_CONTROL_DOT, getProject())) {
String dotShortcut = CompletionContributor.getActionShortcut(IdeActions.ACTION_CHOOSE_LOOKUP_ITEM_DOT);
if (StringUtil.isNotEmpty(dotShortcut)) {
addAdvertisement("Press " + dotShortcut + " to choose the selected (or first) suggestion and insert a dot afterwards", null);
}
}
}
private void advertiseCtrlArrows() {
if (!myEditor.isOneLineMode() &&
FeatureUsageTracker.getInstance()
.isToBeAdvertisedInLookup(CodeCompletionFeatures.EDITING_COMPLETION_CONTROL_ARROWS, getProject())) {
String downShortcut = CompletionContributor.getActionShortcut(IdeActions.ACTION_LOOKUP_DOWN);
String upShortcut = CompletionContributor.getActionShortcut(IdeActions.ACTION_LOOKUP_UP);
if (StringUtil.isNotEmpty(downShortcut) && StringUtil.isNotEmpty(upShortcut)) {
addAdvertisement(downShortcut + " and " + upShortcut + " will move caret down and up in the editor", null);
}
}
}
@Override
public void dispose() {
}
private static int findReplacementOffset(int selectionEndOffset, PsiReference reference) {
final List<TextRange> ranges = ReferenceRange.getAbsoluteRanges(reference);
for (TextRange range : ranges) {
if (range.contains(selectionEndOffset)) {
return range.getEndOffset();
}
}
return selectionEndOffset;
}
void scheduleAdvertising() {
if (myLookup.isAvailableToUser()) {
return;
}
for (final CompletionContributor contributor : CompletionContributor.forParameters(myParameters)) {
if (!myLookup.isCalculating() && !myLookup.isVisible()) return;
@SuppressWarnings("deprecation") String s = contributor.advertise(myParameters);
if (s != null) {
addAdvertisement(s, null);
}
}
}
private boolean isOutdated() {
return CompletionServiceImpl.getCompletionPhase().indicator != this;
}
private void trackModifiers() {
assert !isAutopopupCompletion();
final JComponent contentComponent = myEditor.getContentComponent();
contentComponent.addKeyListener(new ModifierTracker(contentComponent));
}
public void setMergeCommand() {
CommandProcessor.getInstance().setCurrentCommandGroupId(getCompletionCommandName());
}
private String getCompletionCommandName() {
return "Completion" + hashCode();
}
public boolean showLookup() {
return updateLookup();
}
public CompletionParameters getParameters() {
return myParameters;
}
public CodeCompletionHandlerBase getHandler() {
return myHandler;
}
public LookupImpl getLookup() {
return myLookup;
}
public void withSingleUpdate(Runnable action) {
try {
myIsUpdateSuppressed = true;
action.run();
} finally {
myIsUpdateSuppressed = false;
myQueue.queue(myUpdate);
}
}
private boolean updateLookup() {
ApplicationManager.getApplication().assertIsDispatchThread();
if (isOutdated() || !shouldShowLookup() || myIsUpdateSuppressed) return false;
while (true) {
Runnable action = myAdvertiserChanges.poll();
if (action == null) break;
action.run();
}
if (!myLookupUpdated) {
if (myLookup.getAdvertisements().isEmpty() && !isAutopopupCompletion() && !DumbService.isDumb(getProject())) {
DefaultCompletionContributor.addDefaultAdvertisements(myParameters, myLookup, myHasPsiElements);
}
myLookup.getAdvertiser().showRandomText();
}
boolean justShown = false;
if (!myLookup.isShown()) {
if (hideAutopopupIfMeaningless()) {
return false;
}
if (Registry.is("dump.threads.on.empty.lookup") && myLookup.isCalculating() && myLookup.getItems().isEmpty()) {
PerformanceWatcher.getInstance().dumpThreads("emptyLookup/", true);
}
if (!myLookup.showLookup()) {
return false;
}
justShown = true;
}
myLookupUpdated = true;
myLookup.refreshUi(true, justShown);
hideAutopopupIfMeaningless();
if (justShown) {
myLookup.ensureSelectionVisible(true);
}
return true;
}
private boolean shouldShowLookup() {
if (isAutopopupCompletion()) {
if (myCount == 0) {
return false;
}
if (myLookup.isCalculating() && Registry.is("ide.completion.delay.autopopup.until.completed")) {
return false;
}
}
return true;
}
private boolean isInsideIdentifier() {
return getIdentifierEndOffset() != getSelectionEndOffset();
}
int getIdentifierEndOffset() {
return myOffsetMap.getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET);
}
void addItem(final CompletionResult item) {
if (!isRunning()) return;
ProgressManager.checkCanceled();
final boolean unitTestMode = ApplicationManager.getApplication().isUnitTestMode();
if (!unitTestMode) {
LOG.assertTrue(!ApplicationManager.getApplication().isDispatchThread());
}
LookupElement lookupElement = item.getLookupElement();
if (!myHasPsiElements && lookupElement.getPsiElement() != null) {
myHasPsiElements = true;
}
boolean allowMiddleMatches = myCount > CompletionLookupArranger.MAX_PREFERRED_COUNT * 2;
if (allowMiddleMatches) {
addDelayedMiddleMatches();
}
myArranger.associateSorter(lookupElement, (CompletionSorterImpl)item.getSorter());
if (item.isStartMatch() || allowMiddleMatches) {
addItemToLookup(item);
} else {
synchronized (myDelayedMiddleMatches) {
myDelayedMiddleMatches.add(item);
}
}
}
private void addItemToLookup(CompletionResult item) {
if (!myLookup.addItem(item.getLookupElement(), item.getPrefixMatcher())) {
return;
}
myCount++;
if (myCount == 1) {
JobScheduler.getScheduler().schedule(myFreezeSemaphore::up, ourInsertSingleItemTimeSpan, TimeUnit.MILLISECONDS);
}
myQueue.queue(myUpdate);
}
void addDelayedMiddleMatches() {
ArrayList<CompletionResult> delayed;
synchronized (myDelayedMiddleMatches) {
if (myDelayedMiddleMatches.isEmpty()) return;
delayed = ContainerUtil.newArrayList(myDelayedMiddleMatches);
myDelayedMiddleMatches.clear();
}
for (CompletionResult item : delayed) {
ProgressManager.checkCanceled();
addItemToLookup(item);
}
}
public void closeAndFinish(boolean hideLookup) {
if (!myLookup.isLookupDisposed()) {
Lookup lookup = LookupManager.getActiveLookup(myEditor);
LOG.assertTrue(lookup == myLookup, "lookup changed: " + lookup + "; " + this);
}
myLookup.removeLookupListener(myLookupListener);
finishCompletionProcess(true);
CompletionServiceImpl.assertPhase(CompletionPhase.NoCompletion.getClass());
if (hideLookup) {
myLookup.hideLookup(true);
}
}
private void finishCompletionProcess(boolean disposeOffsetMap) {
cancel();
ApplicationManager.getApplication().assertIsDispatchThread();
Disposer.dispose(myQueue);
LookupManager.getInstance(getProject()).removePropertyChangeListener(myLookupManagerListener);
CompletionProgressIndicator currentCompletion = CompletionServiceImpl.getCompletionService().getCurrentCompletion();
LOG.assertTrue(currentCompletion == this, currentCompletion + "!=" + this);
CompletionServiceImpl
.assertPhase(CompletionPhase.BgCalculation.class, CompletionPhase.ItemsCalculated.class, CompletionPhase.Synchronous.class,
CompletionPhase.CommittingDocuments.class);
CompletionPhase oldPhase = CompletionServiceImpl.getCompletionPhase();
if (oldPhase instanceof CompletionPhase.CommittingDocuments) {
LOG.assertTrue(((CompletionPhase.CommittingDocuments)oldPhase).isRestartingCompletion(), oldPhase);
((CompletionPhase.CommittingDocuments)oldPhase).replaced = true;
}
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
if (disposeOffsetMap) {
disposeIndicator();
}
}
void disposeIndicator() {
Disposer.dispose(this);
}
@TestOnly
public static void cleanupForNextTest() {
CompletionProgressIndicator currentCompletion = CompletionServiceImpl.getCompletionService().getCurrentCompletion();
if (currentCompletion != null) {
currentCompletion.finishCompletionProcess(true);
CompletionServiceImpl.assertPhase(CompletionPhase.NoCompletion.getClass());
}
else {
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
}
StatisticsUpdate.cancelLastCompletionStatisticsUpdate();
}
boolean blockingWaitForFinish(int timeoutMs) {
if (ApplicationManager.getApplication().isUnitTestMode() && !CompletionAutoPopupHandler.ourTestingAutopopup) {
if (!myFinishSemaphore.waitFor(100 * 1000)) {
throw new AssertionError("Too long completion");
}
return true;
}
if (myFreezeSemaphore.waitFor(timeoutMs)) {
// the completion is really finished, now we may auto-insert or show lookup
return !isRunning() && !isCanceled();
}
return false;
}
@Override
public void stop() {
super.stop();
myQueue.cancelAllUpdates();
myFreezeSemaphore.up();
myFinishSemaphore.up();
GuiUtils.invokeLaterIfNeeded(() -> {
final 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);
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
return;
}
if (myEditor instanceof EditorWindow) {
LOG.assertTrue(((EditorWindow)myEditor).getInjectedFile().isValid(), "injected file !valid");
LOG.assertTrue(((DocumentWindow)myEditor.getDocument()).isValid(), "docWindow !valid");
}
PsiFile file = myLookup.getPsiFile();
LOG.assertTrue(file == null || file.isValid(), "file !valid");
myLookup.setCalculating(false);
if (myCount == 0) {
myLookup.hideLookup(false);
if (!isAutopopupCompletion()) {
final CompletionProgressIndicator current = CompletionServiceImpl.getCompletionService().getCurrentCompletion();
LOG.assertTrue(current == null, current + "!=" + this);
handleEmptyLookup(!((CompletionPhase.BgCalculation)phase).modifiersChanged);
}
}
else {
CompletionServiceImpl.setCompletionPhase(new CompletionPhase.ItemsCalculated(this));
updateLookup();
}
}, myQueue.getModalityState());
}
private boolean hideAutopopupIfMeaningless() {
if (!myLookup.isLookupDisposed() && isAutopopupCompletion() && !myLookup.isSelectionTouched() && !myLookup.isCalculating()) {
myLookup.refreshUi(true, false);
final List<LookupElement> items = myLookup.getItems();
for (LookupElement item : items) {
if (!myLookup.itemPattern(item).equals(item.getLookupString())) {
return false;
}
if (item.isValid() && item.isWorthShowingInAutoPopup()) {
return false;
}
}
myLookup.hideLookup(false);
LOG.assertTrue(CompletionServiceImpl.getCompletionService().getCurrentCompletion() == null);
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
return true;
}
return false;
}
public boolean fillInCommonPrefix(final boolean explicit) {
if (isInsideIdentifier()) {
return false;
}
final Boolean aBoolean = new WriteCommandAction<Boolean>(getProject()) {
@Override
protected void run(@NotNull Result<Boolean> result) throws Throwable {
if (!explicit) {
setMergeCommand();
}
try {
result.setResult(myLookup.fillInCommonPrefix(explicit));
}
catch (Exception e) {
LOG.error(e);
}
}
}.execute().getResultObject();
return aBoolean.booleanValue();
}
public void restorePrefix(@NotNull final Runnable customRestore) {
new WriteCommandAction(getProject()) {
@Override
protected void run(@NotNull Result result) throws Throwable {
setMergeCommand();
customRestore.run();
}
}.execute();
}
public int nextInvocationCount(int invocation, boolean reused) {
return reused ? Math.max(getParameters().getInvocationCount() + 1, 2) : invocation;
}
public Editor getEditor() {
return myEditor;
}
@NotNull
public Caret getCaret() {
return myCaret;
}
public boolean isRepeatedInvocation(CompletionType completionType, Editor editor) {
if (completionType != myParameters.getCompletionType() || editor != myEditor) {
return false;
}
if (isAutopopupCompletion() && !myLookup.mayBeNoticed()) {
return false;
}
return true;
}
@Override
public boolean isAutopopupCompletion() {
return myParameters.getInvocationCount() == 0;
}
@NotNull
public Project getProject() {
return ObjectUtils.assertNotNull(myEditor.getProject());
}
public void addWatchedPrefix(int startOffset, ElementPattern<String> restartCondition) {
myRestartingPrefixConditions.add(Pair.create(startOffset, restartCondition));
}
public void prefixUpdated() {
final int caretOffset = myEditor.getCaretModel().getOffset();
if (caretOffset < myStartCaret) {
scheduleRestart();
myRestartingPrefixConditions.clear();
return;
}
final CharSequence text = myEditor.getDocument().getCharsSequence();
for (Pair<Integer, ElementPattern<String>> pair : myRestartingPrefixConditions) {
int start = pair.first;
if (caretOffset >= start && start >= 0) {
final String newPrefix = text.subSequence(start, caretOffset).toString();
if (pair.second.accepts(newPrefix)) {
scheduleRestart();
myRestartingPrefixConditions.clear();
return;
}
}
}
hideAutopopupIfMeaningless();
}
public void scheduleRestart() {
ApplicationManager.getApplication().assertIsDispatchThread();
if (ApplicationManager.getApplication().isUnitTestMode() && !CompletionAutoPopupHandler.ourTestingAutopopup) {
closeAndFinish(false);
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
new CodeCompletionHandlerBase(myParameters.getCompletionType(), false, false, true).invokeCompletion(getProject(), myEditor, myParameters.getInvocationCount());
return;
}
cancel();
final CompletionProgressIndicator current = CompletionServiceImpl.getCompletionService().getCurrentCompletion();
if (this != current) {
LOG.error(current + "!=" + this);
}
hideAutopopupIfMeaningless();
CompletionPhase oldPhase = CompletionServiceImpl.getCompletionPhase();
if (oldPhase instanceof CompletionPhase.CommittingDocuments) {
((CompletionPhase.CommittingDocuments)oldPhase).replaced = true;
}
final CompletionPhase.CommittingDocuments phase = new CompletionPhase.CommittingDocuments(this, myEditor);
CompletionServiceImpl.setCompletionPhase(phase);
phase.ignoreCurrentDocumentChange();
final Project project = getProject();
AutoPopupController.runTransactionWithEverythingCommitted(project, () -> {
if (phase.checkExpired()) return;
CompletionAutoPopupHandler.invokeCompletion(myParameters.getCompletionType(),
isAutopopupCompletion(), project, myEditor, myParameters.getInvocationCount(),
true);
});
}
@Override
public String toString() {
return "CompletionProgressIndicator[count=" +
myCount +
",phase=" +
CompletionServiceImpl.getCompletionPhase() +
"]@" +
System.identityHashCode(this);
}
protected void handleEmptyLookup(final boolean awaitSecondInvocation) {
if (isAutopopupCompletion() && ApplicationManager.getApplication().isUnitTestMode()) {
return;
}
LOG.assertTrue(!isAutopopupCompletion());
if (ApplicationManager.getApplication().isUnitTestMode() || !myHandler.invokedExplicitly) {
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
return;
}
for (final CompletionContributor contributor : CompletionContributor.forParameters(getParameters())) {
final String text = contributor.handleEmptyLookup(getParameters(), getEditor());
if (StringUtil.isNotEmpty(text)) {
LightweightHint hint = showErrorHint(getProject(), getEditor(), text);
CompletionServiceImpl.setCompletionPhase(
awaitSecondInvocation ? new CompletionPhase.NoSuggestionsHint(hint, this) : CompletionPhase.NoCompletion);
return;
}
}
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
}
private static LightweightHint showErrorHint(Project project, Editor editor, String text) {
final LightweightHint[] result = {null};
final EditorHintListener listener = (project1, hint, flags) -> result[0] = hint;
final MessageBusConnection connection = project.getMessageBus().connect();
connection.subscribe(EditorHintListener.TOPIC, listener);
assert text != null;
HintManager.getInstance().showErrorHint(editor, StringUtil.escapeXml(text), HintManager.UNDER);
connection.disconnect();
return result[0];
}
private static boolean shouldPreselectFirstSuggestion(CompletionParameters parameters) {
return true;
}
void startCompletion(final CompletionInitializationContext initContext) {
boolean sync = ApplicationManager.getApplication().isWriteAccessAllowed();
myStrategy = sync ? new SyncCompletion() : new AsyncCompletion();
myStrategy.startThread(ProgressWrapper.wrap(this), this::scheduleAdvertising);
final WeighingDelegate weigher = myStrategy.delegateWeighing(this);
class CalculateItems implements Runnable {
@Override
public void run() {
try {
calculateItems(initContext, weigher);
}
catch (ProcessCanceledException ignore) {
cancel(); // some contributor may just throw PCE; if indicator is not canceled everything will hang
}
catch (Throwable t) {
cancel();
LOG.error(t);
}
}
}
myStrategy.startThread(this, new CalculateItems());
}
private void calculateItems(CompletionInitializationContext initContext, WeighingDelegate weigher) {
duringCompletion(initContext);
ProgressManager.checkCanceled();
CompletionService.getCompletionService().performCompletion(myParameters, weigher);
ProgressManager.checkCanceled();
weigher.waitFor();
ProgressManager.checkCanceled();
}
@Nullable
CompletionThreadingBase getCompletionThreading() {
return myStrategy;
}
public void addAdvertisement(@NotNull final String text, @Nullable final Color bgColor) {
myAdvertiserChanges.offer(() -> myLookup.addAdvertisement(text, bgColor));
myQueue.queue(myUpdate);
}
@TestOnly
public static void setGroupingTimeSpan(int timeSpan) {
ourInsertSingleItemTimeSpan = timeSpan;
}
@Deprecated
public static void setAutopopupTriggerTime(int timeSpan) {
ourShowPopupGroupingTime = timeSpan;
ourShowPopupAfterFirstItemGroupingTime = timeSpan;
}
private static class ModifierTracker extends KeyAdapter {
private final JComponent myContentComponent;
public ModifierTracker(JComponent contentComponent) {
myContentComponent = contentComponent;
}
@Override
public void keyPressed(KeyEvent e) {
processModifier(e);
}
@Override
public void keyReleased(KeyEvent e) {
processModifier(e);
}
private void processModifier(KeyEvent e) {
final int code = e.getKeyCode();
if (code == KeyEvent.VK_CONTROL || code == KeyEvent.VK_META || code == KeyEvent.VK_ALT || code == KeyEvent.VK_SHIFT) {
myContentComponent.removeKeyListener(this);
final CompletionPhase phase = CompletionServiceImpl.getCompletionPhase();
if (phase instanceof CompletionPhase.BgCalculation) {
((CompletionPhase.BgCalculation)phase).modifiersChanged = true;
}
else if (phase instanceof CompletionPhase.InsertedSingleItem) {
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
}
}
}
}
}