platofrm: set fsnotifier glibc compatibility only for i386/amd64
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / completion / CompletionProgressIndicator.java
1 /*
2  * Copyright 2000-2014 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.CodeInsightSettings;
20 import com.intellij.codeInsight.TargetElementUtilBase;
21 import com.intellij.codeInsight.completion.impl.CompletionServiceImpl;
22 import com.intellij.codeInsight.completion.impl.CompletionSorterImpl;
23 import com.intellij.codeInsight.editorActions.CompletionAutoPopupHandler;
24 import com.intellij.codeInsight.hint.EditorHintListener;
25 import com.intellij.codeInsight.hint.HintManager;
26 import com.intellij.codeInsight.lookup.*;
27 import com.intellij.codeInsight.lookup.impl.LookupImpl;
28 import com.intellij.diagnostic.PerformanceWatcher;
29 import com.intellij.featureStatistics.FeatureUsageTracker;
30 import com.intellij.injected.editor.DocumentWindow;
31 import com.intellij.injected.editor.EditorWindow;
32 import com.intellij.lang.Language;
33 import com.intellij.openapi.Disposable;
34 import com.intellij.openapi.actionSystem.IdeActions;
35 import com.intellij.openapi.application.AccessToken;
36 import com.intellij.openapi.application.ApplicationManager;
37 import com.intellij.openapi.application.Result;
38 import com.intellij.openapi.application.WriteAction;
39 import com.intellij.openapi.command.CommandProcessor;
40 import com.intellij.openapi.command.WriteCommandAction;
41 import com.intellij.openapi.diagnostic.Logger;
42 import com.intellij.openapi.editor.Caret;
43 import com.intellij.openapi.editor.Editor;
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.PsiFile;
59 import com.intellij.psi.PsiReference;
60 import com.intellij.psi.ReferenceRange;
61 import com.intellij.psi.util.PsiUtilCore;
62 import com.intellij.ui.LightweightHint;
63 import com.intellij.util.Alarm;
64 import com.intellij.util.ObjectUtils;
65 import com.intellij.util.ThreeState;
66 import com.intellij.util.concurrency.Semaphore;
67 import com.intellij.util.containers.ContainerUtil;
68 import com.intellij.util.messages.MessageBusConnection;
69 import com.intellij.util.ui.update.MergingUpdateQueue;
70 import com.intellij.util.ui.update.Update;
71 import org.jetbrains.annotations.NotNull;
72 import org.jetbrains.annotations.Nullable;
73 import org.jetbrains.annotations.TestOnly;
74
75 import javax.swing.*;
76 import java.awt.*;
77 import java.awt.event.KeyAdapter;
78 import java.awt.event.KeyEvent;
79 import java.beans.PropertyChangeEvent;
80 import java.beans.PropertyChangeListener;
81 import java.util.List;
82 import java.util.Queue;
83 import java.util.concurrent.ConcurrentLinkedQueue;
84 import java.util.concurrent.ConcurrentMap;
85
86 /**
87  * @author peter
88  */
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   private final CompletionParameters myParameters;
95   private final CodeCompletionHandlerBase myHandler;
96   private final LookupImpl myLookup;
97   private final MergingUpdateQueue myQueue;
98   private final Update myUpdate = new Update("update") {
99     @Override
100     public void run() {
101       updateLookup();
102       myQueue.setMergingTimeSpan(300);
103     }
104   };
105   private final Semaphore myFreezeSemaphore;
106   private final OffsetMap myOffsetMap;
107   private final List<Pair<Integer, ElementPattern<String>>> myRestartingPrefixConditions = ContainerUtil.createLockFreeCopyOnWriteList();
108   private final LookupAdapter myLookupListener = new LookupAdapter() {
109     @Override
110     public void itemSelected(LookupEvent event) {
111       finishCompletionProcess(false);
112
113       LookupElement item = event.getItem();
114       if (item == null) return;
115
116       setMergeCommand();
117
118       myHandler.lookupItemSelected(CompletionProgressIndicator.this, item, event.getCompletionChar(), myLookup.getItems());
119     }
120
121
122     @Override
123     public void lookupCanceled(final LookupEvent event) {
124       finishCompletionProcess(true);
125     }
126   };
127   private volatile int myCount;
128   private volatile boolean myHasPsiElements;
129   private boolean myLookupUpdated;
130   private final ConcurrentMap<LookupElement, CompletionSorterImpl> myItemSorters =
131     ContainerUtil.newConcurrentMap(ContainerUtil.<LookupElement>identityStrategy());
132   private final PropertyChangeListener myLookupManagerListener;
133   private final Queue<Runnable> myAdvertiserChanges = new ConcurrentLinkedQueue<Runnable>();
134   private final int myStartCaret;
135
136   public CompletionProgressIndicator(final Editor editor,
137                                      @NotNull Caret caret,
138                                      CompletionParameters parameters,
139                                      CodeCompletionHandlerBase handler,
140                                      Semaphore freezeSemaphore,
141                                      final OffsetMap offsetMap,
142                                      boolean hasModifiers,
143                                      LookupImpl lookup) {
144     myEditor = editor;
145     myCaret = caret;
146     myParameters = parameters;
147     myHandler = handler;
148     myFreezeSemaphore = freezeSemaphore;
149     myOffsetMap = offsetMap;
150     myLookup = lookup;
151     myStartCaret = myEditor.getCaretModel().getOffset();
152
153     myAdvertiserChanges.offer(new Runnable() {
154       @Override
155       public void run() {
156         myLookup.getAdvertiser().clearAdvertisements();
157       }
158     });
159
160     myLookup.setArranger(new CompletionLookupArranger(parameters, this));
161
162     myLookup.addLookupListener(myLookupListener);
163     myLookup.setCalculating(true);
164
165     myLookupManagerListener = new PropertyChangeListener() {
166       @Override
167       public void propertyChange(PropertyChangeEvent evt) {
168         if (evt.getNewValue() != null) {
169           LOG.error("An attempt to change the lookup during completion, phase = " + CompletionServiceImpl.getCompletionPhase());
170         }
171       }
172     };
173     LookupManager.getInstance(getProject()).addPropertyChangeListener(myLookupManagerListener);
174
175     myQueue = new MergingUpdateQueue("completion lookup progress", 100, true, myEditor.getContentComponent());
176     myQueue.setPassThrough(false);
177
178     ApplicationManager.getApplication().assertIsDispatchThread();
179     Disposer.register(this, offsetMap);
180
181     if (hasModifiers && !ApplicationManager.getApplication().isUnitTestMode()) {
182       trackModifiers();
183     }
184   }
185
186   public OffsetMap getOffsetMap() {
187     return myOffsetMap;
188   }
189
190   public int getSelectionEndOffset() {
191     return getOffsetMap().getOffset(CompletionInitializationContext.SELECTION_END_OFFSET);
192   }
193
194   void duringCompletion(CompletionInitializationContext initContext) {
195     if (isAutopopupCompletion()) {
196       if (shouldPreselectFirstSuggestion(myParameters)) {
197         if (!CodeInsightSettings.getInstance().SELECT_AUTOPOPUP_SUGGESTIONS_BY_CHARS) {
198           myLookup.setFocusDegree(LookupImpl.FocusDegree.SEMI_FOCUSED);
199           if (FeatureUsageTracker.getInstance().isToBeAdvertisedInLookup(CodeCompletionFeatures.EDITING_COMPLETION_FINISH_BY_CONTROL_DOT, getProject())) {
200             String dotShortcut = CompletionContributor.getActionShortcut(IdeActions.ACTION_CHOOSE_LOOKUP_ITEM_DOT);
201             if (StringUtil.isNotEmpty(dotShortcut)) {
202               addAdvertisement("Press " + dotShortcut + " to choose the selected (or first) suggestion and insert a dot afterwards", null);
203             }
204           }
205         } else {
206           myLookup.setFocusDegree(LookupImpl.FocusDegree.FOCUSED);
207         }
208       }
209       if (!myEditor.isOneLineMode() &&
210           FeatureUsageTracker.getInstance()
211             .isToBeAdvertisedInLookup(CodeCompletionFeatures.EDITING_COMPLETION_CONTROL_ARROWS, getProject())) {
212         String downShortcut = CompletionContributor.getActionShortcut(IdeActions.ACTION_LOOKUP_DOWN);
213         String upShortcut = CompletionContributor.getActionShortcut(IdeActions.ACTION_LOOKUP_UP);
214         if (StringUtil.isNotEmpty(downShortcut) && StringUtil.isNotEmpty(upShortcut)) {
215           addAdvertisement(downShortcut + " and " + upShortcut + " will move caret down and up in the editor", null);
216         }
217       }
218     } else if (DumbService.isDumb(getProject())) {
219       addAdvertisement("The results might be incomplete while indexing is in progress", MessageType.WARNING.getPopupBackground());
220     }
221
222     ProgressManager.checkCanceled();
223
224     if (!initContext.getOffsetMap().wasModified(CompletionInitializationContext.IDENTIFIER_END_OFFSET)) {
225       try {
226         final int selectionEndOffset = initContext.getSelectionEndOffset();
227         final PsiReference reference = TargetElementUtilBase.findReference(myEditor, selectionEndOffset);
228         if (reference != null) {
229           initContext.setReplacementOffset(findReplacementOffset(selectionEndOffset, reference));
230         }
231       }
232       catch (IndexNotReadyException ignored) {
233       }
234     }
235
236     for (CompletionContributor contributor : CompletionContributor.forLanguage(initContext.getPositionLanguage())) {
237       ProgressManager.checkCanceled();
238       if (DumbService.getInstance(initContext.getProject()).isDumb() && !DumbService.isDumbAware(contributor)) {
239         continue;
240       }
241
242       contributor.duringCompletion(initContext);
243     }
244   }
245
246   @NotNull
247   CompletionSorterImpl getSorter(LookupElement element) {
248     return myItemSorters.get(element);
249   }
250
251   @Override
252   public void dispose() {
253   }
254
255   private static int findReplacementOffset(int selectionEndOffset, PsiReference reference) {
256     final List<TextRange> ranges = ReferenceRange.getAbsoluteRanges(reference);
257     for (TextRange range : ranges) {
258       if (range.contains(selectionEndOffset)) {
259         return range.getEndOffset();
260       }
261     }
262
263     return selectionEndOffset;
264   }
265
266
267   void scheduleAdvertising() {
268     if (myLookup.isAvailableToUser()) {
269       return;
270     }
271     for (final CompletionContributor contributor : CompletionContributor.forParameters(myParameters)) {
272       if (!myLookup.isCalculating() && !myLookup.isVisible()) return;
273
274       @SuppressWarnings("deprecation") String s = contributor.advertise(myParameters);
275       if (s != null) {
276         addAdvertisement(s, null);
277       }
278     }
279   }
280
281   private boolean isOutdated() {
282     return CompletionServiceImpl.getCompletionPhase().indicator != this;
283   }
284
285   private void trackModifiers() {
286     assert !isAutopopupCompletion();
287
288     final JComponent contentComponent = myEditor.getContentComponent();
289     contentComponent.addKeyListener(new ModifierTracker(contentComponent));
290   }
291
292   public void setMergeCommand() {
293     CommandProcessor.getInstance().setCurrentCommandGroupId(getCompletionCommandName());
294   }
295
296   private String getCompletionCommandName() {
297     return "Completion" + hashCode();
298   }
299
300   public boolean showLookup() {
301     return updateLookup();
302   }
303
304   public CompletionParameters getParameters() {
305     return myParameters;
306   }
307
308   public CodeCompletionHandlerBase getHandler() {
309     return myHandler;
310   }
311
312   public LookupImpl getLookup() {
313     return myLookup;
314   }
315
316   private boolean updateLookup() {
317     ApplicationManager.getApplication().assertIsDispatchThread();
318     if (isOutdated() || !shouldShowLookup()) return false;
319
320     while (true) {
321       Runnable action = myAdvertiserChanges.poll();
322       if (action == null) break;
323       action.run();
324     }
325
326     if (!myLookupUpdated) {
327       if (myLookup.getAdvertisements().isEmpty() && !isAutopopupCompletion() && !DumbService.isDumb(getProject())) {
328         DefaultCompletionContributor.addDefaultAdvertisements(myParameters, myLookup, myHasPsiElements);
329       }
330       myLookup.getAdvertiser().showRandomText();
331     }
332
333     boolean justShown = false;
334     if (!myLookup.isShown()) {
335       if (hideAutopopupIfMeaningless()) {
336         return false;
337       }
338
339       if (Registry.is("dump.threads.on.empty.lookup") && myLookup.isCalculating() && myLookup.getItems().isEmpty()) {
340         PerformanceWatcher.getInstance().dumpThreads("emptyLookup/", true);
341       }
342
343       if (!myLookup.showLookup()) {
344         return false;
345       }
346       justShown = true;
347     }
348     myLookupUpdated = true;
349     myLookup.refreshUi(true, justShown);
350     hideAutopopupIfMeaningless();
351     if (justShown) {
352       myLookup.ensureSelectionVisible(true);
353     }
354     return true;
355   }
356
357   private boolean shouldShowLookup() {
358     if (isAutopopupCompletion()) {
359       if (myCount == 0) {
360         return false;
361       }
362       if (myLookup.isCalculating() && Registry.is("ide.completion.delay.autopopup.until.completed")) {
363         return false;
364       }
365     }
366     return true;
367   }
368
369   final boolean isInsideIdentifier() {
370     return getIdentifierEndOffset() != getSelectionEndOffset();
371   }
372
373   public int getIdentifierEndOffset() {
374     return myOffsetMap.getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET);
375   }
376
377   public synchronized void addItem(final CompletionResult item) {
378     if (!isRunning()) return;
379     ProgressManager.checkCanceled();
380
381     final boolean unitTestMode = ApplicationManager.getApplication().isUnitTestMode();
382     if (!unitTestMode) {
383       LOG.assertTrue(!ApplicationManager.getApplication().isDispatchThread());
384     }
385
386     LOG.assertTrue(myParameters.getPosition().isValid());
387
388     LookupElement lookupElement = item.getLookupElement();
389     if (!myHasPsiElements && lookupElement.getPsiElement() != null) {
390       myHasPsiElements = true;
391     }
392     myItemSorters.put(lookupElement, (CompletionSorterImpl)item.getSorter());
393     if (!myLookup.addItem(lookupElement, item.getPrefixMatcher())) {
394       return;
395     }
396     myCount++;
397
398     if (myCount == 1) {
399       new Alarm(Alarm.ThreadToUse.SHARED_THREAD, this).addRequest(new Runnable() {
400         @Override
401         public void run() {
402           myFreezeSemaphore.up();
403         }
404       }, 300);
405     }
406     myQueue.queue(myUpdate);
407   }
408
409   public void closeAndFinish(boolean hideLookup) {
410     if (!myLookup.isLookupDisposed()) {
411       Lookup lookup = LookupManager.getActiveLookup(myEditor);
412       LOG.assertTrue(lookup == myLookup, "lookup changed: " + lookup + "; " + this);
413     }
414     myLookup.removeLookupListener(myLookupListener);
415     finishCompletionProcess(true);
416     CompletionServiceImpl.assertPhase(CompletionPhase.NoCompletion.getClass());
417
418     if (hideLookup) {
419       LookupManager.getInstance(getProject()).hideActiveLookup();
420     }
421   }
422
423   private void finishCompletionProcess(boolean disposeOffsetMap) {
424     cancel();
425
426     ApplicationManager.getApplication().assertIsDispatchThread();
427     Disposer.dispose(myQueue);
428     LookupManager.getInstance(getProject()).removePropertyChangeListener(myLookupManagerListener);
429
430     CompletionProgressIndicator currentCompletion = CompletionServiceImpl.getCompletionService().getCurrentCompletion();
431     LOG.assertTrue(currentCompletion == this, currentCompletion + "!=" + this);
432
433     CompletionServiceImpl
434       .assertPhase(CompletionPhase.BgCalculation.class, CompletionPhase.ItemsCalculated.class, CompletionPhase.Synchronous.class,
435                    CompletionPhase.CommittingDocuments.class);
436     CompletionPhase oldPhase = CompletionServiceImpl.getCompletionPhase();
437     if (oldPhase instanceof CompletionPhase.CommittingDocuments) {
438       LOG.assertTrue(((CompletionPhase.CommittingDocuments)oldPhase).isRestartingCompletion(), oldPhase);
439       ((CompletionPhase.CommittingDocuments)oldPhase).replaced = true;
440     }
441     CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
442     if (disposeOffsetMap) {
443       disposeIndicator();
444     }
445   }
446
447   void disposeIndicator() {
448     // our offset map should be disposed under write action, so that duringCompletion (read action) won't access it after disposing
449     AccessToken token = WriteAction.start();
450     try {
451       Disposer.dispose(this);
452     }
453     finally {
454       token.finish();
455     }
456   }
457
458   @TestOnly
459   public static void cleanupForNextTest() {
460     CompletionProgressIndicator currentCompletion = CompletionServiceImpl.getCompletionService().getCurrentCompletion();
461     if (currentCompletion != null) {
462       currentCompletion.finishCompletionProcess(true);
463       CompletionServiceImpl.assertPhase(CompletionPhase.NoCompletion.getClass());
464     }
465     else {
466       CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
467     }
468     CompletionLookupArranger.cancelLastCompletionStatisticsUpdate();
469   }
470
471   @Override
472   public void stop() {
473     super.stop();
474
475     myQueue.cancelAllUpdates();
476     myFreezeSemaphore.up();
477
478     ApplicationManager.getApplication().invokeLater(new Runnable() {
479       @Override
480       public void run() {
481         final CompletionPhase phase = CompletionServiceImpl.getCompletionPhase();
482         if (!(phase instanceof CompletionPhase.BgCalculation) || phase.indicator != CompletionProgressIndicator.this) return;
483
484         LOG.assertTrue(!getProject().isDisposed(), "project disposed");
485
486         if (myEditor.isDisposed()) {
487           LookupManager.getInstance(getProject()).hideActiveLookup();
488           CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
489           return;
490         }
491
492         if (myEditor instanceof EditorWindow) {
493           LOG.assertTrue(((EditorWindow)myEditor).getInjectedFile().isValid(), "injected file !valid");
494           LOG.assertTrue(((DocumentWindow)myEditor.getDocument()).isValid(), "docWindow !valid");
495         }
496         PsiFile file = myLookup.getPsiFile();
497         LOG.assertTrue(file == null || file.isValid(), "file !valid");
498
499         myLookup.setCalculating(false);
500
501         if (myCount == 0) {
502           LookupManager.getInstance(getProject()).hideActiveLookup();
503           if (!isAutopopupCompletion()) {
504             final CompletionProgressIndicator current = CompletionServiceImpl.getCompletionService().getCurrentCompletion();
505             LOG.assertTrue(current == null, current + "!=" + CompletionProgressIndicator.this);
506
507             handleEmptyLookup(!((CompletionPhase.BgCalculation)phase).modifiersChanged);
508           }
509         }
510         else {
511           CompletionServiceImpl.setCompletionPhase(new CompletionPhase.ItemsCalculated(CompletionProgressIndicator.this));
512           updateLookup();
513         }
514       }
515     }, myQueue.getModalityState());
516   }
517
518   private boolean hideAutopopupIfMeaningless() {
519     if (!myLookup.isLookupDisposed() && isAutopopupCompletion() && !myLookup.isSelectionTouched() && !myLookup.isCalculating()) {
520       myLookup.refreshUi(true, false);
521       final List<LookupElement> items = myLookup.getItems();
522
523       for (LookupElement item : items) {
524         if (!myLookup.itemPattern(item).equals(item.getLookupString())) {
525           return false;
526         }
527
528         if (item.isValid() && item.isWorthShowingInAutoPopup()) {
529           return false;
530         }
531       }
532
533       myLookup.hideLookup(false);
534       LOG.assertTrue(CompletionServiceImpl.getCompletionService().getCurrentCompletion() == null);
535       CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
536       return true;
537     }
538     return false;
539   }
540
541   public boolean fillInCommonPrefix(final boolean explicit) {
542     if (isInsideIdentifier()) {
543       return false;
544     }
545
546     final Boolean aBoolean = new WriteCommandAction<Boolean>(getProject()) {
547       @Override
548       protected void run(@NotNull Result<Boolean> result) throws Throwable {
549         if (!explicit) {
550           setMergeCommand();
551         }
552         try {
553           result.setResult(myLookup.fillInCommonPrefix(explicit));
554         }
555         catch (Exception e) {
556           LOG.error(e);
557         }
558       }
559     }.execute().getResultObject();
560     return aBoolean.booleanValue();
561   }
562
563   public void restorePrefix(@NotNull final Runnable customRestore) {
564     new WriteCommandAction(getProject()) {
565       @Override
566       protected void run(@NotNull Result result) throws Throwable {
567         setMergeCommand();
568
569         customRestore.run();
570       }
571     }.execute();
572   }
573
574   public int nextInvocationCount(int invocation, boolean reused) {
575     return reused ? Math.max(getParameters().getInvocationCount() + 1, 2) : invocation;
576   }
577
578   public Editor getEditor() {
579     return myEditor;
580   }
581
582   @NotNull
583   public Caret getCaret() {
584     return myCaret;
585   }
586
587   public boolean isRepeatedInvocation(CompletionType completionType, Editor editor) {
588     if (completionType != myParameters.getCompletionType() || editor != myEditor) {
589       return false;
590     }
591
592     if (isAutopopupCompletion() && !myLookup.mayBeNoticed()) {
593       return false;
594     }
595
596     return true;
597   }
598
599   @Override
600   public boolean isAutopopupCompletion() {
601     return myParameters.getInvocationCount() == 0;
602   }
603
604   @NotNull
605   public Project getProject() {
606     return ObjectUtils.assertNotNull(myEditor.getProject());
607   }
608
609   public void addWatchedPrefix(int startOffset, ElementPattern<String> restartCondition) {
610     myRestartingPrefixConditions.add(Pair.create(startOffset, restartCondition));
611   }
612
613   public void prefixUpdated() {
614     final int caretOffset = myEditor.getCaretModel().getOffset();
615     if (caretOffset < myStartCaret) {
616       scheduleRestart();
617       myRestartingPrefixConditions.clear();
618       return;
619     }
620
621     final CharSequence text = myEditor.getDocument().getCharsSequence();
622     for (Pair<Integer, ElementPattern<String>> pair : myRestartingPrefixConditions) {
623       int start = pair.first;
624       if (caretOffset >= start && start >= 0) {
625         final String newPrefix = text.subSequence(start, caretOffset).toString();
626         if (pair.second.accepts(newPrefix)) {
627           scheduleRestart();
628           myRestartingPrefixConditions.clear();
629           return;
630         }
631       }
632     }
633
634     hideAutopopupIfMeaningless();
635   }
636
637   public void scheduleRestart() {
638     ApplicationManager.getApplication().assertIsDispatchThread();
639     cancel();
640
641     final CompletionProgressIndicator current = CompletionServiceImpl.getCompletionService().getCurrentCompletion();
642     if (this != current) {
643       LOG.error(current + "!=" + this);
644     }
645
646     hideAutopopupIfMeaningless();
647
648     CompletionPhase oldPhase = CompletionServiceImpl.getCompletionPhase();
649     if (oldPhase instanceof CompletionPhase.CommittingDocuments) {
650       ((CompletionPhase.CommittingDocuments)oldPhase).replaced = true;
651     }
652
653     final CompletionPhase.CommittingDocuments phase = new CompletionPhase.CommittingDocuments(this, myEditor);
654     CompletionServiceImpl.setCompletionPhase(phase);
655     phase.ignoreCurrentDocumentChange();
656
657     final Project project = getProject();
658     ApplicationManager.getApplication().invokeLater(new Runnable() {
659       @Override
660       public void run() {
661         CompletionAutoPopupHandler.runLaterWithCommitted(project, myEditor.getDocument(), new Runnable() {
662           @Override
663           public void run() {
664             if (phase.checkExpired()) return;
665
666             CompletionAutoPopupHandler.invokeCompletion(myParameters.getCompletionType(),
667                                                         isAutopopupCompletion(), project, myEditor, myParameters.getInvocationCount(),
668                                                         true);
669           }
670         });
671       }
672     }, project.getDisposed());
673   }
674
675   @Override
676   public String toString() {
677     return "CompletionProgressIndicator[count=" +
678            myCount +
679            ",phase=" +
680            CompletionServiceImpl.getCompletionPhase() +
681            "]@" +
682            System.identityHashCode(this);
683   }
684
685   protected void handleEmptyLookup(final boolean awaitSecondInvocation) {
686     if (isAutopopupCompletion() && ApplicationManager.getApplication().isUnitTestMode()) {
687       return;
688     }
689
690     LOG.assertTrue(!isAutopopupCompletion());
691
692     if (ApplicationManager.getApplication().isUnitTestMode() || !myHandler.invokedExplicitly) {
693       CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
694       return;
695     }
696
697     for (final CompletionContributor contributor : CompletionContributor.forParameters(getParameters())) {
698       final String text = contributor.handleEmptyLookup(getParameters(), getEditor());
699       if (StringUtil.isNotEmpty(text)) {
700         LightweightHint hint = showErrorHint(getProject(), getEditor(), text);
701         CompletionServiceImpl.setCompletionPhase(
702           awaitSecondInvocation ? new CompletionPhase.NoSuggestionsHint(hint, this) : CompletionPhase.NoCompletion);
703         return;
704       }
705     }
706     CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
707   }
708
709   private static LightweightHint showErrorHint(Project project, Editor editor, String text) {
710     final LightweightHint[] result = {null};
711     final EditorHintListener listener = new EditorHintListener() {
712       @Override
713       public void hintShown(final Project project, final LightweightHint hint, final int flags) {
714         result[0] = hint;
715       }
716     };
717     final MessageBusConnection connection = project.getMessageBus().connect();
718     connection.subscribe(EditorHintListener.TOPIC, listener);
719     assert text != null;
720     HintManager.getInstance().showErrorHint(editor, text, HintManager.UNDER);
721     connection.disconnect();
722     return result[0];
723   }
724
725   private static boolean shouldPreselectFirstSuggestion(CompletionParameters parameters) {
726     if (!Registry.is("ide.completion.autopopup.choose.by.enter")) {
727       return false;
728     }
729
730     if (!ApplicationManager.getApplication().isUnitTestMode()) {
731       return true;
732     }
733
734     switch (CodeInsightSettings.getInstance().AUTOPOPUP_FOCUS_POLICY) {
735       case CodeInsightSettings.ALWAYS:
736         return true;
737       case CodeInsightSettings.NEVER:
738         return false;
739     }
740
741     final Language language = PsiUtilCore.getLanguageAtOffset(parameters.getPosition().getContainingFile(), parameters.getOffset());
742     for (CompletionConfidence confidence : CompletionConfidenceEP.forLanguage(language)) {
743       //noinspection deprecation
744       final ThreeState result = confidence.shouldFocusLookup(parameters);
745       if (result != ThreeState.UNSURE) {
746         LOG.debug(confidence + " has returned shouldFocusLookup=" + result);
747         return result == ThreeState.YES;
748       }
749     }
750     return false;
751   }
752
753   void startCompletion(final CompletionInitializationContext initContext) {
754     boolean sync = ApplicationManager.getApplication().isUnitTestMode() && !CompletionAutoPopupHandler.ourTestingAutopopup;
755     final CompletionThreading strategy = sync ? new SyncCompletion() : new AsyncCompletion();
756
757     strategy.startThread(ProgressWrapper.wrap(this), new Runnable() {
758       @Override
759       public void run() {
760         scheduleAdvertising();
761       }
762     });
763     final WeighingDelegate weigher = strategy.delegateWeighing(this);
764
765     class CalculateItems implements Runnable {
766       @Override
767       public void run() {
768         try {
769           calculateItems(initContext, weigher);
770         }
771         catch (ProcessCanceledException ignore) {
772           cancel(); // some contributor may just throw PCE; if indicator is not canceled everything will hang
773         }
774         catch (Throwable t) {
775           cancel();
776           LOG.error(t);
777         }
778       }
779     }
780     strategy.startThread(this, new CalculateItems());
781   }
782
783   private LookupElement[] calculateItems(CompletionInitializationContext initContext, WeighingDelegate weigher) {
784     duringCompletion(initContext);
785     ProgressManager.checkCanceled();
786
787     LookupElement[] result = CompletionService.getCompletionService().performCompletion(myParameters, weigher);
788     ProgressManager.checkCanceled();
789
790     weigher.waitFor();
791     ProgressManager.checkCanceled();
792
793     return result;
794   }
795
796   public void addAdvertisement(@NotNull final String text, @Nullable final Color bgColor) {
797     myAdvertiserChanges.offer(new Runnable() {
798       @Override
799       public void run() {
800         myLookup.addAdvertisement(text, bgColor);
801       }
802     });
803
804     myQueue.queue(myUpdate);
805   }
806
807   private static class ModifierTracker extends KeyAdapter {
808     private final JComponent myContentComponent;
809
810     public ModifierTracker(JComponent contentComponent) {
811       myContentComponent = contentComponent;
812     }
813
814     @Override
815     public void keyPressed(KeyEvent e) {
816       processModifier(e);
817     }
818
819     @Override
820     public void keyReleased(KeyEvent e) {
821       processModifier(e);
822     }
823
824     private void processModifier(KeyEvent e) {
825       final int code = e.getKeyCode();
826       if (code == KeyEvent.VK_CONTROL || code == KeyEvent.VK_META || code == KeyEvent.VK_ALT || code == KeyEvent.VK_SHIFT) {
827         myContentComponent.removeKeyListener(this);
828         final CompletionPhase phase = CompletionServiceImpl.getCompletionPhase();
829         if (phase instanceof CompletionPhase.BgCalculation) {
830           ((CompletionPhase.BgCalculation)phase).modifiersChanged = true;
831         }
832         else if (phase instanceof CompletionPhase.InsertedSingleItem) {
833           CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion);
834         }
835       }
836     }
837   }
838 }