da0d6b351686d00086e9aa389bdba910d7b0c063
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / daemon / impl / DaemonCodeAnalyzerImpl.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.daemon.impl;
18
19 import com.intellij.codeHighlighting.BackgroundEditorHighlighter;
20 import com.intellij.codeHighlighting.HighlightingPass;
21 import com.intellij.codeHighlighting.Pass;
22 import com.intellij.codeHighlighting.TextEditorHighlightingPass;
23 import com.intellij.codeInsight.AutoPopupController;
24 import com.intellij.codeInsight.daemon.*;
25 import com.intellij.codeInsight.hint.HintManager;
26 import com.intellij.codeInsight.intention.impl.FileLevelIntentionComponent;
27 import com.intellij.codeInsight.intention.impl.IntentionHintComponent;
28 import com.intellij.diagnostic.ThreadDumper;
29 import com.intellij.ide.PowerSaveMode;
30 import com.intellij.lang.annotation.HighlightSeverity;
31 import com.intellij.openapi.Disposable;
32 import com.intellij.openapi.application.ApplicationManager;
33 import com.intellij.openapi.application.ModalityState;
34 import com.intellij.openapi.application.ex.ApplicationEx;
35 import com.intellij.openapi.application.ex.ApplicationManagerEx;
36 import com.intellij.openapi.application.impl.ApplicationInfoImpl;
37 import com.intellij.openapi.components.PersistentStateComponent;
38 import com.intellij.openapi.components.State;
39 import com.intellij.openapi.components.Storage;
40 import com.intellij.openapi.components.StoragePathMacros;
41 import com.intellij.openapi.diagnostic.Logger;
42 import com.intellij.openapi.editor.Document;
43 import com.intellij.openapi.editor.Editor;
44 import com.intellij.openapi.editor.RangeMarker;
45 import com.intellij.openapi.editor.ex.RangeHighlighterEx;
46 import com.intellij.openapi.extensions.Extensions;
47 import com.intellij.openapi.fileEditor.FileEditor;
48 import com.intellij.openapi.fileEditor.FileEditorManager;
49 import com.intellij.openapi.fileEditor.TextEditor;
50 import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
51 import com.intellij.openapi.fileEditor.impl.text.AsyncEditorLoader;
52 import com.intellij.openapi.fileEditor.impl.text.TextEditorImpl;
53 import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
54 import com.intellij.openapi.fileTypes.FileType;
55 import com.intellij.openapi.fileTypes.FileTypeManager;
56 import com.intellij.openapi.fileTypes.impl.FileTypeManagerImpl;
57 import com.intellij.openapi.progress.ProcessCanceledException;
58 import com.intellij.openapi.progress.ProgressIndicator;
59 import com.intellij.openapi.progress.ProgressManager;
60 import com.intellij.openapi.project.DumbService;
61 import com.intellij.openapi.project.Project;
62 import com.intellij.openapi.util.Disposer;
63 import com.intellij.openapi.util.Key;
64 import com.intellij.openapi.util.TextRange;
65 import com.intellij.openapi.vfs.VirtualFile;
66 import com.intellij.openapi.vfs.VirtualFileManager;
67 import com.intellij.openapi.vfs.newvfs.RefreshQueueImpl;
68 import com.intellij.packageDependencies.DependencyValidationManager;
69 import com.intellij.psi.*;
70 import com.intellij.psi.impl.PsiDocumentManagerBase;
71 import com.intellij.psi.search.scope.packageSet.NamedScopeManager;
72 import com.intellij.psi.util.PsiModificationTracker;
73 import com.intellij.psi.util.PsiUtilCore;
74 import com.intellij.util.*;
75 import com.intellij.util.concurrency.EdtExecutorService;
76 import com.intellij.util.concurrency.FixedFuture;
77 import com.intellij.util.io.storage.HeavyProcessLatch;
78 import com.intellij.util.ui.UIUtil;
79 import gnu.trove.THashMap;
80 import gnu.trove.THashSet;
81 import org.jdom.Element;
82 import org.jetbrains.annotations.NonNls;
83 import org.jetbrains.annotations.NotNull;
84 import org.jetbrains.annotations.Nullable;
85 import org.jetbrains.annotations.TestOnly;
86
87 import java.util.*;
88 import java.util.concurrent.Future;
89 import java.util.concurrent.ScheduledExecutorService;
90 import java.util.concurrent.TimeUnit;
91 import java.util.concurrent.TimeoutException;
92
93 /**
94  * This class also controls the auto-reparse and auto-hints.
95  */
96 @State(
97   name = "DaemonCodeAnalyzer",
98   storages = @Storage(StoragePathMacros.WORKSPACE_FILE)
99 )
100 public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements PersistentStateComponent<Element>, Disposable {
101   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl");
102
103   private static final Key<List<HighlightInfo>> FILE_LEVEL_HIGHLIGHTS = Key.create("FILE_LEVEL_HIGHLIGHTS");
104   private final Project myProject;
105   private final DaemonCodeAnalyzerSettings mySettings;
106   @NotNull private final EditorTracker myEditorTracker;
107   @NotNull private final PsiDocumentManager myPsiDocumentManager;
108   private DaemonProgressIndicator myUpdateProgress; //guarded by this
109
110   private final Runnable myUpdateRunnable = createUpdateRunnable();
111
112   // use scheduler instead of Alarm because the latter requires ModalityState.current() which is obtainable from EDT only which requires too many invokeLaters
113   private final ScheduledExecutorService myAlarm = EdtExecutorService.getScheduledExecutorInstance();
114   @NotNull
115   private volatile Future myUpdateRunnableFuture = new FixedFuture<>(null);
116   private boolean myUpdateByTimerEnabled = true;
117   private final Collection<VirtualFile> myDisabledHintsFiles = new THashSet<>();
118   private final Collection<VirtualFile> myDisabledHighlightingFiles = new THashSet<>();
119
120   private final FileStatusMap myFileStatusMap;
121   private DaemonCodeAnalyzerSettings myLastSettings;
122
123   private volatile IntentionHintComponent myLastIntentionHint;
124   private volatile boolean myDisposed;     // the only possible transition: false -> true
125   private volatile boolean myInitialized;  // the only possible transition: false -> true
126
127   @NonNls private static final String DISABLE_HINTS_TAG = "disable_hints";
128   @NonNls private static final String FILE_TAG = "file";
129   @NonNls private static final String URL_ATT = "url";
130   private final PassExecutorService myPassExecutorService;
131
132   public DaemonCodeAnalyzerImpl(@NotNull Project project,
133                                 @NotNull DaemonCodeAnalyzerSettings daemonCodeAnalyzerSettings,
134                                 @NotNull EditorTracker editorTracker,
135                                 @NotNull PsiDocumentManager psiDocumentManager,
136                                 @SuppressWarnings("UnusedParameters") @NotNull final NamedScopeManager namedScopeManager,
137                                 @SuppressWarnings("UnusedParameters") @NotNull final DependencyValidationManager dependencyValidationManager) {
138     myProject = project;
139     mySettings = daemonCodeAnalyzerSettings;
140     myEditorTracker = editorTracker;
141     myPsiDocumentManager = psiDocumentManager;
142     myLastSettings = ((DaemonCodeAnalyzerSettingsImpl)daemonCodeAnalyzerSettings).clone();
143
144     myFileStatusMap = new FileStatusMap(project);
145     myPassExecutorService = new PassExecutorService(project);
146     Disposer.register(this, myPassExecutorService);
147     Disposer.register(this, myFileStatusMap);
148     DaemonProgressIndicator.setDebug(LOG.isDebugEnabled());
149
150     assert !myInitialized : "Double Initializing";
151     Disposer.register(this, new StatusBarUpdater(project));
152
153     myInitialized = true;
154     myDisposed = false;
155     myFileStatusMap.markAllFilesDirty("DCAI init");
156     Disposer.register(this, () -> {
157       assert myInitialized : "Disposing not initialized component";
158       assert !myDisposed : "Double dispose";
159
160       stopProcess(false, "Dispose");
161
162       myDisposed = true;
163       myLastSettings = null;
164     });
165   }
166
167   @Override
168   public void dispose() {
169
170   }
171
172   @NotNull
173   @TestOnly
174   public static List<HighlightInfo> getHighlights(@NotNull Document document, HighlightSeverity minSeverity, @NotNull Project project) {
175     List<HighlightInfo> infos = new ArrayList<>();
176     processHighlights(document, project, minSeverity, 0, document.getTextLength(), Processors.cancelableCollectProcessor(infos));
177     return infos;
178   }
179
180   @Override
181   @NotNull
182   @TestOnly
183   public List<HighlightInfo> getFileLevelHighlights(@NotNull Project project, @NotNull PsiFile file) {
184     VirtualFile vFile = file.getViewProvider().getVirtualFile();
185     final FileEditorManager manager = FileEditorManager.getInstance(project);
186     List<HighlightInfo> result = new ArrayList<>();
187     for (FileEditor fileEditor : manager.getEditors(vFile)) {
188       final List<HighlightInfo> infos = fileEditor.getUserData(FILE_LEVEL_HIGHLIGHTS);
189       if (infos == null) continue;
190       for (HighlightInfo info : infos) {
191           result.add(info);
192       }
193     }
194     return result;
195   }
196
197   @Override
198   public void cleanFileLevelHighlights(@NotNull Project project, final int group, PsiFile psiFile) {
199     if (psiFile == null) return;
200     FileViewProvider provider = psiFile.getViewProvider();
201     VirtualFile vFile = provider.getVirtualFile();
202     final FileEditorManager manager = FileEditorManager.getInstance(project);
203     for (FileEditor fileEditor : manager.getEditors(vFile)) {
204       final List<HighlightInfo> infos = fileEditor.getUserData(FILE_LEVEL_HIGHLIGHTS);
205       if (infos == null) continue;
206       List<HighlightInfo> infosToRemove = new ArrayList<>();
207       for (HighlightInfo info : infos) {
208         if (info.getGroup() == group) {
209           manager.removeTopComponent(fileEditor, info.fileLevelComponent);
210           infosToRemove.add(info);
211         }
212       }
213       infos.removeAll(infosToRemove);
214     }
215   }
216
217   @Override
218   public void addFileLevelHighlight(@NotNull final Project project,
219                                     final int group,
220                                     @NotNull final HighlightInfo info,
221                                     @NotNull final PsiFile psiFile) {
222     VirtualFile vFile = psiFile.getViewProvider().getVirtualFile();
223     final FileEditorManager manager = FileEditorManager.getInstance(project);
224     for (FileEditor fileEditor : manager.getEditors(vFile)) {
225       if (fileEditor instanceof TextEditor) {
226         FileLevelIntentionComponent component = new FileLevelIntentionComponent(info.getDescription(), info.getSeverity(),
227                                                                                 info.getGutterIconRenderer(), info.quickFixActionRanges,
228                                                                                 project, psiFile, ((TextEditor)fileEditor).getEditor());
229         manager.addTopComponent(fileEditor, component);
230         List<HighlightInfo> fileLevelInfos = fileEditor.getUserData(FILE_LEVEL_HIGHLIGHTS);
231         if (fileLevelInfos == null) {
232           fileLevelInfos = new ArrayList<>();
233           fileEditor.putUserData(FILE_LEVEL_HIGHLIGHTS, fileLevelInfos);
234         }
235         info.fileLevelComponent = component;
236         info.setGroup(group);
237         fileLevelInfos.add(info);
238       }
239     }
240   }
241
242   @Override
243   @NotNull
244   public List<HighlightInfo> runMainPasses(@NotNull PsiFile psiFile,
245                                            @NotNull Document document,
246                                            @NotNull final ProgressIndicator progress) {
247     if (ApplicationManager.getApplication().isDispatchThread()) {
248       throw new IllegalStateException("Must not run highlighting from under EDT");
249     }
250     if (!ApplicationManager.getApplication().isReadAccessAllowed()) {
251       throw new IllegalStateException("Must run highlighting from under read action");
252     }
253     ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
254     if (!(indicator instanceof DaemonProgressIndicator)) {
255       throw new IllegalStateException("Must run highlighting under progress with DaemonProgressIndicator");
256     }
257     // clear status maps to run passes from scratch so that refCountHolder won't conflict and try to restart itself on partially filled maps
258     myFileStatusMap.markAllFilesDirty("prepare to run main passes");
259     stopProcess(false, "disable background daemon");
260     myPassExecutorService.cancelAll(true);
261
262     final List<HighlightInfo> result;
263     try {
264       result = new ArrayList<>();
265       final VirtualFile virtualFile = psiFile.getVirtualFile();
266       if (virtualFile != null && !virtualFile.getFileType().isBinary()) {
267         List<TextEditorHighlightingPass> passes =
268           TextEditorHighlightingPassRegistrarEx.getInstanceEx(myProject).instantiateMainPasses(psiFile, document,
269                                                                                                HighlightInfoProcessor.getEmpty());
270
271         Collections.sort(passes, (o1, o2) -> {
272           if (o1 instanceof GeneralHighlightingPass) return -1;
273           if (o2 instanceof GeneralHighlightingPass) return 1;
274           return 0;
275         });
276
277         try {
278           for (TextEditorHighlightingPass pass : passes) {
279             pass.doCollectInformation(progress);
280             result.addAll(pass.getInfos());
281           }
282         }
283         catch (ProcessCanceledException e) {
284           LOG.debug("Canceled: " + progress);
285           throw e;
286         }
287       }
288     }
289     finally {
290       stopProcess(true, "re-enable background daemon after main passes run");
291     }
292
293     return result;
294   }
295
296   @NotNull
297   @TestOnly
298   public List<HighlightInfo> runPasses(@NotNull PsiFile file,
299                                        @NotNull Document document,
300                                        @NotNull TextEditor textEditor,
301                                        @NotNull int[] toIgnore,
302                                        boolean canChangeDocument,
303                                        @Nullable Runnable callbackWhileWaiting) throws ProcessCanceledException {
304     return runPasses(file, document, Collections.singletonList(textEditor), toIgnore, canChangeDocument, callbackWhileWaiting);
305   }
306
307   private volatile boolean mustWaitForSmartMode = true;
308   @TestOnly
309   public void mustWaitForSmartMode(final boolean mustWait, @NotNull Disposable parent) {
310     final boolean old = mustWaitForSmartMode;
311     mustWaitForSmartMode = mustWait;
312     Disposer.register(parent, () -> mustWaitForSmartMode = old);
313   }
314
315   @NotNull
316   @TestOnly
317   List<HighlightInfo> runPasses(@NotNull PsiFile file,
318                                 @NotNull Document document,
319                                 @NotNull List<TextEditor> textEditors,
320                                 @NotNull int[] toIgnore,
321                                 boolean canChangeDocument,
322                                 @Nullable final Runnable callbackWhileWaiting) throws ProcessCanceledException {
323     assert myInitialized;
324     assert !myDisposed;
325     ApplicationEx application = ApplicationManagerEx.getApplicationEx();
326     application.assertIsDispatchThread();
327     if (application.isWriteAccessAllowed()) {
328       throw new AssertionError("Must not start highlighting from within write action, or deadlock is imminent");
329     }
330     DaemonProgressIndicator.setDebug(!ApplicationInfoImpl.isInPerformanceTest());
331     ((FileTypeManagerImpl)FileTypeManager.getInstance()).drainReDetectQueue();
332     // pump first so that queued event do not interfere
333     UIUtil.dispatchAllInvocationEvents();
334
335     // refresh will fire write actions interfering with highlighting
336     while (RefreshQueueImpl.isRefreshInProgress() || HeavyProcessLatch.INSTANCE.isRunning()) {
337       UIUtil.dispatchAllInvocationEvents();
338     }
339     long dstart = System.currentTimeMillis();
340     while (mustWaitForSmartMode && DumbService.getInstance(myProject).isDumb()) {
341       if (System.currentTimeMillis() > dstart + 100000) {
342         throw new IllegalStateException("Timeout waiting for smart mode. If you absolutely want to be dumb, please use DaemonCodeAnalyzerImpl.mustWaitForSmartMode(false).");
343       }
344       UIUtil.dispatchAllInvocationEvents();
345     }
346
347     UIUtil.dispatchAllInvocationEvents();
348
349     Project project = file.getProject();
350     FileStatusMap.getAndClearLog();
351     FileStatusMap fileStatusMap = getFileStatusMap();
352     fileStatusMap.allowDirt(canChangeDocument);
353
354     Map<FileEditor, HighlightingPass[]> map = new HashMap<>();
355     for (TextEditor textEditor : textEditors) {
356       if (textEditor instanceof TextEditorImpl) {
357         try {
358           ((TextEditorImpl)textEditor).waitForLoaded(10, TimeUnit.SECONDS);
359         }
360         catch (TimeoutException e) {
361           throw new RuntimeException(textEditor + " has not completed loading in 10 seconds");
362         }
363       }
364       TextEditorBackgroundHighlighter highlighter = (TextEditorBackgroundHighlighter)textEditor.getBackgroundHighlighter();
365       if (highlighter == null) {
366         Editor editor = textEditor.getEditor();
367         throw new RuntimeException("Null highlighter from " + textEditor + "; loaded: " + AsyncEditorLoader.isEditorLoaded(editor));
368       }
369       final List<TextEditorHighlightingPass> passes = highlighter.getPasses(toIgnore);
370       HighlightingPass[] array = passes.toArray(new HighlightingPass[passes.size()]);
371       assert array.length != 0 : "Highlighting is disabled for the file " + file;
372       map.put(textEditor, array);
373     }
374     for (int ignoreId : toIgnore) {
375       fileStatusMap.markFileUpToDate(document, ignoreId);
376     }
377
378     myUpdateRunnableFuture.cancel(false);
379
380     final DaemonProgressIndicator progress = createUpdateProgress();
381     myPassExecutorService.submitPasses(map, progress);
382     try {
383       long start = System.currentTimeMillis();
384       while (progress.isRunning() && System.currentTimeMillis() < start + 5*60*1000) {
385         wrap(() -> {
386           progress.checkCanceled();
387           if (callbackWhileWaiting != null) {
388             callbackWhileWaiting.run();
389           }
390           waitInOtherThread(50, canChangeDocument);
391           UIUtil.dispatchAllInvocationEvents();
392           Throwable savedException = PassExecutorService.getSavedException(progress);
393           if (savedException != null) throw savedException;
394         });
395       }
396       if (progress.isRunning() && !progress.isCanceled()) {
397         throw new RuntimeException("Highlighting still running after "+(System.currentTimeMillis()-start)/1000+" seconds.\n"+ ThreadDumper.dumpThreadsToString());
398       }
399
400       final HighlightingSessionImpl session =
401         (HighlightingSessionImpl)HighlightingSessionImpl.getOrCreateHighlightingSession(file, textEditors.get(0).getEditor(), progress, null);
402       wrap(() -> {
403         if (!waitInOtherThread(60000, canChangeDocument)) {
404           throw new TimeoutException("Unable to complete in 60s");
405         }
406         session.waitForHighlightInfosApplied();
407       });
408       UIUtil.dispatchAllInvocationEvents();
409       UIUtil.dispatchAllInvocationEvents();
410       assert progress.isCanceled() && progress.isDisposed();
411
412       return getHighlights(document, null, project);
413     }
414     finally {
415       DaemonProgressIndicator.setDebug(false);
416       String log = FileStatusMap.getAndClearLog();
417       fileStatusMap.allowDirt(true);
418       try {
419         waitForTermination();
420       }
421       catch (Throwable e) {
422         LOG.error(log, e);
423       }
424     }
425   }
426
427   @TestOnly
428   private boolean waitInOtherThread(int millis, boolean canChangeDocument) throws Throwable {
429     Disposable disposable = Disposer.newDisposable();
430     // last hope protection against PsiModificationTrackerImpl.incCounter() craziness (yes, Kotlin)
431     myProject.getMessageBus().connect(disposable).subscribe(PsiModificationTracker.TOPIC,
432       () -> {
433         throw new IllegalStateException("You must not perform PSI modifications from inside highlighting");
434       });
435     if (!canChangeDocument) {
436       myProject.getMessageBus().connect(disposable).subscribe(DaemonCodeAnalyzer.DAEMON_EVENT_TOPIC, new DaemonListenerAdapter() {
437         @Override
438         public void daemonCancelEventOccurred(@NotNull String reason) {
439           throw new IllegalStateException("You must not cancel daemon inside highlighting test: "+reason);
440         }
441       });
442     }
443
444     try {
445       Future<Boolean> future = ApplicationManager.getApplication().executeOnPooledThread(() -> {
446         try {
447           return myPassExecutorService.waitFor(millis);
448         }
449         catch (Throwable e) {
450           throw new RuntimeException(e);
451         }
452       });
453       return future.get();
454     }
455     finally {
456       Disposer.dispose(disposable);
457     }
458   }
459
460   @TestOnly
461   public void prepareForTest() {
462     setUpdateByTimerEnabled(false);
463     waitForTermination();
464   }
465
466   @TestOnly
467   public void cleanupAfterTest() {
468     if (myProject.isOpen()) {
469       prepareForTest();
470     }
471   }
472
473   @TestOnly
474   void waitForTermination() {
475     myPassExecutorService.cancelAll(true);
476   }
477
478   @Override
479   public void settingsChanged() {
480     DaemonCodeAnalyzerSettings settings = DaemonCodeAnalyzerSettings.getInstance();
481     if (settings.isCodeHighlightingChanged(myLastSettings)) {
482       restart();
483     }
484     myLastSettings = ((DaemonCodeAnalyzerSettingsImpl)settings).clone();
485   }
486
487   @Override
488   public void updateVisibleHighlighters(@NotNull Editor editor) {
489     ApplicationManager.getApplication().assertIsDispatchThread();
490     // no need, will not work anyway
491   }
492
493   @Override
494   public void setUpdateByTimerEnabled(boolean value) {
495     myUpdateByTimerEnabled = value;
496     stopProcess(value, "Update by timer change");
497   }
498
499   private int myDisableCount;
500
501   @Override
502   public void disableUpdateByTimer(@NotNull Disposable parentDisposable) {
503     setUpdateByTimerEnabled(false);
504     myDisableCount++;
505     ApplicationManager.getApplication().assertIsDispatchThread();
506
507     Disposer.register(parentDisposable, () -> {
508       myDisableCount--;
509       if (myDisableCount == 0) {
510         setUpdateByTimerEnabled(true);
511       }
512     });
513   }
514
515   boolean isUpdateByTimerEnabled() {
516     return myUpdateByTimerEnabled;
517   }
518
519   @Override
520   public void setImportHintsEnabled(@NotNull PsiFile file, boolean value) {
521     VirtualFile vFile = file.getVirtualFile();
522     if (value) {
523       myDisabledHintsFiles.remove(vFile);
524       stopProcess(true, "Import hints change");
525     }
526     else {
527       myDisabledHintsFiles.add(vFile);
528       HintManager.getInstance().hideAllHints();
529     }
530   }
531
532   @Override
533   public void resetImportHintsEnabledForProject() {
534     myDisabledHintsFiles.clear();
535   }
536
537   @Override
538   public void setHighlightingEnabled(@NotNull PsiFile file, boolean value) {
539     VirtualFile virtualFile = PsiUtilCore.getVirtualFile(file);
540     if (value) {
541       myDisabledHighlightingFiles.remove(virtualFile);
542     }
543     else {
544       myDisabledHighlightingFiles.add(virtualFile);
545     }
546   }
547
548   @Override
549   public boolean isHighlightingAvailable(@Nullable PsiFile file) {
550     if (file == null || !file.isPhysical()) return false;
551     if (myDisabledHighlightingFiles.contains(PsiUtilCore.getVirtualFile(file))) return false;
552
553     if (file instanceof PsiCompiledElement) return false;
554     final FileType fileType = file.getFileType();
555
556     // To enable T.O.D.O. highlighting
557     return !fileType.isBinary();
558   }
559
560   @Override
561   public boolean isImportHintsEnabled(@NotNull PsiFile file) {
562     return isAutohintsAvailable(file) && !myDisabledHintsFiles.contains(file.getVirtualFile());
563   }
564
565   @Override
566   public boolean isAutohintsAvailable(PsiFile file) {
567     return isHighlightingAvailable(file) && !(file instanceof PsiCompiledElement);
568   }
569
570   @Override
571   public void restart() {
572     myFileStatusMap.markAllFilesDirty("Global restart");
573     stopProcess(true, "Global restart");
574   }
575
576   @Override
577   public void restart(@NotNull PsiFile file) {
578     Document document = myPsiDocumentManager.getCachedDocument(file);
579     if (document == null) return;
580     String reason = "Psi file restart: " + file.getName();
581     myFileStatusMap.markFileScopeDirty(document, new TextRange(0, document.getTextLength()), file.getTextLength(), reason);
582     stopProcess(true, reason);
583   }
584
585   @NotNull
586   List<TextEditorHighlightingPass> getPassesToShowProgressFor(Document document) {
587     List<TextEditorHighlightingPass> allPasses = myPassExecutorService.getAllSubmittedPasses();
588     List<TextEditorHighlightingPass> result = new ArrayList<>(allPasses.size());
589     for (TextEditorHighlightingPass pass : allPasses) {
590       if (pass.getDocument() == document || pass.getDocument() == null) {
591         result.add(pass);
592       }
593     }
594     return result;
595   }
596
597   boolean isAllAnalysisFinished(@NotNull PsiFile file) {
598     if (myDisposed) return false;
599     Document document = myPsiDocumentManager.getCachedDocument(file);
600     return document != null &&
601            document.getModificationStamp() == file.getViewProvider().getModificationStamp() &&
602            myFileStatusMap.allDirtyScopesAreNull(document);
603   }
604
605   @Override
606   public boolean isErrorAnalyzingFinished(@NotNull PsiFile file) {
607     if (myDisposed) return false;
608     Document document = myPsiDocumentManager.getCachedDocument(file);
609     return document != null &&
610            document.getModificationStamp() == file.getViewProvider().getModificationStamp() &&
611            myFileStatusMap.getFileDirtyScope(document, Pass.UPDATE_ALL) == null;
612   }
613
614   @Override
615   @NotNull
616   public FileStatusMap getFileStatusMap() {
617     return myFileStatusMap;
618   }
619
620   synchronized boolean isRunning() {
621     return myUpdateProgress != null && !myUpdateProgress.isCanceled();
622   }
623   
624   @TestOnly
625   public boolean isRunningOrPending() {
626     ApplicationManager.getApplication().assertIsDispatchThread();
627     return isRunning() || !myUpdateRunnableFuture.isDone();
628   }
629
630   synchronized void stopProcess(boolean toRestartAlarm, @NotNull @NonNls String reason) {
631     cancelUpdateProgress(toRestartAlarm, reason);
632     myUpdateRunnableFuture.cancel(false);
633     boolean restart = toRestartAlarm && !myDisposed && myInitialized;
634     if (restart) {
635       myUpdateRunnableFuture = myAlarm.schedule(myUpdateRunnable, mySettings.AUTOREPARSE_DELAY, TimeUnit.MILLISECONDS);
636     }
637   }
638
639   private synchronized void cancelUpdateProgress(final boolean start, @NonNls String reason) {
640     if (myUpdateProgress != null) {
641       PassExecutorService.log(myUpdateProgress, null, "Cancel", reason, start);
642       myUpdateProgress.cancel();
643       myPassExecutorService.cancelAll(false);
644       myUpdateProgress = null;
645     }
646   }
647
648
649   static boolean processHighlightsNearOffset(@NotNull Document document,
650                                              @NotNull Project project,
651                                              @NotNull final HighlightSeverity minSeverity,
652                                              final int offset,
653                                              final boolean includeFixRange,
654                                              @NotNull final Processor<HighlightInfo> processor) {
655     return processHighlights(document, project, null, 0, document.getTextLength(), info -> {
656       if (!isOffsetInsideHighlightInfo(offset, info, includeFixRange)) return true;
657
658       int compare = info.getSeverity().compareTo(minSeverity);
659       return compare < 0 || processor.process(info);
660     });
661   }
662
663   @Nullable
664   public HighlightInfo findHighlightByOffset(@NotNull Document document, final int offset, final boolean includeFixRange) {
665     return findHighlightByOffset(document, offset, includeFixRange, HighlightSeverity.INFORMATION);
666   }
667
668   @Nullable
669   HighlightInfo findHighlightByOffset(@NotNull Document document,
670                                       final int offset,
671                                       final boolean includeFixRange,
672                                       @NotNull HighlightSeverity minSeverity) {
673     final List<HighlightInfo> foundInfoList = new SmartList<>();
674     processHighlightsNearOffset(document, myProject, minSeverity, offset, includeFixRange,
675         info -> {
676           if (info.getSeverity() == HighlightInfoType.ELEMENT_UNDER_CARET_SEVERITY) {
677             return true;
678           }
679           if (!foundInfoList.isEmpty()) {
680             HighlightInfo foundInfo = foundInfoList.get(0);
681             int compare = foundInfo.getSeverity().compareTo(info.getSeverity());
682             if (compare < 0) {
683               foundInfoList.clear();
684             }
685             else if (compare > 0) {
686               return true;
687             }
688           }
689           foundInfoList.add(info);
690           return true;
691         });
692
693     if (foundInfoList.isEmpty()) return null;
694     if (foundInfoList.size() == 1) return foundInfoList.get(0);
695     return new HighlightInfoComposite(foundInfoList);
696   }
697
698   private static boolean isOffsetInsideHighlightInfo(int offset, @NotNull HighlightInfo info, boolean includeFixRange) {
699     RangeHighlighterEx highlighter = info.highlighter;
700     if (highlighter == null || !highlighter.isValid()) return false;
701     int startOffset = highlighter.getStartOffset();
702     int endOffset = highlighter.getEndOffset();
703     if (startOffset <= offset && offset <= endOffset) {
704       return true;
705     }
706     if (!includeFixRange) return false;
707     RangeMarker fixMarker = info.fixMarker;
708     if (fixMarker != null) {  // null means its range is the same as highlighter
709       if (!fixMarker.isValid()) return false;
710       startOffset = fixMarker.getStartOffset();
711       endOffset = fixMarker.getEndOffset();
712       return startOffset <= offset && offset <= endOffset;
713     }
714     return false;
715   }
716
717   @NotNull
718   public static List<LineMarkerInfo> getLineMarkers(@NotNull Document document, @NotNull Project project) {
719     ApplicationManager.getApplication().assertIsDispatchThread();
720     List<LineMarkerInfo> result = new ArrayList<>();
721     LineMarkersUtil.processLineMarkers(project, document, new TextRange(0, document.getTextLength()), -1,
722                                        new CommonProcessors.CollectProcessor<>(result));
723     return result;
724   }
725
726   void setLastIntentionHint(@NotNull Project project,
727                             @NotNull PsiFile file,
728                             @NotNull Editor editor,
729                             @NotNull ShowIntentionsPass.IntentionsInfo intentions,
730                             boolean hasToRecreate) {
731     if (!editor.getSettings().isShowIntentionBulb()) {
732       return;
733     }
734     ApplicationManager.getApplication().assertIsDispatchThread();
735     hideLastIntentionHint();
736     
737     if (editor.getCaretModel().getCaretCount() > 1) return;
738     
739     IntentionHintComponent hintComponent = IntentionHintComponent.showIntentionHint(project, file, editor, intentions, false);
740     if (hasToRecreate) {
741       hintComponent.recreate();
742     }
743     myLastIntentionHint = hintComponent;
744   }
745
746   void hideLastIntentionHint() {
747     ApplicationManager.getApplication().assertIsDispatchThread();
748     IntentionHintComponent hint = myLastIntentionHint;
749     if (hint != null && hint.isVisible()) {
750       hint.hide();
751       myLastIntentionHint = null;
752     }
753   }
754
755   @Nullable
756   public IntentionHintComponent getLastIntentionHint() {
757     return myLastIntentionHint;
758   }
759
760   @Nullable
761   @Override
762   public Element getState() {
763     Element state = new Element("state");
764     if (myDisabledHintsFiles.isEmpty()) {
765       return state;
766     }
767
768     List<String> array = new SmartList<>();
769     for (VirtualFile file : myDisabledHintsFiles) {
770       if (file.isValid()) {
771         array.add(file.getUrl());
772       }
773     }
774
775     if (!array.isEmpty()) {
776       Collections.sort(array);
777
778       Element disableHintsElement = new Element(DISABLE_HINTS_TAG);
779       state.addContent(disableHintsElement);
780       for (String url : array) {
781         disableHintsElement.addContent(new Element(FILE_TAG).setAttribute(URL_ATT, url));
782       }
783     }
784     return state;
785   }
786
787   @Override
788   public void loadState(Element state) {
789     myDisabledHintsFiles.clear();
790
791     Element element = state.getChild(DISABLE_HINTS_TAG);
792     if (element != null) {
793       for (Element e : element.getChildren(FILE_TAG)) {
794         String url = e.getAttributeValue(URL_ATT);
795         if (url != null) {
796           VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(url);
797           if (file != null) {
798             myDisabledHintsFiles.add(file);
799           }
800         }
801       }
802     }
803   }
804
805   private final Runnable submitPassesRunnable = new Runnable() {
806     @Override
807     public void run() {
808       PassExecutorService.log(getUpdateProgress(), null, "Update Runnable. myUpdateByTimerEnabled:",
809                               myUpdateByTimerEnabled, " something disposed:",
810                               PowerSaveMode.isEnabled() || myDisposed || !myProject.isInitialized(), " activeEditors:",
811                               myProject.isDisposed() ? null : getSelectedEditors());
812       if (!myUpdateByTimerEnabled) return;
813       if (myDisposed) return;
814       ApplicationManager.getApplication().assertIsDispatchThread();
815
816       final Collection<FileEditor> activeEditors = getSelectedEditors();
817       if (activeEditors.isEmpty()) return;
818
819       if (ApplicationManager.getApplication().isWriteAccessAllowed()) {
820         // makes no sense to start from within write action, will cancel anyway
821         // we'll restart when the write action finish
822         return;
823       }
824       final PsiDocumentManagerBase documentManager = (PsiDocumentManagerBase)myPsiDocumentManager;
825       if (documentManager.hasUncommitedDocuments()) {
826         documentManager.cancelAndRunWhenAllCommitted("restart daemon when all committed", this);
827         return;
828       }
829       if (RefResolveService.ENABLED &&
830           !RefResolveService.getInstance(myProject).isUpToDate() &&
831           RefResolveService.getInstance(myProject).getQueueSize() == 1) {
832         return; // if the user have just typed in something, wait until the file is re-resolved
833         // (or else it will blink like crazy since unused symbols calculation depends on resolve service)
834       }
835
836       Map<FileEditor, HighlightingPass[]> passes = new THashMap<>(activeEditors.size());
837       for (FileEditor fileEditor : activeEditors) {
838         BackgroundEditorHighlighter highlighter = fileEditor.getBackgroundHighlighter();
839         if (highlighter != null) {
840           HighlightingPass[] highlightingPasses = highlighter.createPassesForEditor();
841           passes.put(fileEditor, highlightingPasses);
842         }
843       }
844       // cancel all after calling createPasses() since there are perverts {@link com.intellij.util.xml.ui.DomUIFactoryImpl} who are changing PSI there
845       cancelUpdateProgress(true, "Cancel by alarm");
846       myUpdateRunnableFuture.cancel(false);
847       DaemonProgressIndicator progress = createUpdateProgress();
848       myPassExecutorService.submitPasses(passes, progress);
849     }
850   };
851
852   @NotNull
853   private Runnable createUpdateRunnable() {
854     return new Runnable() {
855       @Override
856       public void run() {
857         ApplicationManager.getApplication().assertIsDispatchThread();
858
859         if (myDisposed || !myProject.isInitialized() || PowerSaveMode.isEnabled()) {
860           return;
861         }
862         // wait for heavy processing to stop, re-schedule daemon but not too soon
863         if (HeavyProcessLatch.INSTANCE.isRunning()) {
864           HeavyProcessLatch.INSTANCE.executeOutOfHeavyProcess(() ->
865               myUpdateRunnableFuture = myAlarm.schedule(myUpdateRunnable, Math.max(mySettings.AUTOREPARSE_DELAY, 100), TimeUnit.MILLISECONDS));
866           return;
867         }
868         Editor activeEditor = FileEditorManager.getInstance(myProject).getSelectedTextEditor();
869
870         if (activeEditor == null) {
871           AutoPopupController.runTransactionWithEverythingCommitted(myProject, submitPassesRunnable);
872         }
873         else {
874           ((PsiDocumentManagerBase)myPsiDocumentManager).cancelAndRunWhenAllCommitted("start daemon when all committed", submitPassesRunnable);
875         }
876       }
877
878       @Override
879       public String toString() {
880         return "Daemon update runnable";
881       }
882     };
883   }
884
885   @NotNull
886   private synchronized DaemonProgressIndicator createUpdateProgress() {
887     DaemonProgressIndicator old = myUpdateProgress;
888     if (old != null && !old.isCanceled()) {
889       old.cancel();
890     }
891     DaemonProgressIndicator progress = new DaemonProgressIndicator() {
892       @Override
893       public void stopIfRunning() {
894         super.stopIfRunning();
895         myProject.getMessageBus().syncPublisher(DAEMON_EVENT_TOPIC).daemonFinished();
896       }
897     };
898     progress.setModalityProgress(null);
899     progress.start();
900     myUpdateProgress = progress;
901     return progress;
902   }
903
904   @Override
905   public void autoImportReferenceAtCursor(@NotNull Editor editor, @NotNull PsiFile file) {
906     for (ReferenceImporter importer : Extensions.getExtensions(ReferenceImporter.EP_NAME)) {
907       if (importer.autoImportReferenceAtCursor(editor, file)) break;
908     }
909   }
910
911   @TestOnly
912   synchronized DaemonProgressIndicator getUpdateProgress() {
913     return myUpdateProgress;
914   }
915
916   @NotNull
917   private Collection<FileEditor> getSelectedEditors() {
918     ApplicationManager.getApplication().assertIsDispatchThread();
919
920     // Editors in modal context
921     List<Editor> editors = getActiveEditors();
922
923     Collection<FileEditor> activeTextEditors = new THashSet<>(editors.size());
924     for (Editor editor : editors) {
925       if (editor.isDisposed()) continue;
926       TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor);
927       activeTextEditors.add(textEditor);
928     }
929     if (ApplicationManager.getApplication().getCurrentModalityState() != ModalityState.NON_MODAL) {
930       return activeTextEditors;
931     }
932
933     // Editors in tabs.
934     Collection<FileEditor> result = new THashSet<>();
935     Collection<VirtualFile> files = new THashSet<>(activeTextEditors.size());
936     final FileEditor[] tabEditors = FileEditorManager.getInstance(myProject).getSelectedEditors();
937     for (FileEditor tabEditor : tabEditors) {
938       if (!tabEditor.isValid()) continue;
939       VirtualFile file = ((FileEditorManagerEx)FileEditorManager.getInstance(myProject)).getFile(tabEditor);
940       if (file != null) {
941         files.add(file);
942       }
943       result.add(tabEditor);
944     }
945     // do not duplicate documents
946     for (FileEditor fileEditor : activeTextEditors) {
947       VirtualFile file = ((FileEditorManagerEx)FileEditorManager.getInstance(myProject)).getFile(fileEditor);
948       if (file != null && files.contains(file)) continue;
949       result.add(fileEditor);
950     }
951     return result;
952   }
953
954   @NotNull
955   private List<Editor> getActiveEditors() {
956     return myEditorTracker.getActiveEditors();
957   }
958
959   @TestOnly
960   private static void wrap(@NotNull ThrowableRunnable runnable) {
961     try {
962       runnable.run();
963     }
964     catch (RuntimeException | Error e) {
965       throw e;
966     }
967     catch (Throwable e) {
968       throw new RuntimeException(e);
969     }
970   }
971 }