| /* |
| * 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.TargetElementUtilBase; |
| 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.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.ConcurrentHashMap; |
| 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; |
| |
| /** |
| * @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; |
| 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(); |
| } |
| }; |
| 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 ConcurrentHashMap<LookupElement, CompletionSorterImpl> myItemSorters = |
| new ConcurrentHashMap<LookupElement, CompletionSorterImpl>( |
| ContainerUtil.<LookupElement>identityStrategy()); |
| private final PropertyChangeListener myLookupManagerListener; |
| private final Queue<Runnable> myAdvertiserChanges = new ConcurrentLinkedQueue<Runnable>(); |
| private final int myStartCaret; |
| |
| public CompletionProgressIndicator(final Editor editor, |
| CompletionParameters parameters, |
| CodeCompletionHandlerBase handler, |
| Semaphore freezeSemaphore, |
| final OffsetMap offsetMap, |
| boolean hasModifiers, |
| LookupImpl lookup) { |
| myEditor = editor; |
| 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", 300, 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 = TargetElementUtilBase.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); |
| } |
| } |
| } |
| |
| @Override |
| public void cancel() { |
| super.cancel(); |
| } |
| |
| 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; |
| } |
| |
| 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); |
| } |
| } |
| } |
| } |
| } |