e9f0b8b7543ddc9e05d6f3ee8005b036f318c193
[idea/community.git] / java / java-tests / testSrc / com / intellij / codeInsight / daemon / impl / DaemonRespondToChangesTest.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 package com.intellij.codeInsight.daemon.impl;
17
18 import com.intellij.codeHighlighting.*;
19 import com.intellij.codeInsight.EditorInfo;
20 import com.intellij.codeInsight.completion.CompletionContributor;
21 import com.intellij.codeInsight.daemon.*;
22 import com.intellij.codeInsight.daemon.impl.quickfix.DeleteCatchFix;
23 import com.intellij.codeInsight.daemon.quickFix.LightQuickFixTestCase;
24 import com.intellij.codeInsight.folding.CodeFoldingManager;
25 import com.intellij.codeInsight.generation.actions.CommentByLineCommentAction;
26 import com.intellij.codeInsight.hint.EditorHintListener;
27 import com.intellij.codeInsight.intention.AbstractIntentionAction;
28 import com.intellij.codeInsight.intention.IntentionAction;
29 import com.intellij.codeInsight.intention.IntentionManager;
30 import com.intellij.codeInsight.intention.impl.IntentionHintComponent;
31 import com.intellij.codeInspection.InspectionProfile;
32 import com.intellij.codeInspection.LocalInspectionTool;
33 import com.intellij.codeInspection.ProblemsHolder;
34 import com.intellij.codeInspection.accessStaticViaInstance.AccessStaticViaInstance;
35 import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection;
36 import com.intellij.codeInspection.deadCode.UnusedDeclarationInspectionBase;
37 import com.intellij.codeInspection.ex.InspectionProfileImpl;
38 import com.intellij.codeInspection.ex.InspectionToolRegistrar;
39 import com.intellij.codeInspection.ex.InspectionToolWrapper;
40 import com.intellij.codeInspection.ex.LocalInspectionToolWrapper;
41 import com.intellij.codeInspection.htmlInspections.RequiredAttributesInspectionBase;
42 import com.intellij.codeInspection.varScopeCanBeNarrowed.FieldCanBeLocalInspection;
43 import com.intellij.diagnostic.PerformanceWatcher;
44 import com.intellij.execution.filters.TextConsoleBuilderFactory;
45 import com.intellij.execution.ui.ConsoleView;
46 import com.intellij.execution.ui.ConsoleViewContentType;
47 import com.intellij.ide.DataManager;
48 import com.intellij.ide.GeneralSettings;
49 import com.intellij.ide.SaveAndSyncHandlerImpl;
50 import com.intellij.ide.highlighter.JavaFileType;
51 import com.intellij.javaee.ExternalResourceManagerExImpl;
52 import com.intellij.lang.CompositeLanguage;
53 import com.intellij.lang.ExternalLanguageAnnotators;
54 import com.intellij.lang.LanguageFilter;
55 import com.intellij.lang.StdLanguages;
56 import com.intellij.lang.annotation.AnnotationHolder;
57 import com.intellij.lang.annotation.ExternalAnnotator;
58 import com.intellij.lang.annotation.HighlightSeverity;
59 import com.intellij.lang.java.JavaLanguage;
60 import com.intellij.openapi.Disposable;
61 import com.intellij.openapi.actionSystem.AnActionEvent;
62 import com.intellij.openapi.actionSystem.DataConstants;
63 import com.intellij.openapi.actionSystem.DataContext;
64 import com.intellij.openapi.actionSystem.IdeActions;
65 import com.intellij.openapi.actionSystem.impl.SimpleDataContext;
66 import com.intellij.openapi.application.AccessToken;
67 import com.intellij.openapi.application.ApplicationManager;
68 import com.intellij.openapi.application.Result;
69 import com.intellij.openapi.application.WriteAction;
70 import com.intellij.openapi.application.ex.ApplicationEx;
71 import com.intellij.openapi.application.ex.ApplicationManagerEx;
72 import com.intellij.openapi.command.CommandProcessor;
73 import com.intellij.openapi.command.WriteCommandAction;
74 import com.intellij.openapi.command.undo.UndoManager;
75 import com.intellij.openapi.components.AbstractProjectComponent;
76 import com.intellij.openapi.components.impl.stores.StorageUtil;
77 import com.intellij.openapi.editor.*;
78 import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
79 import com.intellij.openapi.editor.actionSystem.EditorActionManager;
80 import com.intellij.openapi.editor.actionSystem.TypedAction;
81 import com.intellij.openapi.editor.ex.EditorEx;
82 import com.intellij.openapi.editor.ex.MarkupModelEx;
83 import com.intellij.openapi.editor.ex.RangeHighlighterEx;
84 import com.intellij.openapi.editor.impl.DocumentMarkupModel;
85 import com.intellij.openapi.editor.impl.EditorImpl;
86 import com.intellij.openapi.editor.impl.event.MarkupModelListener;
87 import com.intellij.openapi.editor.markup.GutterIconRenderer;
88 import com.intellij.openapi.editor.markup.HighlighterTargetArea;
89 import com.intellij.openapi.editor.markup.MarkupModel;
90 import com.intellij.openapi.editor.markup.RangeHighlighter;
91 import com.intellij.openapi.fileEditor.FileEditorManager;
92 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
93 import com.intellij.openapi.fileEditor.TextEditor;
94 import com.intellij.openapi.fileEditor.impl.text.PsiAwareTextEditorProvider;
95 import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
96 import com.intellij.openapi.fileTypes.PlainTextFileType;
97 import com.intellij.openapi.fileTypes.StdFileTypes;
98 import com.intellij.openapi.module.Module;
99 import com.intellij.openapi.progress.ProcessCanceledException;
100 import com.intellij.openapi.progress.ProgressIndicator;
101 import com.intellij.openapi.progress.impl.CoreProgressManager;
102 import com.intellij.openapi.project.Project;
103 import com.intellij.openapi.project.ProjectManager;
104 import com.intellij.openapi.project.ex.ProjectManagerEx;
105 import com.intellij.openapi.project.impl.ProjectManagerImpl;
106 import com.intellij.openapi.util.Disposer;
107 import com.intellij.openapi.util.ProperTextRange;
108 import com.intellij.openapi.util.Segment;
109 import com.intellij.openapi.util.TextRange;
110 import com.intellij.openapi.util.text.StringUtil;
111 import com.intellij.openapi.vfs.VirtualFile;
112 import com.intellij.profile.codeInspection.InspectionProfileManager;
113 import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
114 import com.intellij.psi.*;
115 import com.intellij.psi.impl.DebugUtil;
116 import com.intellij.psi.search.GlobalSearchScope;
117 import com.intellij.refactoring.inline.InlineRefactoringActionHandler;
118 import com.intellij.refactoring.rename.RenameProcessor;
119 import com.intellij.testFramework.*;
120 import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl;
121 import com.intellij.ui.HintListener;
122 import com.intellij.ui.LightweightHint;
123 import com.intellij.util.*;
124 import com.intellij.util.containers.ContainerUtil;
125 import com.intellij.util.io.storage.HeavyProcessLatch;
126 import com.intellij.util.ui.UIUtil;
127 import com.intellij.xml.util.CheckDtdReferencesInspection;
128 import gnu.trove.THashSet;
129 import org.jetbrains.annotations.Nls;
130 import org.jetbrains.annotations.NonNls;
131 import org.jetbrains.annotations.NotNull;
132 import org.jetbrains.annotations.Nullable;
133
134 import java.awt.*;
135 import java.io.File;
136 import java.io.IOException;
137 import java.lang.reflect.Method;
138 import java.util.*;
139 import java.util.List;
140 import java.util.concurrent.atomic.AtomicBoolean;
141 import java.util.concurrent.atomic.AtomicLong;
142 import java.util.concurrent.atomic.AtomicReference;
143
144 /**
145  * @author cdr
146  */
147 @SkipSlowTestLocally
148 public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase {
149   private static final String BASE_PATH = "/codeInsight/daemonCodeAnalyzer/typing/";
150
151   private DaemonCodeAnalyzerImpl myDaemonCodeAnalyzer;
152
153   @Override
154   protected void setUp() throws Exception {
155     super.setUp();
156     enableInspectionTool(new UnusedDeclarationInspection());
157     myDaemonCodeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
158     UndoManager.getInstance(myProject);
159     myDaemonCodeAnalyzer.setUpdateByTimerEnabled(true);
160     DaemonProgressIndicator.setDebug(true);
161   }
162
163   @Override
164   protected void tearDown() throws Exception {
165     try {
166       Project project = getProject();
167       if (project != null) {
168         doPostponedFormatting(project);
169         ((ProjectManagerImpl)ProjectManagerEx.getInstanceEx()).closeProject(project, false, false, false);
170       }
171     }
172     finally {
173       super.tearDown();
174     }
175   }
176
177   @Override
178   protected boolean doTestLineMarkers() {
179     return true;
180   }
181
182   @Override
183   protected void setUpProject() throws Exception {
184     super.setUpProject();
185     ProjectManagerEx.getInstanceEx().openProject(getProject());
186     super.runStartupActivities();
187     UIUtil.dispatchAllInvocationEvents(); // startup activities
188   }
189
190   @Override
191   protected void runStartupActivities() {
192
193   }
194
195   private static void typeInAlienEditor(Editor alienEditor, char c) {
196     TypedAction action = EditorActionManager.getInstance().getTypedAction();
197     DataContext dataContext = ((EditorEx)alienEditor).getDataContext();
198
199     action.actionPerformed(alienEditor, c, dataContext);
200   }
201
202   
203   public void testHighlightersUpdate() throws Exception {
204     configureByFile(BASE_PATH + "HighlightersUpdate.java");
205     Document document = getDocument(getFile());
206     highlightErrors();
207     List<HighlightInfo> errors = DaemonCodeAnalyzerImpl.getHighlights(document, HighlightSeverity.ERROR, getProject());
208     assertEquals(1, errors.size());
209     TextRange dirty = myDaemonCodeAnalyzer.getFileStatusMap().getFileDirtyScope(document, Pass.UPDATE_ALL);
210     assertNull(dirty);
211
212     type(' ');
213     PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
214     dirty = myDaemonCodeAnalyzer.getFileStatusMap().getFileDirtyScope(document, Pass.UPDATE_ALL);
215     assertNotNull(dirty);
216   }
217
218   
219   public void testNoPsiEventsAltogether() throws Exception {
220     configureByFile(BASE_PATH + "HighlightersUpdate.java");
221     Document document = getDocument(getFile());
222     highlightErrors();
223     type(' ');
224     backspace();
225     PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
226
227     TextRange dirty = myDaemonCodeAnalyzer.getFileStatusMap().getFileDirtyScope(document, Pass.UPDATE_ALL);
228     assertEquals(getFile().getTextRange(), dirty); // have to rehighlight whole file in case no PSI events have come
229   }
230
231   public void testRenameClass() throws Exception {
232     configureByFile(BASE_PATH + "AClass.java");
233     Document document = getDocument(getFile());
234     Collection<HighlightInfo> infos = highlightErrors();
235     assertEquals(0, infos.size());
236     final PsiClass psiClass = ((PsiJavaFile)getFile()).getClasses()[0];
237     ApplicationManager.getApplication().runWriteAction(() -> {
238       new RenameProcessor(myProject, psiClass, "Class2", false, false).run();
239
240       TextRange dirty = myDaemonCodeAnalyzer.getFileStatusMap().getFileDirtyScope(document, Pass.UPDATE_ALL);
241       assertEquals(getFile().getTextRange(), dirty);
242     });
243
244     highlightErrors();
245     assertTrue(myDaemonCodeAnalyzer.isErrorAnalyzingFinished(getFile()));
246   }
247
248   
249   public void testTypingSpace() throws Exception {
250     configureByFile(BASE_PATH + "AClass.java");
251     Document document = getDocument(getFile());
252     Collection<HighlightInfo> infos = highlightErrors();
253     assertEquals(0, infos.size());
254
255     type("  ");
256     PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
257     PsiElement elementAtCaret = myFile.findElementAt(myEditor.getCaretModel().getOffset());
258     assertTrue(elementAtCaret instanceof PsiWhiteSpace);
259
260     TextRange dirty = myDaemonCodeAnalyzer.getFileStatusMap().getFileDirtyScope(document, Pass.UPDATE_ALL);
261     assertEquals(elementAtCaret.getTextRange(), dirty);
262     highlightErrors();
263     assertTrue(myDaemonCodeAnalyzer.isErrorAnalyzingFinished(getFile()));
264   }
265
266   
267   public void testTypingSpaceInsideError() throws Exception {
268     configureByFile(BASE_PATH + "Error.java");
269     Collection<HighlightInfo> infos = highlightErrors();
270     assertEquals(1, infos.size());
271
272     for (int i = 0; i < 100; i++) {
273       type(" ");
274       List<HighlightInfo> errors = highlightErrors();
275       assertEquals(1, errors.size());
276     }
277   }
278
279   
280   public void testBackSpaceInsideError() throws Exception {
281     configureByFile(BASE_PATH + "BackError.java");
282     Collection<HighlightInfo> infos = highlightErrors();
283     assertEquals(1, infos.size());
284
285     backspace();
286     List<HighlightInfo> errors = highlightErrors();
287     assertEquals(1, errors.size());
288   }
289
290   @Override
291   protected LocalInspectionTool[] configureLocalInspectionTools() {
292     if (isPerformanceTest() && !getTestName(false).equals("TypingCurliesClearsEndOfFileErrorsInPhp_ItIsPerformanceTestBecauseItRunsTooLong")) {
293       // all possible inspections
294       List<InspectionToolWrapper> all = InspectionToolRegistrar.getInstance().createTools();
295       List<LocalInspectionTool> locals = new ArrayList<>();
296       all.stream().filter(tool -> tool instanceof LocalInspectionToolWrapper).forEach(tool -> {
297         LocalInspectionTool e = ((LocalInspectionToolWrapper)tool).getTool();
298         locals.add(e);
299       });
300       return locals.toArray(new LocalInspectionTool[locals.size()]);
301     }
302     return new LocalInspectionTool[]{
303       new FieldCanBeLocalInspection(),
304       new RequiredAttributesInspectionBase(),
305       new CheckDtdReferencesInspection(),
306       new AccessStaticViaInstance(),
307     };
308   }
309
310   
311   public void testUnusedFieldUpdate() throws Exception {
312     configureByFile(BASE_PATH + "UnusedField.java");
313     Document document = getDocument(getFile());
314     List<HighlightInfo> infos = doHighlighting(HighlightSeverity.WARNING);
315     assertEquals(1, infos.size());
316     assertEquals("Private field 'ffff' is never used", infos.get(0).getDescription());
317
318     type("  foo(ffff++);");
319     highlightErrors();
320
321     List<HighlightInfo> errors = DaemonCodeAnalyzerImpl.getHighlights(document, HighlightSeverity.WARNING, getProject());
322     assertEquals(0, errors.size());
323   }
324
325   public void testUnusedMethodUpdate() throws Exception {
326     configureByText(JavaFileType.INSTANCE, "class X {\n" +
327                                            "    static void ffff() {}\n" +
328                                            "    public static void main(String[] args){\n" +
329                                            "        for (int i=0; i<1000;i++) {\n" +
330                                            "            System.out.println(i);\n" +
331                                            "            <caret>ffff();\n" +
332                                            "        }\n" +
333                                            "    }\n}");
334     enableInspectionTool(new UnusedDeclarationInspection(true));
335     List<HighlightInfo> infos = doHighlighting(HighlightSeverity.WARNING);
336     assertEmpty(infos);
337
338     commentLine();
339     infos = doHighlighting(HighlightSeverity.WARNING);
340
341     assertEquals(1, infos.size());
342     assertEquals("Method 'ffff()' is never used", infos.get(0).getDescription());
343   }
344
345
346   public void testAssignedButUnreadFieldUpdate() throws Exception {
347     configureByFile(BASE_PATH + "AssignedButUnreadField.java");
348     List<HighlightInfo> infos = doHighlighting(HighlightSeverity.WARNING);
349     assertEquals(1, infos.size());
350     assertEquals("Private field 'text' is assigned but never accessed", infos.get(0).getDescription());
351
352     ctrlW();
353     WriteCommandAction.runWriteCommandAction(getProject(), () -> {
354       EditorModificationUtil.deleteSelectedText(getEditor());
355       type("  text");
356     });
357
358     List<HighlightInfo> errors = doHighlighting(HighlightSeverity.WARNING);
359     assertEmpty(getFile().getText(), errors);
360   }
361
362   public void testDaemonIgnoresNonPhysicalEditor() throws Exception {
363     configureByFile(BASE_PATH + "AClass.java");
364     highlightErrors();
365
366     EditorFactory editorFactory = EditorFactory.getInstance();
367     final Document consoleDoc = editorFactory.createDocument("my console blah");
368     final Editor consoleEditor = editorFactory.createEditor(consoleDoc);
369
370     try {
371       checkDaemonReaction(false, () -> caretRight(consoleEditor));
372       checkDaemonReaction(true, () -> typeInAlienEditor(consoleEditor, 'x'));
373       checkDaemonReaction(true, () -> LightPlatformCodeInsightTestCase.backspace(consoleEditor, getProject()));
374
375       //real editor
376       checkDaemonReaction(true, this::caretRight);
377     }
378     finally {
379       editorFactory.releaseEditor(consoleEditor);
380     }
381   }
382
383   
384   public void testDaemonIgnoresConsoleActivities() throws Exception {
385     configureByFile(BASE_PATH + "AClass.java");
386     doHighlighting(HighlightSeverity.WARNING);
387
388     final ConsoleView consoleView = TextConsoleBuilderFactory.getInstance().createBuilder(getProject()).getConsole();
389
390     consoleView.getComponent(); //create editor
391     consoleView.print("haha", ConsoleViewContentType.NORMAL_OUTPUT);
392     UIUtil.dispatchAllInvocationEvents();
393
394     try {
395       checkDaemonReaction(false, () -> {
396         consoleView.clear();
397         try {
398           Thread.sleep(300); // *&^ing alarm
399         }
400         catch (InterruptedException e) {
401           LOG.error(e);
402         }
403         UIUtil.dispatchAllInvocationEvents(); //flush
404       });
405       checkDaemonReaction(false, () -> {
406         consoleView.print("sss", ConsoleViewContentType.NORMAL_OUTPUT);
407         try {
408           Thread.sleep(300); // *&^ing alarm
409         }
410         catch (InterruptedException e) {
411           LOG.error(e);
412         }
413         UIUtil.dispatchAllInvocationEvents(); //flush
414       });
415       checkDaemonReaction(false, () -> {
416         consoleView.setOutputPaused(true);
417         try {
418           Thread.sleep(300); // *&^ing alarm
419         }
420         catch (InterruptedException e) {
421           LOG.error(e);
422         }
423         UIUtil.dispatchAllInvocationEvents(); //flush
424       });
425     }
426     finally {
427       Disposer.dispose(consoleView);
428     }
429   }
430
431   private void checkDaemonReaction(boolean mustCancelItself, @NotNull final Runnable action) {
432     PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
433     highlightErrors();
434     myDaemonCodeAnalyzer.waitForTermination();
435     TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(getEditor());
436
437     final AtomicBoolean run = new AtomicBoolean();
438     Disposable disposable = Disposer.newDisposable();
439     final AtomicReference<RuntimeException> stopDaemonReason = new AtomicReference<>();
440     StorageUtil.DEBUG_LOG = "";
441     getProject().getMessageBus().connect(disposable).subscribe(DaemonCodeAnalyzer.DAEMON_EVENT_TOPIC,
442             new DaemonCodeAnalyzer.DaemonListenerAdapter() {
443               @Override
444               public void daemonCancelEventOccurred(@NotNull String reason) {
445                 stopDaemonReason.compareAndSet(null, new RuntimeException("Some bastard's restarted daemon: " + reason + "\nStorage write log: "+
446                                                                           StorageUtil.DEBUG_LOG));
447               }
448             });
449     try {
450       while (true) {
451         try {
452           myDaemonCodeAnalyzer.runPasses(getFile(), getDocument(getFile()), textEditor, new int[0], true, () -> {
453             if (!run.getAndSet(true)) {
454               action.run();
455             }
456           });
457           break;
458         }
459         catch (ProcessCanceledException ignored) { }
460       }
461
462       if (mustCancelItself) {
463         assertNotNull(stopDaemonReason.get());
464       }
465       else {
466         if (stopDaemonReason.get() != null) throw stopDaemonReason.get();
467       }
468     }
469     finally {
470       StorageUtil.DEBUG_LOG = null;
471       Disposer.dispose(disposable);
472     }
473   }
474
475   
476   public void testWholeFileInspection() throws Exception {
477     configureByFile(BASE_PATH + "FieldCanBeLocal.java");
478     List<HighlightInfo> infos = doHighlighting(HighlightSeverity.WARNING);
479     assertEquals(1, infos.size());
480     assertEquals("Field can be converted to a local variable", infos.get(0).getDescription());
481
482     ctrlW();
483     WriteCommandAction.runWriteCommandAction(getProject(), () -> {
484       EditorModificationUtil.deleteSelectedText(getEditor());
485       type("xxxx");
486     });
487
488     infos = doHighlighting(HighlightSeverity.WARNING);
489     assertEmpty(infos);
490
491     ctrlW();
492     WriteCommandAction.runWriteCommandAction(getProject(), () -> {
493       EditorModificationUtil.deleteSelectedText(getEditor());
494       type("0");
495     });
496
497     infos = doHighlighting(HighlightSeverity.WARNING);
498     assertEquals(1, infos.size());
499     assertEquals("Field can be converted to a local variable", infos.get(0).getDescription());
500   }
501
502   private static class MyWholeInspection extends LocalInspectionTool {
503     private final List<PsiElement> visited = Collections.synchronizedList(new ArrayList<>());
504
505     @Nls
506     @NotNull
507     @Override
508     public String getGroupDisplayName() {
509       return "fegna";
510     }
511
512     @Nls
513     @NotNull
514     @Override
515     public String getDisplayName() {
516       return getGroupDisplayName();
517     }
518
519     @NotNull
520     @Override
521     public String getShortName() {
522       return getGroupDisplayName();
523     }
524
525     @NotNull
526     @Override
527     public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
528       return new PsiElementVisitor() {
529         @Override
530         public void visitFile(PsiFile file) {
531           TimeoutUtil.sleep(1000); // make it run longer that LIP
532           super.visitFile(file);
533         }
534
535         @Override
536         public void visitElement(PsiElement element) {
537           visited.add(element);
538           super.visitElement(element);
539         }
540       };
541     }
542
543     @Override
544     public boolean runForWholeFile() {
545       return true;
546     }
547   }
548
549   public void testWholeFileInspectionRestartedOnAllElements() throws Exception {
550     MyWholeInspection tool = new MyWholeInspection();
551     enableInspectionTool(tool);
552     disposeOnTearDown(() -> disableInspectionTool(tool.getShortName()));
553
554     configureByText(JavaFileType.INSTANCE, "class X { void f() { <caret> } }");
555     List<HighlightInfo> infos = doHighlighting(HighlightSeverity.WARNING);
556     assertEmpty(infos);
557     int visitedCount = new HashSet<>(tool.visited).size();
558     tool.visited.clear();
559
560     type(" ");  // white space modification
561
562     infos = doHighlighting(HighlightSeverity.WARNING);
563     assertEmpty(infos);
564
565     int countAfter = new HashSet<>(tool.visited).size();
566     assertTrue("visitedCount = "+visitedCount+"; countAfter="+countAfter, countAfter >= visitedCount);
567   }
568
569   public void testWholeFileInspectionRestartedEvenIfThereWasAModificationInsideCodeBlockInOtherFile() throws Exception {
570     MyWholeInspection tool = new MyWholeInspection();
571
572     enableInspectionTool(tool);
573     disposeOnTearDown(() -> disableInspectionTool(tool.getShortName()));
574
575     PsiFile file = configureByText(JavaFileType.INSTANCE, "class X { void f() { <caret> } }");
576     PsiFile otherFile = createFile(myModule, file.getContainingDirectory().getVirtualFile(), "otherFile.txt", "xxx");
577     List<HighlightInfo> infos = doHighlighting(HighlightSeverity.WARNING);
578     assertEmpty(infos);
579     int visitedCount = tool.visited.size();
580     assertTrue(tool.visited.toString(), visitedCount > 0);
581     tool.visited.clear();
582
583     Document otherDocument = PsiDocumentManager.getInstance(getProject()).getDocument(otherFile);
584     WriteCommandAction.runWriteCommandAction(getProject(), () -> otherDocument.setText("zzz"));
585
586     infos = doHighlighting(HighlightSeverity.WARNING);
587     assertEmpty(infos);
588
589     int countAfter = tool.visited.size();
590     assertTrue(tool.visited.toString(), countAfter > 0);
591   }
592
593   public void testOverriddenMethodMarkers() throws Exception {
594     configureByFile(BASE_PATH + getTestName(false) + ".java");
595     highlightErrors();
596
597     Document document = getEditor().getDocument();
598     List<LineMarkerInfo> markers = DaemonCodeAnalyzerImpl.getLineMarkers(document, getProject());
599     assertEquals(3, markers.size());
600
601     type("//xxxx");
602
603     highlightErrors();
604     markers = DaemonCodeAnalyzerImpl.getLineMarkers(document, getProject());
605     assertEquals(3, markers.size());
606   }
607
608   
609   public void testOverriddenMethodMarkersDoNotClearedByCHangingWhitespaceNearby() throws Exception {
610     configureByFile(BASE_PATH + "OverriddenMethodMarkers.java");
611     highlightErrors();
612
613     Document document = getEditor().getDocument();
614     List<LineMarkerInfo> markers = DaemonCodeAnalyzerImpl.getLineMarkers(document, getProject());
615     assertEquals(3, markers.size());
616
617     PsiElement element = ((PsiJavaFile)myFile).getClasses()[0].findMethodsByName("f", false)[0].getReturnTypeElement().getNextSibling();
618     assertEquals("   ", element.getText());
619     getEditor().getCaretModel().moveToOffset(element.getTextOffset() + 1);
620     type(" ");
621
622     highlightErrors();
623     markers = DaemonCodeAnalyzerImpl.getLineMarkers(document, getProject());
624     assertEquals(3, markers.size());
625   }
626
627   private static void commentLine() {
628     WriteCommandAction.runWriteCommandAction(null, () -> {
629       CommentByLineCommentAction action = new CommentByLineCommentAction();
630       action.actionPerformed(AnActionEvent.createFromAnAction(action, null, "", DataManager.getInstance().getDataContext()));
631     });
632   }
633
634   
635   public void testChangeXmlIncludeLeadsToRehighlight() throws Exception {
636     LanguageFilter[] extensions = ((CompositeLanguage)StdLanguages.XML).getLanguageExtensions();
637     for (LanguageFilter extension : extensions) {
638       ((CompositeLanguage)StdLanguages.XML).unregisterLanguageExtension(extension);
639     }
640
641     final String location = getTestName(false) + ".xsd";
642     final String url = "http://myschema/";
643     ExternalResourceManagerExImpl.registerResourceTemporarily(url, location, getTestRootDisposable());
644
645     configureByFiles(null, BASE_PATH + getTestName(false) + ".xml", BASE_PATH + getTestName(false) + ".xsd");
646
647     Collection<HighlightInfo> errors = highlightErrors();
648     assertEquals(0, errors.size());
649
650     Editor[] allEditors = EditorFactory.getInstance().getAllEditors();
651     Editor schemaEditor = null;
652     for (Editor editor : allEditors) {
653       Document document = editor.getDocument();
654       PsiFile file = PsiDocumentManager.getInstance(getProject()).getPsiFile(document);
655       if (file == null) continue;
656       if (location.equals(file.getName())) {
657         schemaEditor = editor;
658         break;
659       }
660     }
661     delete(schemaEditor);
662
663     errors = highlightErrors();
664     assertFalse(errors.isEmpty());
665
666     for (LanguageFilter extension : extensions) {
667       ((CompositeLanguage)StdLanguages.XML).registerLanguageExtension(extension);
668     }
669   }
670
671   
672   public void testRehighlightInnerBlockAfterInline() throws Exception {
673     configureByFile(BASE_PATH + getTestName(false) + ".java");
674
675     Collection<HighlightInfo> errors = highlightErrors();
676     HighlightInfo error = assertOneElement(errors);
677     assertEquals("Variable 'e' is already defined in the scope", error.getDescription());
678     PsiElement element = getFile().findElementAt(getEditor().getCaretModel().getOffset()).getParent();
679
680     DataContext dataContext = SimpleDataContext.getSimpleContext(DataConstants.PSI_ELEMENT, element, ((EditorEx)getEditor()).getDataContext());
681     new InlineRefactoringActionHandler().invoke(getProject(), getEditor(), getFile(), dataContext);
682
683     Collection<HighlightInfo> afterTyping = highlightErrors();
684     assertEmpty(afterTyping);
685   }
686
687   
688   public void testRangeMarkersDoNotGetAddedOrRemovedWhenUserIsJustTypingInsideHighlightedRegionAndEspeciallyInsideInjectedFragmentsWhichAreColoredGreenAndUsersComplainEndlesslyThatEditorFlickersThere()
689     throws Throwable {
690     configureByText(JavaFileType.INSTANCE, "class S { int f() {\n" +
691                                            "    return <caret>hashCode();\n" +
692                                            "}}");
693
694     Collection<HighlightInfo> infos = doHighlighting(HighlightInfoType.SYMBOL_TYPE_SEVERITY);
695     assertEquals(3, infos.size());
696
697     final int[] count = {0};
698     MarkupModelEx modelEx = (MarkupModelEx)DocumentMarkupModel.forDocument(getDocument(getFile()), getProject(), true);
699     modelEx.addMarkupModelListener(getTestRootDisposable(), new MarkupModelListener.Adapter() {
700       @Override
701       public void afterAdded(@NotNull RangeHighlighterEx highlighter) {
702         count[0]++;
703       }
704
705       @Override
706       public void beforeRemoved(@NotNull RangeHighlighterEx highlighter) {
707         count[0]++;
708       }
709     });
710
711     type(' ');
712     highlightErrors();
713
714     assertEquals(0, count[0]);
715   }
716
717   public void testLineMarkersReuse() throws Throwable {
718     configureByFile(BASE_PATH + "LineMarkerChange.java");
719
720     List<HighlightInfo> errors = highlightErrors();
721     assertEmpty(errors);
722
723     List<LineMarkerInfo> lineMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(myEditor.getDocument(), getProject());
724     assertSize(5, lineMarkers);
725
726     type('X');
727
728     final Collection<String> changed = new ArrayList<>();
729     MarkupModelEx modelEx = (MarkupModelEx)DocumentMarkupModel.forDocument(getDocument(getFile()), getProject(), true);
730     modelEx.addMarkupModelListener(getTestRootDisposable(), new MarkupModelListener() {
731       @Override
732       public void afterAdded(@NotNull RangeHighlighterEx highlighter) {
733         changed(highlighter, ExceptionUtil.getThrowableText(new Throwable("after added")));
734       }
735
736       @Override
737       public void beforeRemoved(@NotNull RangeHighlighterEx highlighter) {
738         changed(highlighter, ExceptionUtil.getThrowableText(new Throwable("before removed")));
739       }
740
741       @Override
742       public void attributesChanged(@NotNull RangeHighlighterEx highlighter, boolean renderersChanged, boolean fontStyleChanged) {
743         changed(highlighter, ExceptionUtil.getThrowableText(new Throwable("changed")));
744       }
745
746       private void changed(@NotNull RangeHighlighterEx highlighter, String reason) {
747         if (highlighter.getTargetArea() != HighlighterTargetArea.LINES_IN_RANGE) return; // not line marker
748         List<LineMarkerInfo> lineMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(myEditor.getDocument(), getProject());
749         if (ContainerUtil.find(lineMarkers, lm -> lm.highlighter == highlighter) == null) return; // not line marker
750
751         changed.add(highlighter+": \n"+reason);
752       }
753     });
754
755     PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
756     List<HighlightInfo> infosAfter = CodeInsightTestFixtureImpl.instantiateAndRun(myFile, myEditor, new int[]{/*Pass.UPDATE_ALL, Pass.LOCAL_INSPECTIONS*/}, false);
757     assertNotEmpty(filter(infosAfter, HighlightSeverity.ERROR));
758
759     assertEmpty(changed);
760     List<LineMarkerInfo> lineMarkersAfter = DaemonCodeAnalyzerImpl.getLineMarkers(myEditor.getDocument(), getProject());
761     assertEquals(lineMarkersAfter.size(), lineMarkers.size());
762   }
763
764   public void testLineMarkersDoNotBlinkOnBackSpaceRightBeforeMethodIdentifier() throws Throwable {
765     configureByText(JavaFileType.INSTANCE, "package x; \n" +
766                                            "class  <caret>ToRun{\n" +
767                                            "  public static void main(String[] args) {\n"+
768                                            "  }\n"+
769                                            "}");
770
771     List<HighlightInfo> errors = highlightErrors();
772     assertEmpty(errors);
773
774     List<LineMarkerInfo> lineMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(myEditor.getDocument(), getProject());
775     assertSize(2, lineMarkers);
776
777     backspace();
778
779     final Collection<String> changed = new ArrayList<>();
780     MarkupModelEx modelEx = (MarkupModelEx)DocumentMarkupModel.forDocument(getDocument(getFile()), getProject(), true);
781     modelEx.addMarkupModelListener(getTestRootDisposable(), new MarkupModelListener() {
782       @Override
783       public void afterAdded(@NotNull RangeHighlighterEx highlighter) {
784         changed(highlighter, ExceptionUtil.getThrowableText(new Throwable("after added")));
785       }
786
787       @Override
788       public void beforeRemoved(@NotNull RangeHighlighterEx highlighter) {
789         changed(highlighter, ExceptionUtil.getThrowableText(new Throwable("before removed")));
790       }
791
792       @Override
793       public void attributesChanged(@NotNull RangeHighlighterEx highlighter, boolean renderersChanged, boolean fontStyleChanged) {
794         changed(highlighter, ExceptionUtil.getThrowableText(new Throwable("changed")));
795       }
796
797       private void changed(@NotNull RangeHighlighterEx highlighter, String reason) {
798         if (highlighter.getTargetArea() != HighlighterTargetArea.LINES_IN_RANGE) return; // not line marker
799         List<LineMarkerInfo> lineMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(myEditor.getDocument(), getProject());
800         if (ContainerUtil.find(lineMarkers, lm -> lm.highlighter == highlighter) == null) return; // not line marker
801
802         changed.add(highlighter+": \n"+reason);
803       }
804     });
805
806     assertEmpty(highlightErrors());
807
808     assertSize(2, DaemonCodeAnalyzerImpl.getLineMarkers(myEditor.getDocument(), getProject()));
809
810     assertEmpty(changed);
811   }
812
813   public void testTypeParametersMustNotBlinkWhenTypingInsideClass() throws Throwable {
814     configureByText(JavaFileType.INSTANCE, "package x; \n" +
815                                            "abstract class ToRun<TTTTTTTTTTTTTTT> implements Comparable<TTTTTTTTTTTTTTT> {\n" +
816                                            "  private ToRun<TTTTTTTTTTTTTTT> delegate;\n"+
817                                            "  <caret>\n"+
818                                            "  \n"+
819                                            "}");
820
821     List<HighlightInfo> errors = highlightErrors();
822     assertEmpty(errors);
823
824     MarkupModelEx modelEx = (MarkupModelEx)DocumentMarkupModel.forDocument(getDocument(getFile()), getProject(), true);
825     modelEx.addMarkupModelListener(getTestRootDisposable(), new MarkupModelListener.Adapter() {
826       @Override
827       public void beforeRemoved(@NotNull RangeHighlighterEx highlighter) {
828         if (TextRange.create(highlighter).substring(highlighter.getDocument().getText()).equals("TTTTTTTTTTTTTTT")) {
829           throw new RuntimeException("Must not remove type parameter highlighter");
830         }
831       }
832     });
833
834     assertEmpty(highlightErrors());
835
836     type("//xxx");
837     assertEmpty(highlightErrors());
838     backspace();
839     assertEmpty(highlightErrors());
840     backspace();
841     assertEmpty(highlightErrors());
842     backspace();
843     assertEmpty(highlightErrors());
844     backspace();
845     backspace();
846     assertEmpty(highlightErrors());
847   }
848
849   public void testOverrideMethodsHighlightingPersistWhenTypeInsideMethodBody() throws Throwable {
850     configureByText(JavaFileType.INSTANCE, "package x; \n" +
851                                            "class ClassA {\n" +
852                                            "    static <T> void sayHello(Class<? extends T> msg) {}\n" +
853                                            "}\n" +
854
855                                            "class ClassB extends ClassA {\n" +
856                                            "    static <T extends String> void sayHello(Class<? extends T> msg) {<caret>\n" +
857                                            "    }\n" +
858                                            "}\n");
859
860     assertSize(1, highlightErrors());
861     type("//my comment inside method body, so class modifier won't be visited");
862     assertSize(1, highlightErrors());
863   }
864
865   public void testLineMarkersClearWhenTypingAtTheEndOfPsiComment() throws Throwable {
866     configureByText(JavaFileType.INSTANCE, "class S {\n//ddd<caret>\n}");
867     StringBuffer log = new StringBuffer();
868     final LineMarkerProvider provider = new LineMarkerProvider() {
869       @Nullable
870       @Override
871       public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) {
872         log.append("getLineMarkerInfo(" + element + ")\n");
873         if (element instanceof PsiComment) {
874           LineMarkerInfo<PsiComment> info = new LineMarkerInfo<>((PsiComment)element, element.getTextRange(), null, Pass.UPDATE_ALL, null, null, GutterIconRenderer.Alignment.LEFT);
875           log.append(info + "\n");
876           return info;
877         }
878         return null;
879       }
880
881       @Override
882       public void collectSlowLineMarkers(@NotNull List<PsiElement> elements, @NotNull Collection<LineMarkerInfo> result) {
883
884       }
885     };
886     LineMarkerProviders.INSTANCE.addExplicitExtension(JavaLanguage.INSTANCE, provider);
887     Disposer.register(myTestRootDisposable, () -> LineMarkerProviders.INSTANCE.removeExplicitExtension(JavaLanguage.INSTANCE, provider));
888     myDaemonCodeAnalyzer.restart();
889     try {
890       List<HighlightInfo> infos = doHighlighting();
891       log.append("File text: '" + getFile().getText() + "'\n");
892       log.append("infos: " + infos + "\n");
893       assertEmpty(filter(infos,HighlightSeverity.ERROR));
894
895       List<LineMarkerInfo> lineMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(myEditor.getDocument(), getProject());
896       assertOneElement(lineMarkers);
897
898       type(' ');
899       infos = doHighlighting();
900       log.append("File text: '" + getFile().getText() + "'\n");
901       log.append("infos: " + infos + "\n");
902       assertEmpty(filter(infos,HighlightSeverity.ERROR));
903
904       lineMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(myEditor.getDocument(), getProject());
905       assertOneElement(lineMarkers);
906
907       backspace();
908       infos = doHighlighting();
909       log.append("File text: '" + getFile().getText() + "'\n");
910       log.append("infos: " + infos + "\n");
911       assertEmpty(filter(infos,HighlightSeverity.ERROR));
912
913       lineMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(myEditor.getDocument(), getProject());
914       assertOneElement(lineMarkers);
915     }
916     catch (AssertionError e) {
917       System.err.println("Log:\n"+log+"\n---");
918       throw e;
919     }
920   }
921
922   public void testWhenTypingOverWrongReferenceItsColorChangesToBlackAndOnlyAfterHighlightingFinishedItReturnsToRed() throws Throwable {
923     configureByText(StdFileTypes.JAVA, "class S {  int f() {\n" +
924                                        "    return asfsdfsdfsd<caret>;\n" +
925                                        "}}");
926
927     Collection<HighlightInfo> errors = highlightErrors();
928     assertOneElement(errors);
929     assertSame(HighlightInfoType.WRONG_REF, errors.iterator().next().type);
930
931     Document document = getDocument(getFile());
932
933     type("xxx");
934
935     List<HighlightInfo> infos = DaemonCodeAnalyzerImpl.getHighlights(document, HighlightInfoType.SYMBOL_TYPE_SEVERITY, getProject());
936     for (HighlightInfo info : infos) {
937       assertNotSame(HighlightInfoType.WRONG_REF, info.type);
938     }
939
940     errors = highlightErrors();
941     assertOneElement(errors);
942     assertSame(HighlightInfoType.WRONG_REF, errors.iterator().next().type);
943   }
944
945   
946   public void testQuickFixRemainsAvailableAfterAnotherFixHasBeenAppliedInTheSameCodeBlockBefore() throws Exception {
947     configureByFile(BASE_PATH + "QuickFixes.java");
948
949     DaemonCodeAnalyzerSettings settings = DaemonCodeAnalyzerSettings.getInstance();
950     boolean old = settings.NEXT_ERROR_ACTION_GOES_TO_ERRORS_FIRST;
951     settings.NEXT_ERROR_ACTION_GOES_TO_ERRORS_FIRST = true;
952
953     try {
954       Collection<HighlightInfo> errors = highlightErrors();
955       assertEquals(3, errors.size());
956       new GotoNextErrorHandler(true).invoke(getProject(), getEditor(), getFile());
957
958       List<IntentionAction> fixes = LightQuickFixTestCase.getAvailableActions(getEditor(), getFile());
959       IntentionAction fix = assertContainsOneOf(fixes, DeleteCatchFix.class);
960       assertEquals("Delete catch for 'java.io.IOException'", fix.getText());
961
962       final IntentionAction finalFix = fix;
963       WriteCommandAction.runWriteCommandAction(getProject(), () -> finalFix.invoke(getProject(), getEditor(), getFile()));
964
965       errors = highlightErrors();
966       assertEquals(2, errors.size());
967
968       new GotoNextErrorHandler(true).invoke(getProject(), getEditor(), getFile());
969       fixes = LightQuickFixTestCase.getAvailableActions(getEditor(), getFile());
970       fix = assertContainsOneOf(fixes, DeleteCatchFix.class);
971       assertEquals("Delete catch for 'java.io.IOException'", fix.getText());
972
973       final IntentionAction finalFix1 = fix;
974       WriteCommandAction.runWriteCommandAction(getProject(), () -> finalFix1.invoke(getProject(), getEditor(), getFile()));
975
976       errors = highlightErrors();
977       assertOneElement(errors);
978
979       new GotoNextErrorHandler(true).invoke(getProject(), getEditor(), getFile());
980       fixes = LightQuickFixTestCase.getAvailableActions(getEditor(), getFile());
981       fix = assertContainsOneOf(fixes, DeleteCatchFix.class);
982       assertEquals("Delete catch for 'java.io.IOException'", fix.getText());
983
984       final IntentionAction finalFix2 = fix;
985       WriteCommandAction.runWriteCommandAction(getProject(), () -> finalFix2.invoke(getProject(), getEditor(), getFile()));
986
987       errors = highlightErrors();
988       assertEmpty(errors);
989     }
990     finally {
991       settings.NEXT_ERROR_ACTION_GOES_TO_ERRORS_FIRST = old;
992     }
993   }
994
995   private static <T> T assertContainsOneOf(@NotNull Collection<T> collection, @NotNull Class<?> aClass) {
996     T result = null;
997     for (T t : collection) {
998       if (aClass.isInstance(t)) {
999         if (result != null) {
1000           fail("multiple " + aClass.getName() + " objects present in collection " + collection);
1001         }
1002         else {
1003           result = t;
1004         }
1005       }
1006     }
1007     assertNotNull(aClass.getName() + " object not found in collection " + collection, result);
1008     return result;
1009   }
1010
1011   
1012   public void testRangeHighlightersDoNotGetStuckForever() throws Throwable {
1013     configureByText(StdFileTypes.JAVA, "class S { void ffffff() {fff<caret>fff();}}");
1014
1015     List<HighlightInfo> infos = highlightErrors();
1016     assertEmpty(infos);
1017     MarkupModel markup = DocumentMarkupModel.forDocument(myEditor.getDocument(), getProject(), true);
1018     final TextRange[] highlightersBefore = getHighlightersTextRange(markup);
1019
1020     type("%%%%");
1021     highlightErrors();
1022     backspace();
1023     backspace();
1024     backspace();
1025     backspace();
1026     infos = highlightErrors();
1027     assertEmpty(infos);
1028
1029     final TextRange[] highlightersAfter = getHighlightersTextRange(markup);
1030
1031     assertEquals(highlightersBefore.length, highlightersAfter.length);
1032     for (int i = 0; i < highlightersBefore.length; i++) {
1033       TextRange before = highlightersBefore[i];
1034       TextRange after = highlightersAfter[i];
1035       assertEquals(before.getStartOffset(), after.getStartOffset());
1036       assertEquals(before.getEndOffset(), after.getEndOffset());
1037     }
1038   }
1039
1040   @NotNull
1041   private static TextRange[] getHighlightersTextRange(@NotNull MarkupModel markup) {
1042     final RangeHighlighter[] highlighters = markup.getAllHighlighters();
1043
1044     final TextRange[] result = new TextRange[highlighters.length];
1045     for (int i = 0; i < highlighters.length; i++) {
1046       result[i] = ProperTextRange.create(highlighters[i]);
1047     }
1048     return orderByHashCode(result); // markup.getAllHighlighters returns unordered array
1049   }
1050
1051   @NotNull
1052   private static <T extends Segment> T[] orderByHashCode(@NotNull T[] highlighters) {
1053     Arrays.sort(highlighters, (o1, o2) -> o2.hashCode() - o1.hashCode());
1054     return highlighters;
1055   }
1056
1057   public void testFileStatusMapDirtyCachingWorks() throws Throwable {
1058     myDaemonCodeAnalyzer.setUpdateByTimerEnabled(false); // to prevent auto-start highlighting
1059     UIUtil.dispatchAllInvocationEvents();
1060     configureByText(StdFileTypes.JAVA, "class <caret>S { int ffffff =  0;}");
1061     UIUtil.dispatchAllInvocationEvents();
1062
1063     final int[] creation = {0};
1064     class Fac extends AbstractProjectComponent implements TextEditorHighlightingPassFactory {
1065       private Fac(Project project) {
1066         super(project);
1067       }
1068
1069       @Override
1070       public TextEditorHighlightingPass createHighlightingPass(@NotNull PsiFile file, @NotNull Editor editor) {
1071         TextRange textRange = FileStatusMap.getDirtyTextRange(editor, Pass.UPDATE_ALL);
1072         if (textRange == null) return null;
1073         return new MyPass(myProject);
1074       }
1075
1076       class MyPass extends TextEditorHighlightingPass {
1077         private MyPass(final Project project) {
1078           super(project, getEditor().getDocument(), false);
1079           creation[0]++;
1080         }
1081
1082         @Override
1083         public void doCollectInformation(@NotNull ProgressIndicator progress) {
1084         }
1085
1086         @Override
1087         public void doApplyInformationToEditor() {
1088         }
1089       }
1090     }
1091     TextEditorHighlightingPassRegistrar registrar = TextEditorHighlightingPassRegistrar.getInstance(getProject());
1092     registrar.registerTextEditorHighlightingPass(new Fac(getProject()), null, null, false, -1);
1093     highlightErrors();
1094     assertEquals(1, creation[0]);
1095
1096     //cached
1097     highlightErrors();
1098     assertEquals(1, creation[0]);
1099     highlightErrors();
1100     assertEquals(1, creation[0]);
1101
1102     type(' ');
1103     highlightErrors();
1104     assertEquals(2, creation[0]);
1105     highlightErrors();
1106     assertEquals(2, creation[0]);
1107     highlightErrors();
1108     assertEquals(2, creation[0]);
1109   }
1110
1111   
1112   public void testDefensivelyDirtyFlagDoesNotClearPrematurely() throws Throwable {
1113     class Fac extends AbstractProjectComponent implements TextEditorHighlightingPassFactory {
1114       private Fac(Project project) {
1115         super(project);
1116       }
1117
1118       @Override
1119       public TextEditorHighlightingPass createHighlightingPass(@NotNull PsiFile file, @NotNull Editor editor) {
1120         return null;
1121       }
1122     }
1123     TextEditorHighlightingPassRegistrar registrar = TextEditorHighlightingPassRegistrar.getInstance(getProject());
1124     registrar.registerTextEditorHighlightingPass(new Fac(getProject()), null, null, false, -1);
1125
1126     configureByText(StdFileTypes.JAVA, "@Deprecated<caret> class S { } ");
1127
1128     List<HighlightInfo> infos = doHighlighting(HighlightInfoType.SYMBOL_TYPE_SEVERITY);
1129     assertEquals(2, infos.size());
1130
1131     assertEquals("@Deprecated", infos.get(0).getText());
1132     assertEquals("S", infos.get(1).getText());
1133
1134     backspace();
1135     type('d');
1136
1137     List<HighlightInfo> after = doHighlighting(HighlightInfoType.SYMBOL_TYPE_SEVERITY);
1138
1139     assertEquals("@Deprecated", after.get(0).getText());
1140     assertEquals("S", after.get(1).getText());
1141
1142     backspace();
1143     type('d');
1144
1145     getEditor().getCaretModel().moveToOffset(getEditor().getDocument().getTextLength());
1146     type(" ");
1147
1148     after = doHighlighting(HighlightInfoType.SYMBOL_TYPE_SEVERITY);
1149     assertEquals(2, after.size());
1150
1151     assertEquals("@Deprecated", after.get(0).getText());
1152     assertEquals("S", after.get(1).getText());
1153   }
1154
1155   
1156   public void testModificationInsideCodeblockDoesnotAffectErrorMarkersOutside() throws Exception {
1157     configureByFile(BASE_PATH + "ErrorMark.java");
1158     List<HighlightInfo> errs = highlightErrors();
1159     assertEquals(1, errs.size());
1160     assertEquals("'}' expected", errs.get(0).getDescription());
1161
1162     type("//comment");
1163     errs = highlightErrors();
1164     assertEquals(1, errs.size());
1165     assertEquals("'}' expected", errs.get(0).getDescription());
1166   }
1167
1168   public void testErrorMarkerAtTheEndOfTheFile() throws Exception {
1169     CommandProcessor.getInstance().executeCommand(getProject(), () -> {
1170       try {
1171         configureByFile(BASE_PATH + "ErrorMarkAtEnd.java");
1172       }
1173       catch (Exception e) {
1174         LOG.error(e);
1175       }
1176     }, "cc", this);
1177     List<HighlightInfo> errs = highlightErrors();
1178     assertEmpty(errs);
1179     CommandProcessor.getInstance().executeCommand(getProject(), () -> {
1180       Document document = getEditor().getDocument();
1181       int offset = getEditor().getCaretModel().getOffset();
1182       while (offset < document.getTextLength()) {
1183         int i = StringUtil.indexOf(document.getText(), '}', offset, document.getTextLength());
1184         if (i == -1) break;
1185         getEditor().getCaretModel().moveToOffset(i);
1186         delete(getEditor());
1187       }
1188     }, "my", this);
1189
1190     errs = highlightErrors();
1191     assertEquals(2, errs.size());
1192     assertEquals("'}' expected", errs.get(0).getDescription());
1193
1194     undo();
1195     errs = highlightErrors();
1196     assertEmpty(errs);
1197   }
1198
1199
1200   // disabled for now
1201   public void _testSOEInEndlessAppendChainPerformance() throws Throwable {
1202     StringBuilder text = new StringBuilder("class S { String ffffff =  new StringBuilder()\n");
1203     for (int i=0; i<2000; i++) {
1204       text.append(".append(").append(i).append(")\n");
1205     }
1206     text.append(".toString();<caret>}");
1207     configureByText(StdFileTypes.JAVA, text.toString());
1208
1209     PlatformTestUtil.startPerformanceTest("too many tree visitors", 30000, () -> {
1210       List<HighlightInfo> infos = highlightErrors();
1211       assertEmpty(infos);
1212       type("kjhgas");
1213       List<HighlightInfo> errors = highlightErrors();
1214       assertFalse(errors.isEmpty());
1215       backspace();
1216       backspace();
1217       backspace();
1218       backspace();
1219       backspace();
1220       backspace();
1221       infos = highlightErrors();
1222       assertEmpty(infos);
1223     }).useLegacyScaling().assertTiming();
1224   }
1225
1226   
1227   public void testBulbAppearsAfterType() throws Throwable {
1228     String text = "class S { ArrayList<caret>XXX x;}";
1229     configureByText(StdFileTypes.JAVA, text);
1230
1231     ((EditorImpl)myEditor).getScrollPane().getViewport().setSize(1000, 1000);
1232     DaemonCodeAnalyzerSettings.getInstance().setImportHintEnabled(true);
1233
1234     final Set<LightweightHint> shown = ContainerUtil.newIdentityTroveSet();
1235     getProject().getMessageBus().connect().subscribe(EditorHintListener.TOPIC, (project, hint, flags) -> {
1236       shown.add(hint);
1237       hint.addHintListener(event -> shown.remove(hint));
1238     });
1239
1240     DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
1241     highlightErrors();
1242
1243     IntentionHintComponent hintComponent = codeAnalyzer.getLastIntentionHint();
1244     assertNotNull(hintComponent);
1245     assertFalse(hintComponent.isDisposed());
1246     assertNotNull(hintComponent.getComponentHint());
1247     assertTrue(shown.contains(hintComponent.getComponentHint()));
1248
1249     type("x");
1250     highlightErrors();
1251     hintComponent = codeAnalyzer.getLastIntentionHint();
1252     assertNotNull(hintComponent);
1253     assertFalse(hintComponent.isDisposed());
1254     assertNotNull(hintComponent.getComponentHint());
1255     assertTrue(shown.contains(hintComponent.getComponentHint()));
1256   }
1257
1258   @Override
1259   protected void configureByExistingFile(@NotNull VirtualFile virtualFile) {
1260     super.configureByExistingFile(virtualFile);
1261     EditorTracker editorTracker = getProject().getComponent(EditorTracker.class);
1262     editorTracker.setActiveEditors(Collections.singletonList(getEditor()));
1263   }
1264
1265   @Override
1266   protected VirtualFile configureByFiles(@Nullable File rawProjectRoot, @NotNull VirtualFile... vFiles) throws IOException {
1267     VirtualFile file = super.configureByFiles(rawProjectRoot, vFiles);
1268     EditorTracker editorTracker = getProject().getComponent(EditorTracker.class);
1269     editorTracker.setActiveEditors(Collections.singletonList(getEditor()));
1270     return file;
1271   }
1272
1273   public void testDaemonIgnoresFrameDeactivation() throws Throwable {
1274     DaemonCodeAnalyzerSettings.getInstance().setImportHintEnabled(true); // return default value to avoid unnecessary save
1275     InspectionProfileManager.getInstance().setRootProfile(InspectionProfileImpl.getDefaultProfile().getName()); // reset to default profile from the custom one to avoid unnecessary save
1276
1277     String text = "class S { ArrayList<caret>XXX x;}";
1278     configureByText(StdFileTypes.JAVA, text);
1279     highlightErrors();
1280
1281     GeneralSettings settings = GeneralSettings.getInstance();
1282     ApplicationEx application = ApplicationManagerEx.getApplicationEx();
1283     boolean frameSave = settings.isSaveOnFrameDeactivation();
1284     boolean appSave = application.isDoNotSave();
1285
1286     settings.setSaveOnFrameDeactivation(true);
1287     application.doNotSave(false);
1288     try {
1289       SaveAndSyncHandlerImpl.doSaveDocumentsAndProjectsAndApp();
1290
1291       checkDaemonReaction(false, SaveAndSyncHandlerImpl::doSaveDocumentsAndProjectsAndApp);
1292     }
1293     finally {
1294       application.doNotSave(appSave);
1295       settings.setSaveOnFrameDeactivation(frameSave);
1296     }
1297   }
1298
1299
1300   public void testApplyLocalQuickFix() throws Throwable {
1301     configureByText(StdFileTypes.JAVA, "class X { static int sss; public int f() { return this.<caret>sss; }}");
1302
1303     ((EditorImpl)myEditor).getScrollPane().getViewport().setSize(1000, 1000);
1304     DaemonCodeAnalyzerSettings.getInstance().setImportHintEnabled(true);
1305
1306     List<HighlightInfo> warns = doHighlighting(HighlightSeverity.WARNING);
1307     assertOneElement(warns);
1308     List<HighlightInfo.IntentionActionDescriptor> actions = ShowIntentionsPass.getAvailableActions(getEditor(), getFile(), -1);
1309     final HighlightInfo.IntentionActionDescriptor descriptor = assertOneElement(actions);
1310     WriteCommandAction.runWriteCommandAction(getProject(), () -> descriptor.getAction().invoke(getProject(), getEditor(), getFile()));
1311
1312     highlightErrors();
1313     actions = ShowIntentionsPass.getAvailableActions(getEditor(), getFile(), -1);
1314     assertEmpty(actions);
1315   }
1316
1317   
1318   public void testApplyErrorInTheMiddle() throws Throwable {
1319     String text = "class <caret>X { ";
1320     for (int i = 0; i < 100; i++) {
1321       text += "\n    {\n" +
1322               "//    String x = \"<zzzzzzzzzz/>\";\n" +
1323               "    }";
1324     }
1325     text += "\n}";
1326     configureByText(StdFileTypes.JAVA, text);
1327
1328     ((EditorImpl)myEditor).getScrollPane().getViewport().setSize(1000, 1000);
1329     DaemonCodeAnalyzerSettings.getInstance().setImportHintEnabled(true);
1330
1331     List<HighlightInfo> warns = highlightErrors();
1332     assertEmpty(warns);
1333
1334     type("//");
1335     List<HighlightInfo> errors = highlightErrors();
1336     assertEquals(2, errors.size());
1337
1338     backspace();
1339     backspace();
1340
1341     errors = highlightErrors();
1342     assertEmpty(errors);
1343   }
1344
1345   
1346   public void testErrorInTheEndOutsideVisibleArea() throws Throwable {
1347     String text = "<xml> \n" + StringUtil.repeatSymbol('\n', 1000) + "</xml>\nxxxxx<caret>";
1348     configureByText(StdFileTypes.XML, text);
1349
1350     ProperTextRange visibleRange = makeEditorWindowVisible(new Point(0, 1000));
1351     assertTrue(visibleRange.getStartOffset() > 0);
1352
1353     List<HighlightInfo> warns = highlightErrors();
1354     HighlightInfo info = assertOneElement(warns);
1355     assertEquals("Top level element is not completed", info.getDescription());
1356
1357     type("xxx");
1358     List<HighlightInfo> errors = highlightErrors();
1359     info = assertOneElement(errors);
1360     assertEquals("Top level element is not completed", info.getDescription());
1361   }
1362
1363   private ProperTextRange makeEditorWindowVisible(Point viewPosition) {
1364     ((EditorImpl)myEditor).getScrollPane().getViewport().setSize(1000, 1000);
1365     DaemonCodeAnalyzerSettings.getInstance().setImportHintEnabled(true);
1366
1367     myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
1368     ((EditorImpl)myEditor).getScrollPane().getViewport().setViewPosition(viewPosition);
1369     ((EditorImpl)myEditor).getScrollPane().getViewport().setExtentSize(new Dimension(100, ((EditorImpl)myEditor).getPreferredHeight() - 
1370                                                                                           viewPosition.y));
1371     return VisibleHighlightingPassFactory.calculateVisibleRange(getEditor());
1372   }
1373
1374
1375   public void testEnterInCodeBlock() throws Throwable {
1376     String text = "class LQF {\n" +
1377                   "    int wwwwwwwwwwww;\n" +
1378                   "    public void main() {<caret>\n" +
1379                   "        wwwwwwwwwwww = 1;\n" +
1380                   "    }\n" +
1381                   "}";
1382     configureByText(StdFileTypes.JAVA, text);
1383
1384     ((EditorImpl)myEditor).getScrollPane().getViewport().setSize(1000, 1000);
1385
1386     List<HighlightInfo> infos = doHighlighting(HighlightInfoType.SYMBOL_TYPE_SEVERITY);
1387     assertEquals(4, infos.size());
1388
1389     type('\n');
1390     infos = doHighlighting(HighlightInfoType.SYMBOL_TYPE_SEVERITY);
1391     assertEquals(4, infos.size());
1392
1393     deleteLine();
1394
1395     infos = doHighlighting(HighlightInfoType.SYMBOL_TYPE_SEVERITY);
1396     assertEquals(4, infos.size());
1397   }
1398
1399   
1400   public void testTypingNearEmptyErrorElement() throws Throwable {
1401     String text = "class LQF {\n" +
1402                   "    public void main() {\n" +
1403                   "        int wwwwwwwwwwww = 1<caret>\n" +
1404                   "    }\n" +
1405                   "}";
1406     configureByText(StdFileTypes.JAVA, text);
1407
1408     ((EditorImpl)myEditor).getScrollPane().getViewport().setSize(1000, 1000);
1409
1410     List<HighlightInfo> infos = highlightErrors();
1411     assertEquals(1, infos.size());
1412
1413     type(';');
1414     infos = highlightErrors();
1415     assertEmpty(infos);
1416   }
1417
1418   
1419   public void testLIPGetAllParentsAfterCodeBlockModification() throws Throwable {
1420     String text = "class LQF {\n" +
1421                   "    int f;\n" +
1422                   "    public void me() {\n" +
1423                   "        <caret>\n" +
1424                   "    }\n" +
1425                   "}";
1426     configureByText(StdFileTypes.JAVA, text);
1427
1428     final List<PsiElement> visitedElements = Collections.synchronizedList(new ArrayList<>());
1429     class MyVisitor extends PsiElementVisitor {
1430       @Override
1431       public void visitElement(PsiElement element) {
1432         visitedElements.add(element);
1433       }
1434     }
1435     final MyVisitor visitor = new MyVisitor();
1436     final LocalInspectionTool tool = new LocalInspectionTool() {
1437       @Nls
1438       @NotNull
1439       @Override
1440       public String getGroupDisplayName() {
1441         return "fegna";
1442       }
1443
1444       @Nls
1445       @NotNull
1446       @Override
1447       public String getDisplayName() {
1448         return getGroupDisplayName();
1449       }
1450
1451       @NotNull
1452       @Override
1453       public String getShortName() {
1454         return getGroupDisplayName();
1455       }
1456
1457       @NotNull
1458       @Override
1459       public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
1460         return visitor;
1461       }
1462     };
1463     disposeOnTearDown(() -> disableInspectionTool(tool.getShortName()));
1464     enableInspectionTool(tool);
1465
1466     List<HighlightInfo> infos = highlightErrors();
1467     assertEmpty(infos);
1468
1469     List<PsiElement> allPsi = CollectHighlightsUtil.getElementsInRange(myFile, 0, myFile.getTextLength());
1470     assertEquals(new HashSet<>(allPsi), new HashSet<>(visitedElements));
1471
1472     // inside code block modification
1473     visitedElements.clear();
1474     type("//");
1475
1476     infos = highlightErrors();
1477     assertEmpty(infos);
1478
1479
1480     PsiMethod method = ((PsiJavaFile)myFile).getClasses()[0].getMethods()[0];
1481     List<PsiElement> methodAndParents =
1482       CollectHighlightsUtil.getElementsInRange(myFile, method.getTextRange().getStartOffset(), method.getTextRange().getEndOffset(), true);
1483     assertEquals(new HashSet<>(methodAndParents), new HashSet<>(visitedElements));
1484   }
1485
1486   
1487   public void testCancelsItSelfOnTypingInAlienProject() throws Throwable {
1488     String body = StringUtil.repeat("\"String field = null;\"\n", 1000);
1489     configureByText(StdFileTypes.JAVA, "class X{ void f() {" + body + "<caret>\n} }");
1490
1491     File temp = createTempDirectory();
1492     final Project alienProject = createProject(temp + "/alien.ipr", DebugUtil.currentStackTrace());
1493     boolean succ2 = ProjectManagerEx.getInstanceEx().openProject(alienProject);
1494     assertTrue(succ2);
1495     DaemonProgressIndicator.setDebug(true);
1496     final DaemonProgressIndicator[] indicator = new DaemonProgressIndicator[1];
1497
1498     try {
1499       Module alienModule = doCreateRealModuleIn("x", alienProject, getModuleType());
1500       final VirtualFile alienRoot = PsiTestUtil.createTestProjectStructure(alienProject, alienModule, myFilesToDelete);
1501       OpenFileDescriptor alienDescriptor = new WriteAction<OpenFileDescriptor>() {
1502         @Override
1503         protected void run(@NotNull Result<OpenFileDescriptor> result) throws Throwable {
1504           VirtualFile alienFile = alienRoot.createChildData(this, "X.java");
1505           setFileText(alienFile, "class Alien { }");
1506           OpenFileDescriptor alienDescriptor = new OpenFileDescriptor(alienProject, alienFile);
1507           result.setResult(alienDescriptor);
1508         }
1509       }.execute().throwException().getResultObject();
1510
1511       FileEditorManager fe = FileEditorManager.getInstance(alienProject);
1512       final Editor alienEditor = fe.openTextEditor(alienDescriptor, false);
1513       ((EditorImpl)alienEditor).setCaretActive();
1514       PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
1515       PsiDocumentManager.getInstance(alienProject).commitAllDocuments();
1516
1517       // start daemon in main project. should check for its cancel when typing in alien
1518       TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(getEditor());
1519       DaemonCodeAnalyzerImpl di = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
1520       final boolean[] checked = {false};
1521       di.runPasses(getFile(), getEditor().getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, true, () -> {
1522         if (checked[0]) return;
1523         checked[0] = true;
1524         indicator[0] = myDaemonCodeAnalyzer.getUpdateProgress();
1525         typeInAlienEditor(alienEditor, 'x');
1526       });
1527     }
1528     catch (ProcessCanceledException ignored) {
1529       //DaemonProgressIndicator.setDebug(true);
1530       //System.out.println("indicator = " + indicator[0]);
1531       return;
1532     }
1533     finally {
1534       ProjectManagerEx.getInstanceEx().closeAndDispose(alienProject);
1535     }
1536     fail("must throw PCE");
1537   }
1538
1539   public void testPasteInAnonymousCodeBlock() throws Throwable {
1540     configureByText(StdFileTypes.JAVA, "class X{ void f() {" +
1541                                        "     int x=0;\n" +
1542                                        "    Runnable r = new Runnable() { public void run() {\n" +
1543                                        " <caret>\n" +
1544                                        "    }};\n" +
1545                                        "    <selection>int y = x;</selection>\n " +
1546                                        "\n} }");
1547     assertEmpty(highlightErrors());
1548     copy();
1549     assertEquals("int y = x;", getEditor().getSelectionModel().getSelectedText());
1550     getEditor().getSelectionModel().removeSelection();
1551     paste();
1552     List<HighlightInfo> errors = highlightErrors();
1553     assertEquals(1, errors.size());
1554   }
1555
1556   private void paste() {
1557     EditorActionManager actionManager = EditorActionManager.getInstance();
1558     final EditorActionHandler actionHandler = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
1559     WriteCommandAction.runWriteCommandAction(null, () -> actionHandler.execute(getEditor(), null, DataManager.getInstance().getDataContext()));
1560   }
1561
1562   private void copy() {
1563     EditorActionManager actionManager = EditorActionManager.getInstance();
1564     final EditorActionHandler actionHandler = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_COPY);
1565     WriteCommandAction.runWriteCommandAction(null, () -> actionHandler.execute(getEditor(), null, DataManager.getInstance().getDataContext()));
1566   }
1567
1568   public void testReactivityPerformance() throws Throwable {
1569     @NonNls String filePath = "/psi/resolve/Thinlet.java";
1570     configureByFile(filePath);
1571     type(' ');
1572     CompletionContributor.forLanguage(getFile().getLanguage());
1573     long s = System.currentTimeMillis();
1574     highlightErrors();
1575     long e = System.currentTimeMillis();
1576     System.out.println("Hi elapsed: "+(e-s));
1577
1578     final DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
1579     int N = Math.max(5, Timings.adjustAccordingToMySpeed(80, true));
1580     System.out.println("N = " + N);
1581     final long[] interruptTimes = new long[N];
1582     List<Thread> watchers = new ArrayList<>();
1583     for (int i = 0; i < N; i++) {
1584       codeAnalyzer.restart();
1585       final int finalI = i;
1586       final long start = System.currentTimeMillis();
1587       Runnable interrupt = () -> {
1588         long now = System.currentTimeMillis();
1589         if (now - start < 100) {
1590           // wait to engage all highlighting threads
1591           return;
1592         }
1593         final AtomicLong typingStart = new AtomicLong();
1594         final DaemonProgressIndicator progress = codeAnalyzer.getUpdateProgress();
1595         Thread watcher = new Thread("reactivity watcher") {
1596           @Override
1597           public void run() {
1598             while (true) {
1599               final long start1 = typingStart.get();
1600               if (start1 == -1) break;
1601               if (start1 == 0) {
1602                 try {
1603                   Thread.sleep(5);
1604                 }
1605                 catch (InterruptedException e1) {
1606                   throw new RuntimeException(e1);
1607                 }
1608                 continue;
1609               }
1610               long now = System.currentTimeMillis();
1611               if (now - start1 > 500) {
1612                 // too long, see WTF
1613                 PerformanceWatcher.dumpThreadsToConsole("Too long interrupt: " +
1614                                                         (now - start1) +
1615                                                         "; Progress canceled=" +
1616                                                         progress.isCanceled() +
1617                                                         "\n----------------------------");
1618                 System.err.println("----all threads---");
1619                 for (Thread thread : Thread.getAllStackTraces().keySet()) {
1620                   boolean canceled = CoreProgressManager.isCanceledThread(thread);
1621                   if (canceled) {
1622                     System.err.println("Thread " + thread + " is canceled");
1623                   }
1624                 }
1625                 System.err.println("----///////---");
1626                 break;
1627               }
1628             }
1629           }
1630         };
1631         watcher.start();
1632         watchers.add(watcher);
1633         typingStart.set(System.currentTimeMillis());
1634         type(' ');
1635         typingStart.set(-1);
1636         long end = System.currentTimeMillis();
1637         long interruptTime = end - now;
1638         interruptTimes[finalI] = interruptTime;
1639         assertNull(codeAnalyzer.getUpdateProgress());
1640         System.out.println(interruptTime);
1641         throw new ProcessCanceledException();
1642       };
1643       try {
1644         PsiFile file = getFile();
1645         Editor editor = getEditor();
1646         Project project = file.getProject();
1647         CodeInsightTestFixtureImpl.ensureIndexesUpToDate(project);
1648         TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor);
1649         PsiDocumentManager.getInstance(myProject).commitAllDocuments();
1650         codeAnalyzer.runPasses(file, editor.getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, false, interrupt);
1651         DaemonProgressIndicator progress = codeAnalyzer.getUpdateProgress();
1652         throw new RuntimeException("should have been interrupted: "+progress);
1653       }
1654       catch (ProcessCanceledException ignored) {
1655       }
1656     }
1657
1658     long ave = ArrayUtil.averageAmongMedians(interruptTimes, 3);
1659     System.out.println("Average among the N/3 median times: " + ave + "ms");
1660     assertTrue(ave < 300);
1661     for (Thread watcher : watchers) {
1662       watcher.join();
1663     }
1664   }
1665
1666   public void testTypingLatencyPerformance() throws Throwable {
1667     @NonNls String filePath = "/psi/resolve/ThinletBig.java";
1668
1669     configureByFile(filePath);
1670
1671     type(' ');
1672     CompletionContributor.forLanguage(getFile().getLanguage());
1673     long s = System.currentTimeMillis();
1674     highlightErrors();
1675     long e = System.currentTimeMillis();
1676     //System.out.println("Hi elapsed: "+(e-s));
1677
1678     final DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
1679     int N = Math.max(5, Timings.adjustAccordingToMySpeed(80, true));
1680     //System.out.println("N = " + N);
1681     final long[] interruptTimes = new long[N];
1682     for (int i = 0; i < N; i++) {
1683       codeAnalyzer.restart();
1684       final int finalI = i;
1685       final long start = System.currentTimeMillis();
1686       Runnable interrupt = () -> {
1687         long now = System.currentTimeMillis();
1688         if (now - start < 100) {
1689           // wait to engage all highlighting threads
1690           return;
1691         }
1692         type(' ');
1693         long end = System.currentTimeMillis();
1694         long interruptTime = end - now;
1695         interruptTimes[finalI] = interruptTime;
1696         assertNull(codeAnalyzer.getUpdateProgress());
1697         //System.out.println(interruptTime);
1698         throw new ProcessCanceledException();
1699       };
1700       try {
1701         PsiFile file = getFile();
1702         Editor editor = getEditor();
1703         Project project = file.getProject();
1704         CodeInsightTestFixtureImpl.ensureIndexesUpToDate(project);
1705         TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor);
1706         PsiDocumentManager.getInstance(myProject).commitAllDocuments();
1707         codeAnalyzer.runPasses(file, editor.getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, false, interrupt);
1708
1709         throw new RuntimeException("should have been interrupted");
1710       }
1711       catch (ProcessCanceledException ignored) {
1712       }
1713       backspace();
1714       //highlightErrors();
1715     }
1716
1717     long mean = ArrayUtil.averageAmongMedians(interruptTimes, 3);
1718     long avg = Arrays.stream(interruptTimes).sum() / interruptTimes.length;
1719     long max = Arrays.stream(interruptTimes).max().getAsLong();
1720     long min = Arrays.stream(interruptTimes).min().getAsLong();
1721     System.out.println("Average among the N/3 median times: " + mean + "ms; max: "+max+"; min:"+min+"; avg: "+avg);
1722     assertTrue(mean < 10);
1723   }
1724
1725   private static void startCPUProfiling() {
1726     try {
1727       Class<?> aClass = Class.forName("com.intellij.util.ProfilingUtil");
1728       Method method = ReflectionUtil.getDeclaredMethod(aClass, "startCPUProfiling");
1729       method.invoke(null);
1730     }
1731     catch (Exception e) {
1732       throw new RuntimeException(e);
1733     }
1734   }
1735   private static void stopCPUProfiling() {
1736     try {
1737       Class<?> aClass = Class.forName("com.intellij.util.ProfilingUtil");
1738       Method method = ReflectionUtil.getDeclaredMethod(aClass, "stopCPUProfiling");
1739       method.invoke(null);
1740     }
1741     catch (Exception e) {
1742       throw new RuntimeException(e);
1743     }
1744   }
1745
1746   private static String captureCPUSnapshot() {
1747     try {
1748       Class<?> aClass = Class.forName("com.intellij.util.ProfilingUtil");
1749       Method method = ReflectionUtil.getDeclaredMethod(aClass, "captureCPUSnapshot");
1750       return (String)method.invoke(null);
1751     }
1752     catch (Exception e) {
1753       throw new RuntimeException(e);
1754     }
1755   }
1756
1757   public void testPostHighlightingPassRunsOnEveryPsiModification() throws Exception {
1758     PsiFile x = createFile("X.java", "public class X { public static void ffffffffffffff(){} }");
1759     PsiFile use = createFile("Use.java", "public class Use { { <caret>X.ffffffffffffff(); } }");
1760     configureByExistingFile(use.getVirtualFile());
1761
1762     InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getCurrentProfile();
1763     HighlightDisplayKey myDeadCodeKey = HighlightDisplayKey.find(UnusedDeclarationInspectionBase.SHORT_NAME);
1764     if (myDeadCodeKey == null) {
1765       myDeadCodeKey = HighlightDisplayKey.register(UnusedDeclarationInspectionBase.SHORT_NAME, UnusedDeclarationInspectionBase.DISPLAY_NAME);
1766     }
1767     UnusedDeclarationInspectionBase myDeadCodeInspection = new UnusedDeclarationInspectionBase(true);
1768     enableInspectionTool(myDeadCodeInspection);
1769     assert profile.isToolEnabled(myDeadCodeKey, myFile);
1770
1771     Editor xEditor = createEditor(x.getVirtualFile());
1772     List<HighlightInfo> xInfos = filter(CodeInsightTestFixtureImpl.instantiateAndRun(x, xEditor, new int[0], false),
1773                                         HighlightSeverity.WARNING);
1774     HighlightInfo info = ContainerUtil.find(xInfos, xInfo -> xInfo.getDescription().equals("Method 'ffffffffffffff()' is never used"));
1775     assertNull(xInfos.toString(), info);
1776
1777     Editor useEditor = myEditor;
1778     List<HighlightInfo> useInfos = filter(CodeInsightTestFixtureImpl.instantiateAndRun(use, useEditor, new int[0], false), HighlightSeverity.ERROR);
1779     assertEmpty(useInfos);
1780
1781     type('/');
1782     type('/');
1783
1784     PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
1785     xInfos = filter(CodeInsightTestFixtureImpl.instantiateAndRun(x, xEditor, new int[0], false), HighlightSeverity.WARNING);
1786     info = ContainerUtil.find(xInfos, xInfo -> xInfo.getDescription().equals("Method 'ffffffffffffff()' is never used"));
1787     assertNotNull(xInfos.toString(), info);
1788   }
1789
1790
1791   public void testErrorDisappearsRightAfterTypingInsideVisibleAreaWhileDaemonContinuesToChugAlong() throws Throwable {
1792     String text = "class X{\nint xxx;\n{\nint i = <selection>null</selection><caret>;\n" + StringUtil.repeat("{ this.hashCode(); }\n\n\n", 10000) + "}}";
1793     configureByText(StdFileTypes.JAVA, text);
1794
1795     ((EditorImpl)myEditor).getScrollPane().getViewport().setSize(100, 100);
1796     DaemonCodeAnalyzerSettings.getInstance().setImportHintEnabled(true);
1797
1798     myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
1799     ((EditorImpl)myEditor).getScrollPane().getViewport().setViewPosition(new Point(0, 0));
1800     ((EditorImpl)myEditor).getScrollPane().getViewport().setExtentSize(new Dimension(100, 100000));
1801     ProperTextRange visibleRange = VisibleHighlightingPassFactory.calculateVisibleRange(getEditor());
1802     assertTrue(visibleRange.getLength() > 0);
1803     final Document document = myEditor.getDocument();
1804     assertTrue(visibleRange.getLength() < document.getTextLength());
1805
1806     List<HighlightInfo> err1 = highlightErrors();
1807     HighlightInfo info = assertOneElement(err1);
1808     final String errorDescription = "Incompatible types. Found: 'null', required: 'int'";
1809     assertEquals(errorDescription, info.getDescription());
1810
1811     MarkupModelEx model = (MarkupModelEx)DocumentMarkupModel.forDocument(document, myProject, false);
1812     final boolean[] errorRemoved = {false};
1813
1814     model.addMarkupModelListener(getTestRootDisposable(), new MarkupModelListener.Adapter() {
1815       @Override
1816       public void beforeRemoved(@NotNull RangeHighlighterEx highlighter) {
1817         Object tt = highlighter.getErrorStripeTooltip();
1818         if (!(tt instanceof HighlightInfo)) return;
1819         String description = ((HighlightInfo)tt).getDescription();
1820         if (errorDescription.equals(description)) {
1821           errorRemoved[0] = true;
1822
1823           List<TextEditorHighlightingPass> passes = myDaemonCodeAnalyzer.getPassesToShowProgressFor(document);
1824           GeneralHighlightingPass ghp = null;
1825           for (TextEditorHighlightingPass pass : passes) {
1826             if (pass instanceof GeneralHighlightingPass && pass.getId() == Pass.UPDATE_ALL) {
1827               assert ghp == null : ghp;
1828               ghp = (GeneralHighlightingPass)pass;
1829             }
1830           }
1831           assertNotNull(ghp);
1832           boolean finished = ghp.isFinished();
1833           assertFalse(finished);
1834         }
1835       }
1836     });
1837     type("1");
1838
1839     List<HighlightInfo> errors = highlightErrors();
1840     assertEmpty(errors);
1841     assertTrue(errorRemoved[0]);
1842   }
1843
1844   public void testDaemonWorksForDefaultProjectSinceItIsNeededInSettingsDialogForSomeReason() {
1845     assertNotNull(DaemonCodeAnalyzer.getInstance(ProjectManager.getInstance().getDefaultProject()));
1846   }
1847
1848   public void testChangeEventsAreNotAlwaysGeneric() throws Exception {
1849     String body = "class X {\n" +
1850                   "<caret>    @org.PPP\n" +
1851                   "    void dtoArrayDouble() {\n" +
1852                   "    }\n" +
1853                   "}";
1854     configureByText(JavaFileType.INSTANCE, body);
1855     makeEditorWindowVisible(new Point());
1856
1857     List<HighlightInfo> errors = highlightErrors();
1858     assertFalse(errors.isEmpty());
1859
1860     type("//");
1861     errors = highlightErrors();
1862     assertEmpty(errors);
1863
1864     backspace();
1865     backspace();
1866     errors = highlightErrors();
1867     assertFalse(errors.isEmpty());
1868   }
1869
1870   public void testInterruptOnTyping() throws Throwable {
1871     @NonNls String filePath = "/psi/resolve/Thinlet.java";
1872     configureByFile(filePath);
1873     highlightErrors();
1874
1875     final DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
1876     codeAnalyzer.restart();
1877     Runnable interrupt = () -> type(' ');
1878     try {
1879       PsiDocumentManager.getInstance(myProject).commitAllDocuments();
1880
1881       PsiFile file = getFile();
1882       Editor editor = getEditor();
1883       Project project = file.getProject();
1884       CodeInsightTestFixtureImpl.ensureIndexesUpToDate(project);
1885       TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor);
1886       codeAnalyzer.runPasses(file, editor.getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, true, interrupt);
1887     }
1888     catch (ProcessCanceledException ignored) {
1889       return;
1890     }
1891     fail("PCE must have been thrown");
1892   }
1893
1894   public void testAllPassesFinishAfterInterruptOnTyping_Performance() throws Throwable {
1895     @NonNls String filePath = "/psi/resolve/Thinlet.java";
1896     configureByFile(filePath);
1897     highlightErrors();
1898
1899     final DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
1900     Runnable interrupt = () -> type(' ');
1901     type(' ');
1902     for (int i=0; i<100; i++) {
1903       backspace();
1904       codeAnalyzer.restart();
1905       try {
1906         PsiDocumentManager.getInstance(myProject).commitAllDocuments();
1907
1908         PsiFile file = getFile();
1909         Editor editor = getEditor();
1910         Project project = file.getProject();
1911         CodeInsightTestFixtureImpl.ensureIndexesUpToDate(project);
1912         TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor);
1913         codeAnalyzer.runPasses(file, editor.getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, true, interrupt);
1914       }
1915       catch (ProcessCanceledException ignored) {
1916         codeAnalyzer.waitForTermination();
1917         continue;
1918       }
1919       fail("PCE must have been thrown");
1920     }
1921   }
1922
1923   public void testCodeFoldingInSplittedWindowAppliesToAllEditors() throws Exception {
1924     final Set<Editor> applied = new THashSet<>();
1925     final Set<Editor> collected = new THashSet<>();
1926     registerFakePass(applied, collected);
1927
1928     configureByText(PlainTextFileType.INSTANCE, "");
1929     Editor editor1 = getEditor();
1930     final Editor editor2 = EditorFactory.getInstance().createEditor(editor1.getDocument(),getProject());
1931     Disposer.register(getProject(), () -> EditorFactory.getInstance().releaseEditor(editor2));
1932     TextEditor textEditor1 = new PsiAwareTextEditorProvider().getTextEditor(editor1);
1933     TextEditor textEditor2 = new PsiAwareTextEditorProvider().getTextEditor(editor2);
1934
1935     List<HighlightInfo> errors = myDaemonCodeAnalyzer.runPasses(myFile, editor1.getDocument(), Arrays.asList(textEditor1,textEditor2), new int[0], false, null);
1936     assertEmpty(errors);
1937
1938     assertEquals(collected, ContainerUtil.newHashSet(editor1, editor2));
1939     assertEquals(applied, ContainerUtil.newHashSet(editor1, editor2));
1940   }
1941
1942   private void registerFakePass(@NotNull final Set<Editor> applied, @NotNull final Set<Editor> collected) {
1943     class Fac extends AbstractProjectComponent implements TextEditorHighlightingPassFactory {
1944       private Fac(Project project) {
1945         super(project);
1946       }
1947
1948       @Override
1949       public TextEditorHighlightingPass createHighlightingPass(@NotNull PsiFile file, @NotNull final Editor editor) {
1950         return new EditorBoundHighlightingPass(editor, file, false) {
1951           @Override
1952           public void doCollectInformation(@NotNull ProgressIndicator progress) {
1953             collected.add(editor);
1954           }
1955
1956           @Override
1957           public void doApplyInformationToEditor() {
1958             applied.add(editor);
1959           }
1960         };
1961       }
1962     }
1963     TextEditorHighlightingPassRegistrar registrar = TextEditorHighlightingPassRegistrar.getInstance(getProject());
1964     registrar.registerTextEditorHighlightingPass(new Fac(getProject()), null, null, false, -1);
1965   }
1966
1967   private volatile boolean runHeavyProcessing;
1968   public void testDaemonDisablesItselfDuringHeavyProcessing() throws Exception {
1969     runHeavyProcessing = false;
1970     DaemonCodeAnalyzerSettings settings = DaemonCodeAnalyzerSettings.getInstance();
1971     int delay = settings.AUTOREPARSE_DELAY;
1972     settings.AUTOREPARSE_DELAY = 0;
1973
1974     final Set<Editor> applied = Collections.synchronizedSet(new THashSet<>());
1975     final Set<Editor> collected = Collections.synchronizedSet(new THashSet<>());
1976     registerFakePass(applied, collected);
1977
1978     configureByText(PlainTextFileType.INSTANCE, "");
1979     Editor editor = getEditor();
1980     EditorTracker editorTracker = getProject().getComponent(EditorTracker.class);
1981     editorTracker.setActiveEditors(Collections.singletonList(editor));
1982     while (HeavyProcessLatch.INSTANCE.isRunning()) {
1983       UIUtil.dispatchAllInvocationEvents();
1984     }
1985     type("xxx"); // restart daemon
1986     assertTrue(editorTracker.getActiveEditors().contains(editor));
1987     assertSame(editor, FileEditorManager.getInstance(myProject).getSelectedTextEditor());
1988
1989     try {
1990       // wait for first pass to complete
1991       long start = System.currentTimeMillis();
1992       while (myDaemonCodeAnalyzer.isRunning() || !applied.contains(editor)) {
1993         UIUtil.dispatchAllInvocationEvents();
1994         if (System.currentTimeMillis() - start > 10000) {
1995           fail("Too long waiting for daemon");
1996         }
1997       }
1998
1999       runHeavyProcessing = true;
2000       ApplicationManager.getApplication().executeOnPooledThread(() -> {
2001         AccessToken token = HeavyProcessLatch.INSTANCE.processStarted("my own heavy op");
2002         try {
2003           while (runHeavyProcessing) {
2004           }
2005         }
2006         finally {
2007           token.finish();
2008         }
2009       });
2010       while (!HeavyProcessLatch.INSTANCE.isRunning()) {
2011         UIUtil.dispatchAllInvocationEvents();
2012       }
2013       applied.clear();
2014       collected.clear();
2015
2016       type("xxx"); // try to restart daemon
2017
2018       start = System.currentTimeMillis();
2019       while (System.currentTimeMillis() < start + 5000) {
2020         assertEmpty(applied);  // it should not restart
2021         assertEmpty(collected);
2022         UIUtil.dispatchAllInvocationEvents();
2023       }
2024     }
2025     finally {
2026       runHeavyProcessing = false;
2027       settings.AUTOREPARSE_DELAY = delay;
2028     }
2029   }
2030
2031   
2032   public void testModificationInsideCodeBlockDoesNotRehighlightWholeFile() throws Exception {
2033     configureByText(JavaFileType.INSTANCE, "class X { int f = \"error\"; int f() { int gg<caret> = 11; return 0;} }");
2034     List<HighlightInfo> errors = highlightErrors();
2035     assertEquals(1, errors.size());
2036     assertEquals("Incompatible types. Found: 'java.lang.String', required: 'int'", errors.get(0).getDescription());
2037
2038     errors.get(0).highlighter.dispose();
2039
2040     errors = highlightErrors();
2041     assertEmpty(errors);
2042
2043     type("23");
2044     errors = highlightErrors();
2045     assertEmpty(errors);
2046
2047     myEditor.getCaretModel().moveToOffset(0);
2048     type("/* */");
2049     errors = highlightErrors();
2050     assertEquals(1, errors.size());
2051     assertEquals("Incompatible types. Found: 'java.lang.String', required: 'int'", errors.get(0).getDescription());
2052   }
2053
2054   public void _testCaretMovementDoesNotRestartHighlighting() throws Exception {
2055     configureByText(JavaFileType.INSTANCE, "class X { int f = \"error\"; int f() { int gg<caret> = 11; return 0;} }");
2056
2057     TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(getEditor());
2058     final DaemonCodeAnalyzerImpl di = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
2059     final AtomicReference<ProgressIndicator> indicator = new AtomicReference<>();
2060     final List<HighlightInfo> errors = filter(
2061       di.runPasses(getFile(), getEditor().getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, false, () -> {
2062         if (indicator.get() == null) {
2063           indicator.set(di.getUpdateProgress());
2064         }
2065         assertSame(indicator.get(), di.getUpdateProgress());
2066         caretRight();
2067         if (getEditor().getCaretModel().getOffset() == getEditor().getDocument().getTextLength()-1) {
2068           getEditor().getCaretModel().moveToOffset(0);
2069         }
2070       }), HighlightSeverity.ERROR);
2071
2072     assertEquals(1, errors.size());
2073     assertEquals("Incompatible types. Found: 'java.lang.String', required: 'int'", errors.get(0).getDescription());
2074   }
2075
2076   
2077   public void testHighlightingDoesWaitForEmbarrassinglySlowExternalAnnotatorsToFinish() throws Exception {
2078     configureByText(JavaFileType.INSTANCE, "class X { int f() { int gg<caret> = 11; return 0;} }");
2079     final AtomicBoolean run = new AtomicBoolean();
2080     final int SLEEP = 20000;
2081     ExternalAnnotator<Integer, Integer> annotator = new ExternalAnnotator<Integer, Integer>() {
2082       @Nullable
2083       @Override
2084       public Integer collectInformation(@NotNull PsiFile file) {
2085         return 0;
2086       }
2087
2088       @Nullable
2089       @Override
2090       public Integer doAnnotate(final Integer collectedInfo) {
2091         TimeoutUtil.sleep(SLEEP);
2092         return 0;
2093       }
2094
2095       @Override
2096       public void apply(@NotNull final PsiFile file, final Integer annotationResult, @NotNull final AnnotationHolder holder) {
2097         run.set(true);
2098       }
2099     };
2100     ExternalLanguageAnnotators.INSTANCE.addExplicitExtension(JavaLanguage.INSTANCE, annotator);
2101
2102     try {
2103       long start = System.currentTimeMillis();
2104       List<HighlightInfo> errors = filter(CodeInsightTestFixtureImpl.instantiateAndRun(getFile(), getEditor(), new int[0], false),
2105                                           HighlightSeverity.ERROR);
2106       long elapsed = System.currentTimeMillis() - start;
2107
2108       assertEquals(0, errors.size());
2109       assertTrue("Elapsed: "+elapsed, elapsed >= SLEEP);
2110       assertTrue(run.get());
2111     }
2112     finally {
2113       ExternalLanguageAnnotators.INSTANCE.removeExplicitExtension(JavaLanguage.INSTANCE, annotator);
2114     }
2115   }
2116
2117   public void testModificationInExcludedFileDoesNotCauseRehighlight() throws Exception {
2118     final PsiFile excluded = configureByText(JavaFileType.INSTANCE, "class EEE { void f(){} }");
2119     PsiTestUtil.addExcludedRoot(myModule, excluded.getVirtualFile().getParent());
2120
2121     configureByText(JavaFileType.INSTANCE, "class X { <caret> }");
2122     List<HighlightInfo> errors = highlightErrors();
2123     assertEmpty(errors);
2124     FileStatusMap me = DaemonCodeAnalyzerEx.getInstanceEx(getProject()).getFileStatusMap();
2125     TextRange scope = me.getFileDirtyScope(getEditor().getDocument(), Pass.UPDATE_ALL);
2126     assertNull(scope);
2127
2128     WriteCommandAction.runWriteCommandAction(getProject(), () -> ((PsiJavaFile)excluded).getClasses()[0].getMethods()[0].delete());
2129
2130     UIUtil.dispatchAllInvocationEvents();
2131     scope = me.getFileDirtyScope(getEditor().getDocument(), Pass.UPDATE_ALL);
2132     assertNull(scope);
2133   }
2134
2135   public void testModificationInWorkspaceXmlDoesNotCauseRehighlight() throws Exception {
2136     configureByText(JavaFileType.INSTANCE, "class X { <caret> }");
2137     ApplicationEx application = ApplicationManagerEx.getApplicationEx();
2138     boolean appSave = application.isDoNotSave();
2139     application.doNotSave(false);
2140     try {
2141       application.saveAll();
2142       final PsiFile excluded = PsiManager.getInstance(getProject()).findFile(getProject().getWorkspaceFile());
2143
2144       List<HighlightInfo> errors = highlightErrors();
2145       assertEmpty(errors);
2146       FileStatusMap me = DaemonCodeAnalyzerEx.getInstanceEx(getProject()).getFileStatusMap();
2147       TextRange scope = me.getFileDirtyScope(getEditor().getDocument(), Pass.UPDATE_ALL);
2148       assertNull(scope);
2149
2150       WriteCommandAction.runWriteCommandAction(getProject(), () -> {
2151         Document document = PsiDocumentManager.getInstance(getProject()).getDocument(excluded);
2152         document.insertString(0, "<!-- dsfsd -->");
2153         PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
2154       });
2155       UIUtil.dispatchAllInvocationEvents();
2156       scope = me.getFileDirtyScope(getEditor().getDocument(), Pass.UPDATE_ALL);
2157       assertNull(scope);
2158     }
2159     finally {
2160       application.doNotSave(appSave);
2161     }
2162   }
2163
2164   public void testLightBulbDoesNotUpdateIntentionsInEDT() throws Exception {
2165     final IntentionAction longLongUpdate = new AbstractIntentionAction() {
2166       @Override
2167       public void invoke(@NotNull Project project, Editor editor, PsiFile file) {
2168       }
2169
2170       @Nls
2171       @NotNull
2172       @Override
2173       public String getText() {
2174         return "LongAction";
2175       }
2176
2177       @Override
2178       public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
2179         if (ApplicationManager.getApplication().isDispatchThread()) {
2180           throw new RuntimeException("Must not update actions in EDT");
2181         }
2182         return true;
2183       }
2184     };
2185     IntentionManager.getInstance().addAction(longLongUpdate);
2186     Disposer.register(myTestRootDisposable, () -> IntentionManager.getInstance().unregisterIntention(longLongUpdate));
2187     configureByText(JavaFileType.INSTANCE, "class X { <caret>  }");
2188     makeEditorWindowVisible(new Point(0, 0));
2189     doHighlighting();
2190     myDaemonCodeAnalyzer.restart();
2191     DaemonCodeAnalyzerSettings mySettings = DaemonCodeAnalyzerSettings.getInstance();
2192     mySettings.AUTOREPARSE_DELAY = 0;
2193     for (int i=0; i<1000; i++) {
2194       caretRight();
2195       UIUtil.dispatchAllInvocationEvents();
2196       caretLeft();
2197       DaemonProgressIndicator updateProgress = myDaemonCodeAnalyzer.getUpdateProgress();
2198       while(myDaemonCodeAnalyzer.getUpdateProgress() == updateProgress) { // wait until daemon started
2199         UIUtil.dispatchAllInvocationEvents();
2200       }
2201       long start = System.currentTimeMillis();
2202       while (myDaemonCodeAnalyzer.isRunning() && System.currentTimeMillis() < start + 500) {
2203         UIUtil.dispatchAllInvocationEvents(); // wait for a bit more until ShowIntentionsPass.doApplyInformationToEditor() called
2204       }
2205     }
2206   }
2207
2208   public void testLightBulbIsHiddenWhenFixRangeIsCollapsed() {
2209     configureByText(StdFileTypes.JAVA, "class S { void foo() { boolean var; if (<selection>va<caret>r</selection>) {}} }");
2210     ((EditorImpl)myEditor).getScrollPane().getViewport().setSize(1000, 1000);
2211
2212     final Set<LightweightHint> visibleHints = ContainerUtil.newIdentityTroveSet();
2213     getProject().getMessageBus().connect(myTestRootDisposable).subscribe(EditorHintListener.TOPIC, new EditorHintListener() {
2214       @Override
2215       public void hintShown(final Project project, final LightweightHint hint, final int flags) {
2216         visibleHints.add(hint);
2217         hint.addHintListener(new HintListener() {
2218           @Override
2219           public void hintHidden(EventObject event) {
2220             visibleHints.remove(hint);
2221             hint.removeHintListener(this);
2222           }
2223         });
2224       }
2225     });
2226
2227     highlightErrors();
2228     IntentionHintComponent lastHintBeforeDeletion = myDaemonCodeAnalyzer.getLastIntentionHint();
2229     assertNotNull(lastHintBeforeDeletion);
2230
2231     delete(myEditor);
2232     highlightErrors();
2233     IntentionHintComponent lastHintAfterDeletion = myDaemonCodeAnalyzer.getLastIntentionHint();
2234     assertSame(lastHintBeforeDeletion, lastHintAfterDeletion);
2235
2236     assertEmpty(visibleHints);
2237   }
2238   
2239   public void testCodeFoldingPassRestartsOnRegionUnfolding() throws Exception {
2240     DaemonCodeAnalyzerSettings settings = DaemonCodeAnalyzerSettings.getInstance();
2241     int savedDelay = settings.AUTOREPARSE_DELAY;
2242     settings.AUTOREPARSE_DELAY = 0;
2243     try {
2244       configureByText(StdFileTypes.JAVA, "class Foo {\n" +
2245                                          "    void m() {\n" +
2246                                          "\n" +
2247                                          "    }\n" +
2248                                          "}");
2249       CodeFoldingManager.getInstance(getProject()).buildInitialFoldings(myEditor);
2250       waitForDaemon();
2251       EditorTestUtil.executeAction(myEditor, IdeActions.ACTION_COLLAPSE_ALL_REGIONS);
2252       waitForDaemon();
2253       checkFoldingState("[FoldRegion +(25:33), placeholder='{...}']");
2254
2255       new WriteCommandAction<Void>(myProject){
2256         @Override
2257         protected void run(@NotNull Result<Void> result) throws Throwable {
2258           myEditor.getDocument().insertString(0, "/*");
2259         }
2260       }.execute();
2261       waitForDaemon();
2262       checkFoldingState("[FoldRegion -(0:37), placeholder='/.../', FoldRegion +(27:35), placeholder='{...}']");
2263       
2264       EditorTestUtil.executeAction(myEditor, IdeActions.ACTION_EXPAND_ALL_REGIONS);
2265       waitForDaemon();
2266       checkFoldingState("[FoldRegion -(0:37), placeholder='/.../']");
2267     }
2268     finally {
2269       settings.AUTOREPARSE_DELAY = savedDelay;
2270     }
2271   }
2272
2273   private void checkFoldingState(String expected) {
2274     assertEquals(expected, Arrays.toString(myEditor.getFoldingModel().getAllFoldRegions()));
2275   }
2276
2277   private void waitForDaemon() {
2278     long deadline = System.currentTimeMillis() + 60_000;
2279     while (!daemonIsWorkingOrPending()) {
2280       if (System.currentTimeMillis() > deadline) fail("Too long waiting for daemon to start");
2281       UIUtil.dispatchInvocationEvent();
2282     }
2283     while (daemonIsWorkingOrPending()) {
2284       if (System.currentTimeMillis() > deadline) fail("Too long waiting for daemon to finish");
2285       UIUtil.dispatchInvocationEvent();
2286     }
2287   }
2288   
2289   private boolean daemonIsWorkingOrPending() {
2290     return PsiDocumentManager.getInstance(myProject).isUncommited(myEditor.getDocument()) || myDaemonCodeAnalyzer.isRunningOrPending();
2291   }
2292
2293   public void testRehighlightInDebuggerExpressionFragment() throws Exception {
2294     PsiExpressionCodeFragment fragment = JavaCodeFragmentFactory.getInstance(getProject()).createExpressionCodeFragment("+ <caret>\"a\"", null,
2295                                     PsiType.getJavaLangObject(getPsiManager(), GlobalSearchScope.allScope(getProject())), true);
2296     myFile = fragment;
2297     Document document = PsiDocumentManager.getInstance(getProject()).getDocument(fragment);
2298     myEditor = EditorFactory.getInstance().createEditor(document, getProject(), StdFileTypes.JAVA, false);
2299
2300     ProperTextRange visibleRange = makeEditorWindowVisible(new Point(0, 0));
2301     assertEquals(document.getTextLength(), visibleRange.getLength());
2302
2303     try {
2304       final EditorInfo editorInfo = new EditorInfo(document.getText());
2305
2306       final String newFileText = editorInfo.getNewFileText();
2307       ApplicationManager.getApplication().runWriteAction(() -> {
2308         if (!document.getText().equals(newFileText)) {
2309           document.setText(newFileText);
2310         }
2311
2312         editorInfo.applyToEditor(myEditor);
2313       });
2314
2315       PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
2316
2317
2318       List<HighlightInfo> errors = highlightErrors();
2319       HighlightInfo error = assertOneElement(errors);
2320       assertEquals("Operator '+' cannot be applied to 'java.lang.String'", error.getDescription());
2321
2322       type(" ");
2323
2324       Collection<HighlightInfo> afterTyping = highlightErrors();
2325       HighlightInfo after = assertOneElement(afterTyping);
2326       assertEquals("Operator '+' cannot be applied to 'java.lang.String'", after.getDescription());
2327     }
2328     finally {
2329       EditorFactory.getInstance().releaseEditor(myEditor);
2330     }
2331   }
2332
2333   public void testFileReload() throws Exception {
2334     VirtualFile file = createFile("a.java", "").getVirtualFile();
2335     Document document = getDocument(file);
2336     assertNotNull(document);
2337
2338     FileStatusMap fileStatusMap = myDaemonCodeAnalyzer.getFileStatusMap();
2339
2340     WriteCommandAction.runWriteCommandAction(getProject(), () -> {
2341       PlatformTestUtil.tryGcSoftlyReachableObjects();
2342       assertNull(PsiDocumentManager.getInstance(getProject()).getCachedPsiFile(document));
2343
2344       document.insertString(0, "class X { void foo() {}}");
2345       assertEquals(TextRange.from(0, document.getTextLength()), fileStatusMap.getFileDirtyScope(document, Pass.UPDATE_ALL));
2346
2347       FileContentUtilCore.reparseFiles(file);
2348       assertEquals(TextRange.from(0, document.getTextLength()), fileStatusMap.getFileDirtyScope(document, Pass.UPDATE_ALL));
2349
2350       findClass("X").getMethods()[0].delete();
2351       assertEquals(TextRange.from(0, document.getTextLength()), fileStatusMap.getFileDirtyScope(document, Pass.UPDATE_ALL));
2352     });
2353   }
2354 }
2355