optimisation: in case of thousands of events do not call expensive Future.schedule...
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / daemon / impl / DaemonListeners.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.ProjectTopics;
20 import com.intellij.codeHighlighting.Pass;
21 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
22 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings;
23 import com.intellij.codeInsight.hint.TooltipController;
24 import com.intellij.codeInspection.InspectionProfile;
25 import com.intellij.ide.AppLifecycleListener;
26 import com.intellij.ide.IdeTooltipManager;
27 import com.intellij.ide.PowerSaveMode;
28 import com.intellij.ide.todo.TodoConfiguration;
29 import com.intellij.openapi.Disposable;
30 import com.intellij.openapi.actionSystem.*;
31 import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
32 import com.intellij.openapi.actionSystem.ex.AnActionListener;
33 import com.intellij.openapi.application.*;
34 import com.intellij.openapi.application.impl.LaterInvocator;
35 import com.intellij.openapi.command.CommandAdapter;
36 import com.intellij.openapi.command.CommandEvent;
37 import com.intellij.openapi.command.CommandProcessor;
38 import com.intellij.openapi.command.undo.UndoManager;
39 import com.intellij.openapi.diagnostic.Logger;
40 import com.intellij.openapi.editor.*;
41 import com.intellij.openapi.editor.actionSystem.DocCommandGroupId;
42 import com.intellij.openapi.editor.colors.EditorColorsManager;
43 import com.intellij.openapi.editor.colors.EditorColorsScheme;
44 import com.intellij.openapi.editor.event.*;
45 import com.intellij.openapi.editor.ex.EditorEventMulticasterEx;
46 import com.intellij.openapi.editor.ex.EditorMarkupModel;
47 import com.intellij.openapi.editor.impl.EditorImpl;
48 import com.intellij.openapi.fileEditor.FileDocumentManager;
49 import com.intellij.openapi.fileEditor.FileEditor;
50 import com.intellij.openapi.fileEditor.FileEditorManager;
51 import com.intellij.openapi.module.ModuleUtilCore;
52 import com.intellij.openapi.project.DumbService;
53 import com.intellij.openapi.project.Project;
54 import com.intellij.openapi.project.ProjectUtil;
55 import com.intellij.openapi.roots.ModuleRootAdapter;
56 import com.intellij.openapi.roots.ModuleRootEvent;
57 import com.intellij.openapi.util.Disposer;
58 import com.intellij.openapi.util.Key;
59 import com.intellij.openapi.util.UserDataHolderEx;
60 import com.intellij.openapi.util.registry.Registry;
61 import com.intellij.openapi.vcs.*;
62 import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager;
63 import com.intellij.openapi.vfs.VirtualFile;
64 import com.intellij.openapi.vfs.VirtualFileAdapter;
65 import com.intellij.openapi.vfs.VirtualFileManager;
66 import com.intellij.openapi.vfs.VirtualFilePropertyEvent;
67 import com.intellij.openapi.wm.StatusBar;
68 import com.intellij.openapi.wm.WindowManager;
69 import com.intellij.openapi.wm.impl.status.TogglePopupHintsPanel;
70 import com.intellij.packageDependencies.DependencyValidationManager;
71 import com.intellij.profile.ProfileChangeAdapter;
72 import com.intellij.profile.codeInspection.ProjectInspectionProfileManager;
73 import com.intellij.psi.*;
74 import com.intellij.psi.impl.PsiDocumentManagerImpl;
75 import com.intellij.psi.impl.PsiManagerEx;
76 import com.intellij.psi.search.scope.packageSet.NamedScopeManager;
77 import com.intellij.util.messages.MessageBus;
78 import com.intellij.util.messages.MessageBusConnection;
79 import com.intellij.util.ui.UIUtil;
80 import com.intellij.vcsUtil.VcsUtil;
81 import org.jetbrains.annotations.NonNls;
82 import org.jetbrains.annotations.NotNull;
83 import org.jetbrains.annotations.Nullable;
84
85 import java.beans.PropertyChangeEvent;
86 import java.beans.PropertyChangeListener;
87 import java.util.Collections;
88 import java.util.List;
89
90
91 /**
92  * @author cdr
93  */
94 public class DaemonListeners implements Disposable {
95   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.DaemonListeners");
96
97   private final Project myProject;
98   private final DaemonCodeAnalyzerImpl myDaemonCodeAnalyzer;
99   @NotNull private final PsiDocumentManager myPsiDocumentManager;
100   private final FileEditorManager myFileEditorManager;
101   private final UndoManager myUndoManager;
102   private final ProjectLevelVcsManager myProjectLevelVcsManager;
103   private final VcsDirtyScopeManager myVcsDirtyScopeManager;
104   private final FileStatusManager myFileStatusManager;
105   @NotNull private final ActionManager myActionManager;
106   private final TooltipController myTooltipController;
107
108   private boolean myEscPressed;
109
110   private volatile boolean cutOperationJustHappened;
111   private final DaemonCodeAnalyzer.DaemonListener myDaemonEventPublisher;
112
113   private static final Key<Boolean> DAEMON_INITIALIZED = Key.create("DAEMON_INITIALIZED");
114
115   public static DaemonListeners getInstance(Project project) {
116     return project.getComponent(DaemonListeners.class);
117   }
118
119   public DaemonListeners(@NotNull final Project project,
120                          @NotNull DaemonCodeAnalyzerImpl daemonCodeAnalyzer,
121                          @NotNull final EditorTracker editorTracker,
122                          @NotNull EditorFactory editorFactory,
123                          @NotNull PsiDocumentManager psiDocumentManager,
124                          @NotNull CommandProcessor commandProcessor,
125                          @NotNull EditorColorsManager editorColorsManager,
126                          @NotNull final Application application,
127                          @NotNull ProjectInspectionProfileManager inspectionProjectProfileManager,
128                          @NotNull TodoConfiguration todoConfiguration,
129                          @NotNull ActionManagerEx actionManagerEx,
130                          @NotNull VirtualFileManager virtualFileManager,
131                          @SuppressWarnings("UnusedParameters") // for dependency order
132                          @NotNull final NamedScopeManager namedScopeManager,
133                          @SuppressWarnings("UnusedParameters") // for dependency order
134                          @NotNull final DependencyValidationManager dependencyValidationManager,
135                          @NotNull final FileDocumentManager fileDocumentManager,
136                          @NotNull final PsiManager psiManager,
137                          @NotNull final FileEditorManager fileEditorManager,
138                          @NotNull TooltipController tooltipController,
139                          @NotNull UndoManager undoManager,
140                          @NotNull ProjectLevelVcsManager projectLevelVcsManager,
141                          @NotNull VcsDirtyScopeManager vcsDirtyScopeManager,
142                          @NotNull FileStatusManager fileStatusManager) {
143     myProject = project;
144     myDaemonCodeAnalyzer = daemonCodeAnalyzer;
145     myPsiDocumentManager = psiDocumentManager;
146     myFileEditorManager = fileEditorManager;
147     myUndoManager = undoManager;
148     myProjectLevelVcsManager = projectLevelVcsManager;
149     myVcsDirtyScopeManager = vcsDirtyScopeManager;
150     myFileStatusManager = fileStatusManager;
151     myActionManager = actionManagerEx;
152     myTooltipController = tooltipController;
153
154     boolean replaced = ((UserDataHolderEx)myProject).replace(DAEMON_INITIALIZED, null, Boolean.TRUE);
155     LOG.assertTrue(replaced, "Daemon listeners already initialized for the project "+myProject);
156
157     MessageBus messageBus = myProject.getMessageBus();
158     myDaemonEventPublisher = messageBus.syncPublisher(DaemonCodeAnalyzer.DAEMON_EVENT_TOPIC);
159     if (project.isDefault()) return;
160     MessageBusConnection connection = messageBus.connect();
161
162     connection.subscribe(AppLifecycleListener.TOPIC, new AppLifecycleListener.Adapter() {
163       @Override
164       public void appClosing() {
165         stopDaemon(false, "App closing");
166       }
167     });
168     EditorEventMulticaster eventMulticaster = editorFactory.getEventMulticaster();
169     eventMulticaster.addDocumentListener(new DocumentAdapter() {
170       // clearing highlighters before changing document because change can damage editor highlighters drastically, so we'll clear more than necessary
171       @Override
172       public void beforeDocumentChange(final DocumentEvent e) {
173         Document document = e.getDocument();
174         VirtualFile virtualFile = fileDocumentManager.getFile(document);
175         Project project = virtualFile == null ? null : ProjectUtil.guessProjectForFile(virtualFile);
176         if (!worthBothering(document, project)) {
177           return; //no need to stop daemon if something happened in the console
178         }
179         stopDaemon(true, "Document change");
180         UpdateHighlightersUtil.updateHighlightersByTyping(myProject, e);
181       }
182     }, this);
183
184     eventMulticaster.addCaretListener(new CaretAdapter() {
185       @Override
186       public void caretPositionChanged(CaretEvent e) {
187         final Editor editor = e.getEditor();
188         if (!editor.getComponent().isShowing() && !application.isUnitTestMode() ||
189             !worthBothering(editor.getDocument(), editor.getProject())) {
190           return; //no need to stop daemon if something happened in the console
191         }
192         if (!application.isUnitTestMode()) {
193           ApplicationManager.getApplication().invokeLater(() -> {
194             if (!editor.getComponent().isShowing() || myProject.isDisposed()) {
195               return;
196             }
197             myDaemonCodeAnalyzer.hideLastIntentionHint();
198           }, ModalityState.current());
199         }
200       }
201     }, this);
202
203     eventMulticaster.addEditorMouseMotionListener(new MyEditorMouseMotionListener(), this);
204     eventMulticaster.addEditorMouseListener(new MyEditorMouseListener(myTooltipController), this);
205
206     EditorTrackerListener editorTrackerListener = new EditorTrackerListener() {
207       private List<Editor> myActiveEditors = Collections.emptyList();
208       @Override
209       public void activeEditorsChanged(@NotNull List<Editor> editors) {
210         List<Editor> activeEditors = editorTracker.getActiveEditors();
211         if (myActiveEditors.equals(activeEditors)) {
212           return;
213         }
214         myActiveEditors = activeEditors;
215         stopDaemon(true, "Active editor change");  // do not stop daemon if idea loses/gains focus
216         if (ApplicationManager.getApplication().isDispatchThread() && LaterInvocator.isInModalContext()) {
217           // editor appear in modal context, re-enable the daemon
218           myDaemonCodeAnalyzer.setUpdateByTimerEnabled(true);
219         }
220         for (Editor editor : activeEditors) {
221           repaintErrorStripeRenderer(editor, myProject);
222         }
223       }
224     };
225     editorTracker.addEditorTrackerListener(editorTrackerListener, this);
226
227     EditorFactoryListener editorFactoryListener = new EditorFactoryListener() {
228       @Override
229       public void editorCreated(@NotNull EditorFactoryEvent event) {
230         Editor editor = event.getEditor();
231         Document document = editor.getDocument();
232         Project editorProject = editor.getProject();
233         // worthBothering() checks for getCachedPsiFile, so call getPsiFile here
234         PsiFile file = editorProject == null ? null : PsiDocumentManager.getInstance(editorProject).getPsiFile(document);
235         boolean showing = editor.getComponent().isShowing();
236         boolean worthBothering = worthBothering(document, editorProject);
237         if (!showing || !worthBothering) {
238           LOG.debug("Not worth bothering about editor created for : " + file + " because editor isShowing(): " +
239                     showing + "; project is open and file is mine: " + worthBothering);
240           return;
241         }
242         repaintErrorStripeRenderer(editor, myProject);
243       }
244
245       @Override
246       public void editorReleased(@NotNull EditorFactoryEvent event) {
247         // mem leak after closing last editor otherwise
248         UIUtil.invokeLaterIfNeeded(myDaemonCodeAnalyzer::hideLastIntentionHint);
249       }
250     };
251     editorFactory.addEditorFactoryListener(editorFactoryListener, this);
252
253     PsiDocumentManagerImpl documentManager = (PsiDocumentManagerImpl)psiDocumentManager;
254     PsiChangeHandler changeHandler = new PsiChangeHandler(myProject, documentManager, editorFactory,connection, daemonCodeAnalyzer.getFileStatusMap());
255     Disposer.register(this, changeHandler);
256     psiManager.addPsiTreeChangeListener(changeHandler, changeHandler);
257
258     connection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() {
259       @Override
260       public void rootsChanged(ModuleRootEvent event) {
261         stopDaemonAndRestartAllFiles("Project roots changed");
262       }
263     });
264
265     connection.subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
266       @Override
267       public void enteredDumbMode() {
268         stopDaemonAndRestartAllFiles("Dumb mode started");
269       }
270
271       @Override
272       public void exitDumbMode() {
273         stopDaemonAndRestartAllFiles("Dumb mode finished");
274       }
275     });
276
277     connection.subscribe(PowerSaveMode.TOPIC, () -> stopDaemon(true, "Power save mode change"));
278     connection.subscribe(EditorColorsManager.TOPIC, scheme -> stopDaemonAndRestartAllFiles("Editor color scheme changed"));
279
280     commandProcessor.addCommandListener(new MyCommandListener(), this);
281     application.addApplicationListener(new MyApplicationListener(), this);
282     inspectionProjectProfileManager.addProfileChangeListener(new MyProfileChangeListener(), this);
283     todoConfiguration.addPropertyChangeListener(new MyTodoListener(), this);
284     todoConfiguration.colorSettingsChanged();
285     actionManagerEx.addAnActionListener(new MyAnActionListener(), this);
286     virtualFileManager.addVirtualFileListener(new VirtualFileAdapter() {
287       @Override
288       public void propertyChanged(@NotNull VirtualFilePropertyEvent event) {
289         String propertyName = event.getPropertyName();
290         if (VirtualFile.PROP_NAME.equals(propertyName)) {
291           stopDaemonAndRestartAllFiles("Virtual file name changed");
292           VirtualFile virtualFile = event.getFile();
293           PsiFile psiFile = !virtualFile.isValid() ? null : ((PsiManagerEx)psiManager).getFileManager().getCachedPsiFile(virtualFile);
294           if (psiFile != null && !myDaemonCodeAnalyzer.isHighlightingAvailable(psiFile)) {
295             Document document = fileDocumentManager.getCachedDocument(virtualFile);
296             if (document != null) {
297               // highlight markers no more
298               //todo clear all highlights regardless the pass id
299
300               // Here color scheme required for TextEditorFields, as far as I understand this
301               // code related to standard file editors, which always use Global color scheme,
302               // thus we can pass null here.
303               final EditorColorsScheme editorColorScheme = null;
304
305               UpdateHighlightersUtil.setHighlightersToEditor(myProject, document, 0, document.getTextLength(),
306                                                              Collections.emptyList(),
307                                                              editorColorScheme,
308                                                              Pass.UPDATE_ALL);
309             }
310           }
311         }
312         if (!propertyName.equals(PsiTreeChangeEvent.PROP_WRITABLE)) {
313           stopDaemon(true, "Virtual file property change");
314         }
315       }
316     }, this);
317
318     ((EditorEventMulticasterEx)eventMulticaster).addErrorStripeListener(new ErrorStripeHandler(myProject), this);
319
320     ModalityStateListener modalityStateListener = entering -> {
321       // before showing dialog we are in non-modal context yet, and before closing dialog we are still in modal context
322       boolean inModalContext = Registry.is("ide.perProjectModality") || LaterInvocator.isInModalContext();
323       stopDaemon(inModalContext, "Modality change. Was modal: " + inModalContext);
324       myDaemonCodeAnalyzer.setUpdateByTimerEnabled(inModalContext);
325     };
326     LaterInvocator.addModalityStateListener(modalityStateListener,this);
327
328     messageBus.connect().subscribe(SeverityRegistrar.SEVERITIES_CHANGED_TOPIC, () -> stopDaemonAndRestartAllFiles("Severities changed"));
329
330     if (RefResolveService.ENABLED) {
331       RefResolveService resolveService = RefResolveService.getInstance(project);
332       resolveService.addListener(this, new RefResolveService.Listener() {
333         @Override
334         public void allFilesResolved() {
335           stopDaemon(true, "RefResolveService is up to date");
336         }
337       });
338     }
339   }
340
341   private boolean worthBothering(final Document document, Project project) {
342     if (document == null) return true;
343     if (project != null && project != myProject) return false;
344     // cached is essential here since we do not want to create PSI file in alien project
345     PsiFile psiFile = myPsiDocumentManager.getCachedPsiFile(document);
346     return psiFile != null && psiFile.getOriginalFile() == psiFile;
347   }
348
349   @Override
350   public void dispose() {
351     stopDaemonAndRestartAllFiles("Project closed");
352     boolean replaced = ((UserDataHolderEx)myProject).replace(DAEMON_INITIALIZED, Boolean.TRUE, Boolean.FALSE);
353     LOG.assertTrue(replaced, "Daemon listeners already disposed for the project "+myProject);
354   }
355
356   public static boolean canChangeFileSilently(@NotNull PsiFileSystemItem file) {
357     Project project = file.getProject();
358     DaemonListeners listeners = getInstance(project);
359     if (listeners == null) return true;
360
361     if (listeners.cutOperationJustHappened) return false;
362     VirtualFile virtualFile = file.getVirtualFile();
363     if (virtualFile == null) return false;
364     if (file instanceof PsiCodeFragment) return true;
365     if (!ModuleUtilCore.projectContainsFile(project, virtualFile, false)) return false;
366     Result vcs = listeners.vcsThinksItChanged(virtualFile);
367     if (vcs == Result.CHANGED) return true;
368     if (vcs == Result.UNCHANGED) return false;
369
370     return listeners.canUndo(virtualFile);
371   }
372
373   private boolean canUndo(@NotNull VirtualFile virtualFile) {
374     for (FileEditor editor : myFileEditorManager.getEditors(virtualFile)) {
375       if (myUndoManager.isUndoAvailable(editor)) return true;
376     }
377     return false;
378   }
379
380   private enum Result {
381     CHANGED, UNCHANGED, NOT_SURE
382   }
383
384   private Result vcsThinksItChanged(VirtualFile virtualFile) {
385     AbstractVcs activeVcs = myProjectLevelVcsManager.getVcsFor(virtualFile);
386     if (activeVcs == null) return Result.NOT_SURE;
387
388     FilePath path = VcsUtil.getFilePath(virtualFile);
389     boolean vcsIsThinking = !myVcsDirtyScopeManager.whatFilesDirty(Collections.singletonList(path)).isEmpty();
390     if (vcsIsThinking) return Result.NOT_SURE; // do not modify file which is in the process of updating
391
392     FileStatus status = myFileStatusManager.getStatus(virtualFile);
393     if (status == FileStatus.UNKNOWN) return Result.NOT_SURE;
394     return status == FileStatus.MODIFIED || status == FileStatus.ADDED ? Result.CHANGED : Result.UNCHANGED;
395   }
396
397   private class MyApplicationListener extends ApplicationAdapter {
398     private boolean myDaemonWasRunning;
399
400     @Override
401     public void beforeWriteActionStart(@NotNull Object action) {
402       myDaemonWasRunning = myDaemonCodeAnalyzer.isRunning();
403       if (!myDaemonWasRunning) return; // we'll restart in writeActionFinished()
404       stopDaemon(true, "Write action start");
405     }
406
407     @Override
408     public void writeActionFinished(@NotNull Object action) {
409       stopDaemon(true, "Write action finish");
410     }
411   }
412
413   private class MyCommandListener extends CommandAdapter {
414     private final String myCutActionName = myActionManager.getAction(IdeActions.ACTION_EDITOR_CUT).getTemplatePresentation().getText();
415
416     @Override
417     public void commandStarted(CommandEvent event) {
418       Document affectedDocument = extractDocumentFromCommand(event);
419       if (!worthBothering(affectedDocument, event.getProject())) return;
420
421       cutOperationJustHappened = myCutActionName.equals(event.getCommandName());
422       if (!myDaemonCodeAnalyzer.isRunning()) return;
423       if (LOG.isDebugEnabled()) {
424         LOG.debug("cancelling code highlighting by command:" + event.getCommand());
425       }
426       stopDaemon(false, "Command start");
427     }
428
429     @Nullable
430     private Document extractDocumentFromCommand(CommandEvent event) {
431       Document affectedDocument = event.getDocument();
432       if (affectedDocument != null) return affectedDocument;
433       Object id = event.getCommandGroupId();
434
435       if (id instanceof Document) {
436         affectedDocument = (Document)id;
437       }
438       else if (id instanceof DocCommandGroupId) {
439         affectedDocument = ((DocCommandGroupId)id).getDocument();
440       }
441       return affectedDocument;
442     }
443
444     @Override
445     public void commandFinished(CommandEvent event) {
446       Document affectedDocument = extractDocumentFromCommand(event);
447       if (!worthBothering(affectedDocument, event.getProject())) return;
448
449       if (myEscPressed) {
450         myEscPressed = false;
451         if (affectedDocument != null) {
452           // prevent Esc key to leave the document in the not-highlighted state
453           if (!myDaemonCodeAnalyzer.getFileStatusMap().allDirtyScopesAreNull(affectedDocument)) {
454             stopDaemon(true, "Command finish");
455           }
456         }
457       }
458       else if (!myDaemonCodeAnalyzer.isRunning()) {
459         stopDaemon(true, "Command finish");
460       }
461     }
462   }
463
464   private class MyTodoListener implements PropertyChangeListener {
465     @Override
466     public void propertyChange(@NotNull PropertyChangeEvent evt) {
467       if (TodoConfiguration.PROP_TODO_PATTERNS.equals(evt.getPropertyName())) {
468         stopDaemonAndRestartAllFiles("Todo patterns changed");
469       }
470     }
471   }
472
473   private class MyProfileChangeListener implements ProfileChangeAdapter {
474     @Override
475     public void profileChanged(InspectionProfile profile) {
476       stopDaemonAndRestartAllFiles("Profile changed");
477     }
478
479     @Override
480     public void profileActivated(InspectionProfile oldProfile, @Nullable InspectionProfile profile) {
481       stopDaemonAndRestartAllFiles("Profile activated");
482     }
483
484     @Override
485     public void profilesInitialized() {
486       inspectionProfilesInitialized();
487     }
488   }
489
490   private TogglePopupHintsPanel myTogglePopupHintsPanel;
491   private void inspectionProfilesInitialized() {
492     UIUtil.invokeLaterIfNeeded(() -> {
493       if (myProject.isDisposed()) return;
494       StatusBar statusBar = WindowManager.getInstance().getStatusBar(myProject);
495       myTogglePopupHintsPanel = new TogglePopupHintsPanel(myProject);
496       statusBar.addWidget(myTogglePopupHintsPanel, myProject);
497       updateStatusBar();
498
499       stopDaemonAndRestartAllFiles("Inspection profiles activated");
500     });
501   }
502
503   public void updateStatusBar() {
504     if (myTogglePopupHintsPanel != null) myTogglePopupHintsPanel.updateStatus();
505   }
506
507   private class MyAnActionListener extends AnActionListener.Adapter {
508     private final AnAction escapeAction = myActionManager.getAction(IdeActions.ACTION_EDITOR_ESCAPE);
509
510     @Override
511     public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
512       myEscPressed = action == escapeAction;
513     }
514
515     @Override
516     public void beforeEditorTyping(char c, DataContext dataContext) {
517       Editor editor = CommonDataKeys.EDITOR.getData(dataContext);
518       //no need to stop daemon if something happened in the console
519       if (editor != null && !worthBothering(editor.getDocument(), editor.getProject())) {
520         return;
521       }
522       stopDaemon(true, "Editor typing");
523     }
524   }
525
526   private static class MyEditorMouseListener extends EditorMouseAdapter {
527     @NotNull
528     private final TooltipController myTooltipController;
529
530     MyEditorMouseListener(@NotNull TooltipController tooltipController) {
531       myTooltipController = tooltipController;
532     }
533
534     @Override
535     public void mouseExited(EditorMouseEvent e) {
536       if (!myTooltipController.shouldSurvive(e.getMouseEvent())) {
537         DaemonTooltipUtil.cancelTooltips();
538       }
539     }
540   }
541
542   private class MyEditorMouseMotionListener implements EditorMouseMotionListener {
543     @Override
544     public void mouseMoved(EditorMouseEvent e) {
545       Editor editor = e.getEditor();
546       if (myProject != editor.getProject()) return;
547       if (editor.getComponent().getClientProperty(EditorImpl.IGNORE_MOUSE_TRACKING) != null) return;
548
549       boolean shown = false;
550       try {
551         // There is a possible case that cursor is located at soft wrap-introduced virtual space (that is mapped to offset
552         // of the document symbol just after soft wrap). We don't want to show any tooltips for it then.
553         VisualPosition visual = editor.xyToVisualPosition(e.getMouseEvent().getPoint());
554         if (editor.getSoftWrapModel().isInsideOrBeforeSoftWrap(visual)) {
555           return;
556         }
557         LogicalPosition logical = editor.visualToLogicalPosition(visual);
558         if (e.getArea() == EditorMouseEventArea.EDITING_AREA && !UIUtil.isControlKeyDown(e.getMouseEvent())) {
559           int offset = editor.logicalPositionToOffset(logical);
560           if (editor.offsetToLogicalPosition(offset).column != logical.column) return; // we are in virtual space
561           HighlightInfo info = myDaemonCodeAnalyzer.findHighlightByOffset(editor.getDocument(), offset, false);
562           if (info == null || info.getDescription() == null) {
563             IdeTooltipManager.getInstance().hideCurrent(e.getMouseEvent());
564             return;
565           }
566           DaemonTooltipUtil.showInfoTooltip(info, editor, offset);
567           shown = true;
568         }
569       }
570       finally {
571         if (!shown && !myTooltipController.shouldSurvive(e.getMouseEvent())) {
572           DaemonTooltipUtil.cancelTooltips();
573         }
574       }
575     }
576
577     @Override
578     public void mouseDragged(EditorMouseEvent e) {
579       myTooltipController.cancelTooltips();
580     }
581   }
582
583   private void stopDaemon(boolean toRestartAlarm, @NonNls @NotNull String reason) {
584     if (myDaemonCodeAnalyzer.stopProcess(toRestartAlarm, reason)) {
585       myDaemonEventPublisher.daemonCancelEventOccurred(reason);
586     }
587   }
588
589   private void stopDaemonAndRestartAllFiles(@NotNull String reason) {
590     if (myDaemonCodeAnalyzer.doRestart()) {
591       myDaemonEventPublisher.daemonCancelEventOccurred(reason);
592     }
593   }
594
595   static void repaintErrorStripeRenderer(@NotNull Editor editor, @NotNull Project project) {
596     ApplicationManager.getApplication().assertIsDispatchThread();
597     if (!project.isInitialized()) return;
598     final Document document = editor.getDocument();
599     final PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document);
600     final EditorMarkupModel markup = (EditorMarkupModel)editor.getMarkupModel();
601     markup.setErrorPanelPopupHandler(new DaemonEditorPopup(psiFile));
602     markup.setErrorStripTooltipRendererProvider(new DaemonTooltipRendererProvider(project));
603     markup.setMinMarkHeight(DaemonCodeAnalyzerSettings.getInstance().ERROR_STRIPE_MARK_MIN_HEIGHT);
604     TrafficLightRenderer.setOrRefreshErrorStripeRenderer(markup, project, document, psiFile);
605   }
606 }