create completion thread outside CompletionProgressIndicator main invocation
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / completion / CompletionProgressIndicator.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.codeInsight.completion;
18
19 import com.intellij.codeInsight.AutoPopupController;
20 import com.intellij.codeInsight.CodeInsightSettings;
21 import com.intellij.codeInsight.TargetElementUtil;
22 import com.intellij.codeInsight.completion.impl.CompletionServiceImpl;
23 import com.intellij.codeInsight.completion.impl.CompletionSorterImpl;
24 import com.intellij.codeInsight.editorActions.CompletionAutoPopupHandler;
25 import com.intellij.codeInsight.hint.EditorHintListener;
26 import com.intellij.codeInsight.hint.HintManager;
27 import com.intellij.codeInsight.lookup.*;
28 import com.intellij.codeInsight.lookup.impl.LookupImpl;
29 import com.intellij.concurrency.JobScheduler;
30 import com.intellij.diagnostic.PerformanceWatcher;
31 import com.intellij.featureStatistics.FeatureUsageTracker;
32 import com.intellij.injected.editor.DocumentWindow;
33 import com.intellij.injected.editor.EditorWindow;
34 import com.intellij.openapi.Disposable;
35 import com.intellij.openapi.actionSystem.IdeActions;
36 import com.intellij.openapi.application.ApplicationManager;
37 import com.intellij.openapi.command.CommandProcessor;
38 import com.intellij.openapi.command.WriteCommandAction;
39 import com.intellij.openapi.diagnostic.Logger;
40 import com.intellij.openapi.editor.Caret;
41 import com.intellij.openapi.editor.Document;
42 import com.intellij.openapi.editor.Editor;
43 import com.intellij.openapi.extensions.Extensions;
44 import com.intellij.openapi.progress.ProcessCanceledException;
45 import com.intellij.openapi.progress.ProgressManager;
46 import com.intellij.openapi.progress.util.ProgressIndicatorBase;
47 import com.intellij.openapi.progress.util.ProgressWrapper;
48 import com.intellij.openapi.project.DumbService;
49 import com.intellij.openapi.project.IndexNotReadyException;
50 import com.intellij.openapi.project.Project;
51 import com.intellij.openapi.ui.MessageType;
52 import com.intellij.openapi.util.Disposer;
53 import com.intellij.openapi.util.Pair;
54 import com.intellij.openapi.util.TextRange;
55 import com.intellij.openapi.util.registry.Registry;
56 import com.intellij.openapi.util.text.StringUtil;
57 import com.intellij.patterns.ElementPattern;
58 import com.intellij.psi.*;
59 import com.intellij.ui.GuiUtils;
60 import com.intellij.ui.LightweightHint;
61 import com.intellij.util.Alarm;
62 import com.intellij.util.ObjectUtils;
63 import com.intellij.util.concurrency.Semaphore;
64 import com.intellij.util.containers.ContainerUtil;
65 import com.intellij.util.messages.MessageBusConnection;
66 import com.intellij.util.ui.update.MergingUpdateQueue;
67 import com.intellij.util.ui.update.Update;
68 import org.jetbrains.annotations.NotNull;
69 import org.jetbrains.annotations.Nullable;
70 import org.jetbrains.annotations.TestOnly;
71
72 import javax.swing.*;
73 import java.awt.*;
74 import java.awt.event.KeyAdapter;
75 import java.awt.event.KeyEvent;
76 import java.beans.PropertyChangeListener;
77 import java.util.ArrayList;
78 import java.util.List;
79 import java.util.Queue;
80 import java.util.Set;
81 import java.util.concurrent.ConcurrentLinkedQueue;
82 import java.util.concurrent.TimeUnit;
83
84 /**
85  * Please don't use this class directly from plugins
86  */
87 @SuppressWarnings("deprecation")
88 @Deprecated
89 public class CompletionProgressIndicator extends ProgressIndicatorBase implements CompletionProcess, Disposable {
90   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.CompletionProgressIndicator");
91   private final Editor myEditor;
92   @NotNull
93   private final Caret myCaret;
94   @Nullable private CompletionParameters myParameters;
95   private final CodeCompletionHandlerBase myHandler;
96   private final CompletionLookupArranger myArranger;
97   private final CompletionType myCompletionType;
98   private final int myInvocationCount;
99   private OffsetsInFile myHostOffsets;
100   private final LookupImpl myLookup;
101   private final Alarm mySuppressTimeoutAlarm = new Alarm(this);
102   private final MergingUpdateQueue myQueue;
103   private final Update myUpdate = new Update("update") {
104     @Override
105     public void run() {
106       updateLookup(myIsUpdateSuppressed);
107       myQueue.setMergingTimeSpan(ourShowPopupGroupingTime);
108     }
109   };
110   private final Semaphore myFreezeSemaphore = new Semaphore(1);
111   private final Semaphore myFinishSemaphore = new Semaphore(1);
112   private final OffsetMap myOffsetMap;
113   private final Set<Pair<Integer, ElementPattern<String>>> myRestartingPrefixConditions = ContainerUtil.newConcurrentSet();
114   private final LookupAdapter myLookupListener = new LookupAdapter() {
115     @Override
116     public void lookupCanceled(final LookupEvent event) {
117       finishCompletionProcess(true);
118     }
119   };
120
121   private volatile boolean myIsUpdateSuppressed = false;
122   private static int ourInsertSingleItemTimeSpan = 300;
123
124   //temp external setters to make Rider autopopup more reactive
125   private static int ourShowPopupGroupingTime = 300;
126   private static int ourShowPopupAfterFirstItemGroupingTime = 100;
127
128   private volatile int myCount;
129   private volatile boolean myHasPsiElements;
130   private boolean myLookupUpdated;
131   private final PropertyChangeListener myLookupManagerListener;
132   private final Queue<Runnable> myAdvertiserChanges = new ConcurrentLinkedQueue<>();
133   private final List<CompletionResult> myDelayedMiddleMatches = ContainerUtil.newArrayList();
134   private final int myStartCaret;
135   private final CompletionThreadingBase myThreading;
136
137   CompletionProgressIndicator(Editor editor, @NotNull Caret caret, int invocationCount,
138                               CodeCompletionHandlerBase handler, OffsetMap offsetMap, OffsetsInFile hostOffsets,
139                               boolean hasModifiers, LookupImpl lookup, CompletionThreadingBase threading) {
140     myEditor = editor;
141     myCaret = caret;
142     myHandler = handler;
143     myCompletionType = handler.completionType;
144     myInvocationCount = invocationCount;
145     myOffsetMap = offsetMap;
146     myHostOffsets = hostOffsets;
147     myLookup = lookup;
148     myStartCaret = myEditor.getCaretModel().getOffset();
149     myThreading = threading;
150
151     myAdvertiserChanges.offer(() -> myLookup.getAdvertiser().clearAdvertisements());
152
153     myArranger = new CompletionLookupArranger(this);
154     myLookup.setArranger(myArranger);
155
156     myLookup.addLookupListener(myLookupListener);
157     myLookup.setCalculating(true);
158
159     myLookupManagerListener = evt -> {
160       if (evt.getNewValue() != null) {
161         LOG.error("An attempt to change the lookup during completion, phase = " + CompletionServiceImpl.getCompletionPhase());
162       }
163     };
164     LookupManager.getInstance(getProject()).addPropertyChangeListener(myLookupManagerListener);
165
166     myQueue = new MergingUpdateQueue("completion lookup progress", ourShowPopupAfterFirstItemGroupingTime, true, myEditor.getContentComponent());
167     myQueue.setPassThrough(false);
168
169     ApplicationManager.getApplication().assertIsDispatchThread();
170
171     if (hasModifiers && !ApplicationManager.getApplication().isUnitTestMode()) {
172       trackModifiers();
173     }
174   }
175
176   private CompletionParameters createCompletionParameters(OffsetsInFile offsets) {
177     int offset = offsets.getOffsets().getOffset(CompletionInitializationContext.START_OFFSET);
178     PsiFile fileCopy = offsets.getFile();
179     PsiFile originalFile = fileCopy.getOriginalFile();
180     PsiElement insertedElement = findCompletionPositionLeaf(offsets, offset, originalFile);
181     insertedElement.putUserData(CompletionContext.COMPLETION_CONTEXT_KEY, new CompletionContext(fileCopy, offsets.getOffsets()));
182     return new CompletionParameters(insertedElement, originalFile, myCompletionType, offset, myInvocationCount, myEditor, this);
183   }
184
185   @NotNull
186   private static PsiElement findCompletionPositionLeaf(OffsetsInFile offsets, int offset, PsiFile originalFile) {
187     PsiElement insertedElement = offsets.getFile().findElementAt(offset);
188     CompletionAssertions.assertCompletionPositionPsiConsistent(offsets, offset, originalFile, insertedElement);
189     return insertedElement;
190   }
191
192   void itemSelected(@Nullable LookupElement lookupItem, char completionChar) {
193     boolean dispose = lookupItem == null;
194     finishCompletionProcess(dispose);
195     if (dispose) return;
196
197     setMergeCommand();
198
199     myHandler.lookupItemSelected(this, lookupItem, completionChar, myLookup.getItems());
200   }
201
202   OffsetMap getOffsetMap() {
203     return myOffsetMap;
204   }
205
206   OffsetsInFile getHostOffsets() {
207     return myHostOffsets;
208   }
209
210   void duringCompletion(CompletionInitializationContext initContext, CompletionParameters parameters) {
211     if (isAutopopupCompletion() && shouldPreselectFirstSuggestion(parameters)) {
212       myLookup.setFocusDegree(CodeInsightSettings.getInstance().SELECT_AUTOPOPUP_SUGGESTIONS_BY_CHARS
213                               ? LookupImpl.FocusDegree.FOCUSED
214                               : LookupImpl.FocusDegree.SEMI_FOCUSED);
215     }
216     addDefaultAdvertisements(parameters);
217
218     ProgressManager.checkCanceled();
219
220     Document document = initContext.getEditor().getDocument();
221     if (!initContext.getOffsetMap().wasModified(CompletionInitializationContext.IDENTIFIER_END_OFFSET)) {
222       try {
223         final int selectionEndOffset = initContext.getSelectionEndOffset();
224         final PsiReference reference = TargetElementUtil.findReference(myEditor, selectionEndOffset);
225         if (reference != null) {
226           final int replacementOffset = findReplacementOffset(selectionEndOffset, reference);
227           if (replacementOffset > document.getTextLength()) {
228             LOG.error("Invalid replacementOffset: " + replacementOffset + " returned by reference " + reference + " of " + reference.getClass() + 
229                       "; doc=" + document + 
230                       "; doc actual=" + (document == initContext.getFile().getViewProvider().getDocument()) + 
231                       "; doc committed=" + PsiDocumentManager.getInstance(getProject()).isCommitted(document));
232           } else {
233             initContext.setReplacementOffset(replacementOffset);
234           }
235         }
236       }
237       catch (IndexNotReadyException ignored) {
238       }
239     }
240
241     for (CompletionContributor contributor : CompletionContributor.forLanguageHonorDumbness(initContext.getPositionLanguage(), initContext.getProject())) {
242       ProgressManager.checkCanceled();
243       contributor.duringCompletion(initContext);
244     }
245     if (document instanceof DocumentWindow) {
246       myHostOffsets = new OffsetsInFile(initContext.getFile(), initContext.getOffsetMap()).toTopLevelFile();
247     }
248   }
249   
250
251   private void addDefaultAdvertisements(CompletionParameters parameters) {
252     if (DumbService.isDumb(getProject())) {
253       addAdvertisement("The results might be incomplete while indexing is in progress", MessageType.WARNING.getPopupBackground());
254       return;
255     }
256     
257     advertiseTabReplacement(parameters);
258     if (isAutopopupCompletion()) {
259       if (shouldPreselectFirstSuggestion(parameters) && !CodeInsightSettings.getInstance().SELECT_AUTOPOPUP_SUGGESTIONS_BY_CHARS) {
260         advertiseCtrlDot();
261       }
262       advertiseCtrlArrows();
263     }
264   }
265
266   private void advertiseTabReplacement(CompletionParameters parameters) {
267     if (CompletionUtil.shouldShowFeature(parameters, CodeCompletionFeatures.EDITING_COMPLETION_REPLACE) &&
268       myOffsetMap.getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET) != myOffsetMap.getOffset(CompletionInitializationContext.SELECTION_END_OFFSET)) {
269       String shortcut = CompletionContributor.getActionShortcut(IdeActions.ACTION_CHOOSE_LOOKUP_ITEM_REPLACE);
270       if (StringUtil.isNotEmpty(shortcut)) {
271         addAdvertisement("Use " + shortcut + " to overwrite the current identifier with the chosen variant", null);
272       }
273     }
274   }
275
276   private void advertiseCtrlDot() {
277     if (FeatureUsageTracker
278       .getInstance().isToBeAdvertisedInLookup(CodeCompletionFeatures.EDITING_COMPLETION_FINISH_BY_CONTROL_DOT, getProject())) {
279       String dotShortcut = CompletionContributor.getActionShortcut(IdeActions.ACTION_CHOOSE_LOOKUP_ITEM_DOT);
280       if (StringUtil.isNotEmpty(dotShortcut)) {
281         addAdvertisement("Press " + dotShortcut + " to choose the selected (or first) suggestion and insert a dot afterwards", null);
282       }
283     }
284   }
285
286   private void advertiseCtrlArrows() {
287     if (!myEditor.isOneLineMode() &&
288         FeatureUsageTracker.getInstance()
289           .isToBeAdvertisedInLookup(CodeCompletionFeatures.EDITING_COMPLETION_CONTROL_ARROWS, getProject())) {
290       String downShortcut = CompletionContributor.getActionShortcut(IdeActions.ACTION_LOOKUP_DOWN);
291       String upShortcut = CompletionContributor.getActionShortcut(IdeActions.ACTION_LOOKUP_UP);
292       if (StringUtil.isNotEmpty(downShortcut) && StringUtil.isNotEmpty(upShortcut)) {
293         addAdvertisement(downShortcut + " and " + upShortcut + " will move caret down and up in the editor", null);
294       }
295     }
296   }
297
298   @Override
299   public void dispose() {
300   }
301
302   private static int findReplacementOffset(int selectionEndOffset, PsiReference reference) {
303     final List<TextRange> ranges = ReferenceRange.getAbsoluteRanges(reference);
304     for (TextRange range : ranges) {
305       if (range.contains(selectionEndOffset)) {
306         return range.getEndOffset();
307       }
308     }
309
310     return selectionEndOffset;
311   }
312
313
314   void scheduleAdvertising(CompletionParameters parameters) {
315     if (myLookup.isAvailableToUser()) {
316       return;
317     }
318     for (CompletionContributor contributor : CompletionContributor.forParameters(parameters)) {
319       if (!myLookup.isCalculating() && !myLookup.isVisible()) return;
320
321       @SuppressWarnings("deprecation") String s = contributor.advertise(parameters);
322       if (s != null) {
323         addAdvertisement(s, null);
324       }
325     }
326   }
327
328   private boolean isOutdated() {
329     return CompletionServiceImpl.getCompletionPhase().indicator != this;
330   }
331
332   private void trackModifiers() {
333     assert !isAutopopupCompletion();
334
335     final JComponent contentComponent = myEditor.getContentComponent();
336     contentComponent.addKeyListener(new ModifierTracker(contentComponent));
337   }
338
339   void setMergeCommand() {
340     CommandProcessor.getInstance().setCurrentCommandGroupId(getCompletionCommandName());
341   }
342
343   private String getCompletionCommandName() {
344     return "Completion" + hashCode();
345   }
346
347   void showLookup() {
348     updateLookup(myIsUpdateSuppressed);
349   }
350
351   // non-null when running generators and adding elements to lookup
352   @Nullable
353   public CompletionParameters getParameters() {
354     return myParameters;
355   }
356
357   public LookupImpl getLookup() {
358     return myLookup;
359   }
360
361   void withSingleUpdate(Runnable action) {
362     try {
363       myIsUpdateSuppressed = true;
364       action.run();
365     } finally {
366       myIsUpdateSuppressed = false;
367       myQueue.queue(myUpdate);
368     }
369   }
370
371   private void updateLookup(boolean isUpdateSuppressed) {
372     ApplicationManager.getApplication().assertIsDispatchThread();
373     if (isOutdated() || !shouldShowLookup() || isUpdateSuppressed) return;
374
375     while (true) {
376       Runnable action = myAdvertiserChanges.poll();
377       if (action == null) break;
378       action.run();
379     }
380
381     if (!myLookupUpdated) {
382       if (myLookup.getAdvertisements().isEmpty() && !isAutopopupCompletion() && !DumbService.isDumb(getProject())) {
383         DefaultCompletionContributor.addDefaultAdvertisements(myLookup, myHasPsiElements);
384       }
385       myLookup.getAdvertiser().showRandomText();
386     }
387
388     boolean justShown = false;
389     if (!myLookup.isShown()) {
390       if (hideAutopopupIfMeaningless()) {
391         return;
392       }
393
394       if (Registry.is("dump.threads.on.empty.lookup") && myLookup.isCalculating() && myLookup.getItems().isEmpty()) {
395         PerformanceWatcher.getInstance().dumpThreads("emptyLookup/", true);
396       }
397
398       if (!myLookup.showLookup()) {
399         return;
400       }
401       justShown = true;
402     }
403     myLookupUpdated = true;
404     myLookup.refreshUi(true, justShown);
405     hideAutopopupIfMeaningless();
406     if (justShown) {
407       myLookup.ensureSelectionVisible(true);
408     }
409   }
410
411   private boolean shouldShowLookup() {
412     if (isAutopopupCompletion()) {
413       if (myCount == 0) {
414         return false;
415       }
416       if (myLookup.isCalculating() && Registry.is("ide.completion.delay.autopopup.until.completed")) {
417         return false;
418       }
419     }
420     return true;
421   }
422
423   int getIdentifierEndOffset() {
424     return myOffsetMap.getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET);
425   }
426
427   void addItem(CompletionResult item) {
428     if (!isRunning()) return;
429     ProgressManager.checkCanceled();
430
431     final boolean unitTestMode = ApplicationManager.getApplication().isUnitTestMode();
432     if (!unitTestMode) {
433       LOG.assertTrue(!ApplicationManager.getApplication().isDispatchThread());
434     }
435
436     LookupElement lookupElement = item.getLookupElement();
437     if (!myHasPsiElements && lookupElement.getPsiElement() != null) {
438       myHasPsiElements = true;
439     }
440
441     boolean allowMiddleMatches = myCount > CompletionLookupArranger.MAX_PREFERRED_COUNT * 2;
442     if (allowMiddleMatches) {
443       addDelayedMiddleMatches();
444     }
445
446     myArranger.associateSorter(lookupElement, (CompletionSorterImpl)item.getSorter());
447     if (item.isStartMatch() || allowMiddleMatches) {
448       addItemToLookup(item);
449     } else {
450       synchronized (myDelayedMiddleMatches) {
451         myDelayedMiddleMatches.add(item);
452       }
453     }
454   }
455
456   private void addItemToLookup(CompletionResult item) {
457     if (!myLookup.addItem(item.getLookupElement(), item.getPrefixMatcher())) {
458       return;
459     }
460     //noinspection NonAtomicOperationOnVolatileField
461     myCount++; // invoked from a single thread
462
463     if (myCount == 1) {
464       JobScheduler.getScheduler().schedule(myFreezeSemaphore::up, ourInsertSingleItemTimeSpan, TimeUnit.MILLISECONDS);
465     }
466     myQueue.queue(myUpdate);
467   }
468
469   void addDelayedMiddleMatches() {
470     ArrayList<CompletionResult> delayed;
471     synchronized (myDelayedMiddleMatches) {
472       if (myDelayedMiddleMatches.isEmpty()) return;
473       delayed = ContainerUtil.newArrayList(myDelayedMiddleMatches);
474       myDelayedMiddleMatches.clear();
475     }
476     for (CompletionResult item : delayed) {
477       ProgressManager.checkCanceled();
478       addItemToLookup(item);
479     }
480   }
481
482   public void closeAndFinish(boolean hideLookup) {
483     if (!myLookup.isLookupDisposed()) {
484       Lookup lookup = LookupManager.getActiveLookup(myEditor);
485       LOG.assertTrue(lookup == myLookup, "lookup changed: " + lookup + "; " + this);
486     }
487     myLookup.removeLookupListener(myLookupListener);
488     finishCompletionProcess(true);
489     CompletionServiceImpl.assertPhase(CompletionPhase.NoCompletion.getClass());
490
491     if (hideLookup) {
492       myLookup.hideLookup(true);
493     }
494   }
495
496   private void finishCompletionProcess(boolean disposeOffsetMap) {
497     cancel();
498
499     ApplicationManager.getApplication().assertIsDispatchThread();
500     Disposer.dispose(myQueue);
501     LookupManager.getInstance(getProject()).removePropertyChangeListener(myLookupManagerListener);
502
503     CompletionProgressIndicator currentCompletion = CompletionServiceImpl.getCompletionService().getCurrentCompletion();
504     LOG.assertTrue(currentCompletion == this, currentCompletion + "!=" + this);
505
506     CompletionServiceImpl
507       .assertPhase(CompletionPhase.BgCalculation.class, CompletionPhase.ItemsCalculated.class, CompletionPhase.Synchronous.class,
508                    CompletionPhase.CommittingDocuments.class);
509     CompletionPhase oldPhase = CompletionServiceImpl.getCompletionPhase();
510     if (oldPhase instanceof CompletionPhase.CommittingDocuments) {
511       LOG.assertTrue(((CompletionPhase.CommittingDocuments)oldPhase).isRestartingCompletion(), oldPhase);
512       ((CompletionPhase.CommittingDocuments)oldPhase).replaced = true;
513     }
514     CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
515     if (disposeOffsetMap) {
516       disposeIndicator();
517     }
518   }
519
520   void disposeIndicator() {
521     Disposer.dispose(this);
522   }
523
524   @TestOnly
525   public static void cleanupForNextTest() {
526     CompletionProgressIndicator currentCompletion = CompletionServiceImpl.getCompletionService().getCurrentCompletion();
527     if (currentCompletion != null) {
528       currentCompletion.finishCompletionProcess(true);
529       CompletionServiceImpl.assertPhase(CompletionPhase.NoCompletion.getClass());
530     }
531     else {
532       CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
533     }
534     StatisticsUpdate.cancelLastCompletionStatisticsUpdate();
535   }
536
537   boolean blockingWaitForFinish(int timeoutMs) {
538     if (ApplicationManager.getApplication().isUnitTestMode() && !CompletionAutoPopupHandler.ourTestingAutopopup) {
539       if (!myFinishSemaphore.waitFor(100 * 1000)) {
540         throw new AssertionError("Too long completion");
541       }
542       return true;
543     }
544     if (myFreezeSemaphore.waitFor(timeoutMs)) {
545       // the completion is really finished, now we may auto-insert or show lookup
546       return !isRunning() && !isCanceled();
547     }
548     return false;
549   }
550
551   @Override
552   public void stop() {
553     super.stop();
554
555     myQueue.cancelAllUpdates();
556     myFreezeSemaphore.up();
557     myFinishSemaphore.up();
558
559     GuiUtils.invokeLaterIfNeeded(() -> {
560       final CompletionPhase phase = CompletionServiceImpl.getCompletionPhase();
561       if (!(phase instanceof CompletionPhase.BgCalculation) || phase.indicator != this) return;
562
563       LOG.assertTrue(!getProject().isDisposed(), "project disposed");
564
565       if (myEditor.isDisposed()) {
566         myLookup.hideLookup(false);
567         CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
568         return;
569       }
570
571       if (myEditor instanceof EditorWindow) {
572         LOG.assertTrue(((EditorWindow)myEditor).getInjectedFile().isValid(), "injected file !valid");
573         LOG.assertTrue(((DocumentWindow)myEditor.getDocument()).isValid(), "docWindow !valid");
574       }
575       PsiFile file = myLookup.getPsiFile();
576       LOG.assertTrue(file == null || file.isValid(), "file !valid");
577
578       myLookup.setCalculating(false);
579
580       if (myCount == 0) {
581         myLookup.hideLookup(false);
582         if (!isAutopopupCompletion()) {
583           final CompletionProgressIndicator current = CompletionServiceImpl.getCompletionService().getCurrentCompletion();
584           LOG.assertTrue(current == null, current + "!=" + this);
585
586           handleEmptyLookup(!((CompletionPhase.BgCalculation)phase).modifiersChanged);
587         }
588       }
589       else {
590         CompletionServiceImpl.setCompletionPhase(new CompletionPhase.ItemsCalculated(this));
591         updateLookup(myIsUpdateSuppressed);
592       }
593     }, myQueue.getModalityState());
594   }
595
596   private boolean hideAutopopupIfMeaningless() {
597     if (!myLookup.isLookupDisposed() && isAutopopupCompletion() && !myLookup.isSelectionTouched() && !myLookup.isCalculating()) {
598       myLookup.refreshUi(true, false);
599       final List<LookupElement> items = myLookup.getItems();
600
601       for (LookupElement item : items) {
602         if (!myLookup.itemPattern(item).equals(item.getLookupString())) {
603           return false;
604         }
605
606         if (item.isValid() && item.isWorthShowingInAutoPopup()) {
607           return false;
608         }
609       }
610
611       myLookup.hideLookup(false);
612       LOG.assertTrue(CompletionServiceImpl.getCompletionService().getCurrentCompletion() == null);
613       CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
614       return true;
615     }
616     return false;
617   }
618
619   void restorePrefix(@NotNull Runnable customRestore) {
620     WriteCommandAction.runWriteCommandAction(getProject(), () -> {
621       setMergeCommand();
622       customRestore.run();
623     });
624   }
625
626   int nextInvocationCount(int invocation, boolean reused) {
627     return reused ? Math.max(myInvocationCount + 1, 2) : invocation;
628   }
629
630   public Editor getEditor() {
631     return myEditor;
632   }
633
634   @NotNull
635   Caret getCaret() {
636     return myCaret;
637   }
638
639   boolean isRepeatedInvocation(CompletionType completionType, Editor editor) {
640     if (completionType != myCompletionType || editor != myEditor) {
641       return false;
642     }
643
644     if (isAutopopupCompletion() && !myLookup.mayBeNoticed()) {
645       return false;
646     }
647
648     return true;
649   }
650
651   @Override
652   public boolean isAutopopupCompletion() {
653     return myInvocationCount == 0;
654   }
655
656   @NotNull
657   public Project getProject() {
658     return ObjectUtils.assertNotNull(myEditor.getProject());
659   }
660
661   public void addWatchedPrefix(int startOffset, ElementPattern<String> restartCondition) {
662     myRestartingPrefixConditions.add(Pair.create(startOffset, restartCondition));
663   }
664
665   public void prefixUpdated() {
666     final int caretOffset = myEditor.getCaretModel().getOffset();
667     if (caretOffset < myStartCaret) {
668       scheduleRestart();
669       myRestartingPrefixConditions.clear();
670       return;
671     }
672
673     final CharSequence text = myEditor.getDocument().getCharsSequence();
674     for (Pair<Integer, ElementPattern<String>> pair : myRestartingPrefixConditions) {
675       int start = pair.first;
676       if (caretOffset >= start && start >= 0) {
677         final String newPrefix = text.subSequence(start, caretOffset).toString();
678         if (pair.second.accepts(newPrefix)) {
679           scheduleRestart();
680           myRestartingPrefixConditions.clear();
681           return;
682         }
683       }
684     }
685
686     hideAutopopupIfMeaningless();
687   }
688
689   public void scheduleRestart() {
690     ApplicationManager.getApplication().assertIsDispatchThread();
691     if (ApplicationManager.getApplication().isUnitTestMode() && !CompletionAutoPopupHandler.ourTestingAutopopup) {
692       closeAndFinish(false);
693       PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
694       new CodeCompletionHandlerBase(myCompletionType, false, false, true).invokeCompletion(getProject(), myEditor, myInvocationCount);
695       return;
696     }
697
698     cancel();
699
700     final CompletionProgressIndicator current = CompletionServiceImpl.getCompletionService().getCurrentCompletion();
701     if (this != current) {
702       LOG.error(current + "!=" + this);
703     }
704
705     hideAutopopupIfMeaningless();
706
707     CompletionPhase oldPhase = CompletionServiceImpl.getCompletionPhase();
708     if (oldPhase instanceof CompletionPhase.CommittingDocuments) {
709       ((CompletionPhase.CommittingDocuments)oldPhase).replaced = true;
710     }
711
712     final CompletionPhase.CommittingDocuments phase = new CompletionPhase.CommittingDocuments(this, myEditor);
713     CompletionServiceImpl.setCompletionPhase(phase);
714     phase.ignoreCurrentDocumentChange();
715
716     final Project project = getProject();
717     AutoPopupController.runTransactionWithEverythingCommitted(project, () -> {
718       if (phase.checkExpired()) return;
719
720       CompletionAutoPopupHandler.invokeCompletion(myCompletionType,
721                                                   isAutopopupCompletion(), project, myEditor, myInvocationCount,
722                                                   true);
723     });
724   }
725
726   @Override
727   public String toString() {
728     return "CompletionProgressIndicator[count=" +
729            myCount +
730            ",phase=" +
731            CompletionServiceImpl.getCompletionPhase() +
732            "]@" +
733            System.identityHashCode(this);
734   }
735
736   void handleEmptyLookup(boolean awaitSecondInvocation) {
737     if (isAutopopupCompletion() && ApplicationManager.getApplication().isUnitTestMode()) {
738       return;
739     }
740
741     LOG.assertTrue(!isAutopopupCompletion());
742
743     if (ApplicationManager.getApplication().isUnitTestMode() || !myHandler.invokedExplicitly) {
744       CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
745       return;
746     }
747
748     CompletionParameters parameters = getParameters();
749     if (parameters != null && runContributorsOnEmptyLookup(awaitSecondInvocation, parameters)) {
750       return;
751     }
752     CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
753   }
754
755   private boolean runContributorsOnEmptyLookup(boolean awaitSecondInvocation, CompletionParameters parameters) {
756     for (CompletionContributor contributor : CompletionContributor.forParameters(parameters)) {
757       final String text = contributor.handleEmptyLookup(parameters, getEditor());
758       if (StringUtil.isNotEmpty(text)) {
759         LightweightHint hint = showErrorHint(getProject(), getEditor(), text);
760         CompletionServiceImpl.setCompletionPhase(
761           awaitSecondInvocation ? new CompletionPhase.NoSuggestionsHint(hint, this) : CompletionPhase.NoCompletion);
762         return true;
763       }
764     }
765     return false;
766   }
767
768   private static LightweightHint showErrorHint(Project project, Editor editor, String text) {
769     final LightweightHint[] result = {null};
770     final EditorHintListener listener = (project1, hint, flags) -> result[0] = hint;
771     final MessageBusConnection connection = project.getMessageBus().connect();
772     connection.subscribe(EditorHintListener.TOPIC, listener);
773     assert text != null;
774     HintManager.getInstance().showErrorHint(editor, StringUtil.escapeXml(text), HintManager.UNDER);
775     connection.disconnect();
776     return result[0];
777   }
778
779   private static boolean shouldPreselectFirstSuggestion(CompletionParameters parameters) {
780     if (Registry.is("ide.completion.lookup.element.preselect.depends.on.context")) {
781       for (CompletionPreselectionBehaviourProvider provider : Extensions.getExtensions(CompletionPreselectionBehaviourProvider.EP_NAME)) {
782         if (!provider.shouldPreselectFirstSuggestion(parameters)) {
783           return false;
784         }
785       }
786     }
787
788     return true;
789   }
790
791   void runContributors(CompletionInitializationContext initContext, OffsetsInFile offsets) {
792     CompletionParameters parameters = createCompletionParameters(offsets);
793     myParameters = parameters;
794
795     myThreading.startThread(ProgressWrapper.wrap(this), ()-> AsyncCompletion.tryReadOrCancel(this, () -> scheduleAdvertising(parameters)));
796     WeighingDelegate weigher = myThreading.delegateWeighing(this);
797
798     try {
799       calculateItems(initContext, weigher, parameters);
800     }
801     catch (ProcessCanceledException ignore) {
802       cancel(); // some contributor may just throw PCE; if indicator is not canceled everything will hang
803     }
804     catch (Throwable t) {
805       cancel();
806       LOG.error(t);
807     }
808   }
809
810   private void calculateItems(CompletionInitializationContext initContext, WeighingDelegate weigher, CompletionParameters parameters) {
811     duringCompletion(initContext, parameters);
812     ProgressManager.checkCanceled();
813
814     CompletionService.getCompletionService().performCompletion(parameters, weigher);
815     ProgressManager.checkCanceled();
816
817     weigher.waitFor();
818     ProgressManager.checkCanceled();
819   }
820
821   @Nullable
822   CompletionThreadingBase getCompletionThreading() {
823     return myThreading;
824   }
825
826   public void addAdvertisement(@NotNull final String text, @Nullable final Color bgColor) {
827     myAdvertiserChanges.offer(() -> myLookup.addAdvertisement(text, bgColor));
828
829     myQueue.queue(myUpdate);
830   }
831
832   @SuppressWarnings("unused") // for Rider
833   @TestOnly
834   public static void setGroupingTimeSpan(int timeSpan) {
835     ourInsertSingleItemTimeSpan = timeSpan;
836   }
837
838   @Deprecated
839   public static void setAutopopupTriggerTime(int timeSpan) {
840     ourShowPopupGroupingTime = timeSpan;
841     ourShowPopupAfterFirstItemGroupingTime = timeSpan;
842   }
843
844   void makeSureLookupIsShown(int timeout) {
845     mySuppressTimeoutAlarm.addRequest(this::showIfSuppressed, timeout);
846   }
847
848   private void showIfSuppressed() {
849     ApplicationManager.getApplication().assertIsDispatchThread();
850
851     if(myLookup.isShown())
852       return;
853
854     updateLookup(false);
855   }
856
857   private static class ModifierTracker extends KeyAdapter {
858     private final JComponent myContentComponent;
859
860     public ModifierTracker(JComponent contentComponent) {
861       myContentComponent = contentComponent;
862     }
863
864     @Override
865     public void keyPressed(KeyEvent e) {
866       processModifier(e);
867     }
868
869     @Override
870     public void keyReleased(KeyEvent e) {
871       processModifier(e);
872     }
873
874     private void processModifier(KeyEvent e) {
875       final int code = e.getKeyCode();
876       if (code == KeyEvent.VK_CONTROL || code == KeyEvent.VK_META || code == KeyEvent.VK_ALT || code == KeyEvent.VK_SHIFT) {
877         myContentComponent.removeKeyListener(this);
878         final CompletionPhase phase = CompletionServiceImpl.getCompletionPhase();
879         if (phase instanceof CompletionPhase.BgCalculation) {
880           ((CompletionPhase.BgCalculation)phase).modifiersChanged = true;
881         }
882         else if (phase instanceof CompletionPhase.InsertedSingleItem) {
883           CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
884         }
885       }
886     }
887   }
888 }