Java tutorial
/* * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * 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.CodeInsightSettings; import com.intellij.codeInsight.TargetElementUtil; 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.diagnostic.PerformanceWatcher; import com.intellij.featureStatistics.FeatureUsageTracker; import com.intellij.injected.editor.DocumentWindow; import com.intellij.injected.editor.EditorWindow; import com.intellij.lang.Language; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.IdeActions; import com.intellij.openapi.application.AccessToken; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.Result; import com.intellij.openapi.application.WriteAction; 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.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.PsiFile; import com.intellij.psi.PsiReference; import com.intellij.psi.ReferenceRange; import com.intellij.psi.util.PsiUtilCore; import com.intellij.ui.LightweightHint; import com.intellij.util.Alarm; import com.intellij.util.ObjectUtils; import com.intellij.util.ThreeState; 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 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.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; /** * @author peter */ 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 LookupImpl myLookup; private final MergingUpdateQueue myQueue; private final Update myUpdate = new Update("update") { @Override public void run() { updateLookup(); myQueue.setMergingTimeSpan(300); } }; private final Semaphore myFreezeSemaphore; private final OffsetMap myOffsetMap; private final List<Pair<Integer, ElementPattern<String>>> myRestartingPrefixConditions = ContainerUtil .createLockFreeCopyOnWriteList(); private final LookupAdapter myLookupListener = new LookupAdapter() { @Override public void itemSelected(LookupEvent event) { finishCompletionProcess(false); LookupElement item = event.getItem(); if (item == null) return; setMergeCommand(); myHandler.lookupItemSelected(CompletionProgressIndicator.this, item, event.getCompletionChar(), myLookup.getItems()); } @Override public void lookupCanceled(final LookupEvent event) { finishCompletionProcess(true); } }; private volatile int myCount; private volatile boolean myHasPsiElements; private boolean myLookupUpdated; private final ConcurrentMap<LookupElement, CompletionSorterImpl> myItemSorters = ContainerUtil .newConcurrentMap(ContainerUtil.<LookupElement>identityStrategy()); private final PropertyChangeListener myLookupManagerListener; private final Queue<Runnable> myAdvertiserChanges = new ConcurrentLinkedQueue<Runnable>(); private final int myStartCaret; public CompletionProgressIndicator(final Editor editor, @NotNull Caret caret, CompletionParameters parameters, CodeCompletionHandlerBase handler, Semaphore freezeSemaphore, final OffsetMap offsetMap, boolean hasModifiers, LookupImpl lookup) { myEditor = editor; myCaret = caret; myParameters = parameters; myHandler = handler; myFreezeSemaphore = freezeSemaphore; myOffsetMap = offsetMap; myLookup = lookup; myStartCaret = myEditor.getCaretModel().getOffset(); myAdvertiserChanges.offer(new Runnable() { @Override public void run() { myLookup.getAdvertiser().clearAdvertisements(); } }); myLookup.setArranger(new CompletionLookupArranger(parameters, this)); myLookup.addLookupListener(myLookupListener); myLookup.setCalculating(true); myLookupManagerListener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent 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", 100, true, myEditor.getContentComponent()); myQueue.setPassThrough(false); ApplicationManager.getApplication().assertIsDispatchThread(); Disposer.register(this, offsetMap); if (hasModifiers && !ApplicationManager.getApplication().isUnitTestMode()) { trackModifiers(); } } public OffsetMap getOffsetMap() { return myOffsetMap; } public int getSelectionEndOffset() { return getOffsetMap().getOffset(CompletionInitializationContext.SELECTION_END_OFFSET); } void duringCompletion(CompletionInitializationContext initContext) { if (isAutopopupCompletion()) { if (shouldPreselectFirstSuggestion(myParameters)) { if (!CodeInsightSettings.getInstance().SELECT_AUTOPOPUP_SUGGESTIONS_BY_CHARS) { myLookup.setFocusDegree(LookupImpl.FocusDegree.SEMI_FOCUSED); 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); } } } else { myLookup.setFocusDegree(LookupImpl.FocusDegree.FOCUSED); } } 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); } } } else if (DumbService.isDumb(getProject())) { addAdvertisement("The results might be incomplete while indexing is in progress", MessageType.WARNING.getPopupBackground()); } ProgressManager.checkCanceled(); if (!initContext.getOffsetMap().wasModified(CompletionInitializationContext.IDENTIFIER_END_OFFSET)) { try { final int selectionEndOffset = initContext.getSelectionEndOffset(); final PsiReference reference = TargetElementUtil.findReference(myEditor, selectionEndOffset); if (reference != null) { initContext.setReplacementOffset(findReplacementOffset(selectionEndOffset, reference)); } } 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); } } @NotNull CompletionSorterImpl getSorter(LookupElement element) { return myItemSorters.get(element); } @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; } private boolean updateLookup() { ApplicationManager.getApplication().assertIsDispatchThread(); if (isOutdated() || !shouldShowLookup()) 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(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; } final boolean isInsideIdentifier() { return getIdentifierEndOffset() != getSelectionEndOffset(); } public int getIdentifierEndOffset() { return myOffsetMap.getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET); } public synchronized void addItem(final CompletionResult item) { if (!isRunning()) return; ProgressManager.checkCanceled(); final boolean unitTestMode = ApplicationManager.getApplication().isUnitTestMode(); if (!unitTestMode) { LOG.assertTrue(!ApplicationManager.getApplication().isDispatchThread()); } LOG.assertTrue(myParameters.getPosition().isValid()); LookupElement lookupElement = item.getLookupElement(); if (!myHasPsiElements && lookupElement.getPsiElement() != null) { myHasPsiElements = true; } myItemSorters.put(lookupElement, (CompletionSorterImpl) item.getSorter()); if (!myLookup.addItem(lookupElement, item.getPrefixMatcher())) { return; } myCount++; if (myCount == 1) { new Alarm(Alarm.ThreadToUse.SHARED_THREAD, this).addRequest(new Runnable() { @Override public void run() { myFreezeSemaphore.up(); } }, 300); } myQueue.queue(myUpdate); } 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) { LookupManager.getInstance(getProject()).hideActiveLookup(); } } 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() { // our offset map should be disposed under write action, so that duringCompletion (read action) won't access it after disposing AccessToken token = WriteAction.start(); try { Disposer.dispose(this); } finally { token.finish(); } } @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); } CompletionLookupArranger.cancelLastCompletionStatisticsUpdate(); } @Override public void stop() { super.stop(); myQueue.cancelAllUpdates(); myFreezeSemaphore.up(); ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { final CompletionPhase phase = CompletionServiceImpl.getCompletionPhase(); if (!(phase instanceof CompletionPhase.BgCalculation) || phase.indicator != CompletionProgressIndicator.this) return; LOG.assertTrue(!getProject().isDisposed(), "project disposed"); if (myEditor.isDisposed()) { LookupManager.getInstance(getProject()).hideActiveLookup(); 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) { LookupManager.getInstance(getProject()).hideActiveLookup(); if (!isAutopopupCompletion()) { final CompletionProgressIndicator current = CompletionServiceImpl.getCompletionService() .getCurrentCompletion(); LOG.assertTrue(current == null, current + "!=" + CompletionProgressIndicator.this); handleEmptyLookup(!((CompletionPhase.BgCalculation) phase).modifiersChanged); } } else { CompletionServiceImpl.setCompletionPhase( new CompletionPhase.ItemsCalculated(CompletionProgressIndicator.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 myHandler.autopopup; } @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(); 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(); ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { CompletionAutoPopupHandler.runLaterWithCommitted(project, myEditor.getDocument(), new Runnable() { @Override public void run() { if (phase.checkExpired()) return; CompletionAutoPopupHandler.invokeCompletion(myParameters.getCompletionType(), isAutopopupCompletion(), project, myEditor, myParameters.getInvocationCount(), true); } }); } }, project.getDisposed()); } @Override public String toString() { return "CompletionProgressIndicator[count=" + myCount + ",phase=" + CompletionServiceImpl.getCompletionPhase() + "]@" + System.identityHashCode(this); } protected void handleEmptyLookup(final boolean awaitSecondInvocation) { 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 = new EditorHintListener() { @Override public void hintShown(final Project project, final LightweightHint hint, final int flags) { result[0] = hint; } }; final MessageBusConnection connection = project.getMessageBus().connect(); connection.subscribe(EditorHintListener.TOPIC, listener); assert text != null; HintManager.getInstance().showErrorHint(editor, text, HintManager.UNDER); connection.disconnect(); return result[0]; } private static boolean shouldPreselectFirstSuggestion(CompletionParameters parameters) { if (!Registry.is("ide.completion.autopopup.choose.by.enter")) { return false; } if (!ApplicationManager.getApplication().isUnitTestMode()) { return true; } switch (CodeInsightSettings.getInstance().AUTOPOPUP_FOCUS_POLICY) { case CodeInsightSettings.ALWAYS: return true; case CodeInsightSettings.NEVER: return false; } final Language language = PsiUtilCore.getLanguageAtOffset(parameters.getPosition().getContainingFile(), parameters.getOffset()); for (CompletionConfidence confidence : CompletionConfidenceEP.forLanguage(language)) { //noinspection deprecation final ThreeState result = confidence.shouldFocusLookup(parameters); if (result != ThreeState.UNSURE) { LOG.debug(confidence + " has returned shouldFocusLookup=" + result); return result == ThreeState.YES; } } return false; } void startCompletion(final CompletionInitializationContext initContext) { boolean sync = ApplicationManager.getApplication().isUnitTestMode() && !CompletionAutoPopupHandler.ourTestingAutopopup; final CompletionThreading strategy = sync ? new SyncCompletion() : new AsyncCompletion(); strategy.startThread(ProgressWrapper.wrap(this), new Runnable() { @Override public void run() { scheduleAdvertising(); } }); final WeighingDelegate weigher = strategy.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); } } } strategy.startThread(this, new CalculateItems()); } private LookupElement[] calculateItems(CompletionInitializationContext initContext, WeighingDelegate weigher) { duringCompletion(initContext); ProgressManager.checkCanceled(); LookupElement[] result = CompletionService.getCompletionService().performCompletion(myParameters, weigher); ProgressManager.checkCanceled(); weigher.waitFor(); ProgressManager.checkCanceled(); return result; } public void addAdvertisement(@NotNull final String text, @Nullable final Color bgColor) { myAdvertiserChanges.offer(new Runnable() { @Override public void run() { myLookup.addAdvertisement(text, bgColor); } }); myQueue.queue(myUpdate); } 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); } } } } }