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