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