cleanup
[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     final DaemonProgressIndicator[] indicator = new DaemonProgressIndicator[1];
1528
1529     try {
1530       Module alienModule = doCreateRealModuleIn("x", alienProject, getModuleType());
1531       final VirtualFile alienRoot = PsiTestUtil.createTestProjectStructure(alienProject, alienModule, myFilesToDelete);
1532       OpenFileDescriptor alienDescriptor = new WriteAction<OpenFileDescriptor>() {
1533         @Override
1534         protected void run(@NotNull Result<OpenFileDescriptor> result) throws Throwable {
1535           VirtualFile alienFile = alienRoot.createChildData(this, "X.java");
1536           setFileText(alienFile, "class Alien { }");
1537           OpenFileDescriptor alienDescriptor = new OpenFileDescriptor(alienProject, alienFile);
1538           result.setResult(alienDescriptor);
1539         }
1540       }.execute().throwException().getResultObject();
1541
1542       FileEditorManager fe = FileEditorManager.getInstance(alienProject);
1543       final Editor alienEditor = fe.openTextEditor(alienDescriptor, false);
1544       ((EditorImpl)alienEditor).setCaretActive();
1545       PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
1546       PsiDocumentManager.getInstance(alienProject).commitAllDocuments();
1547
1548       // start daemon in main project. should check for its cancel when typing in alien
1549       TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(getEditor());
1550       DaemonCodeAnalyzerImpl di = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
1551       final boolean[] checked = {false};
1552       di.runPasses(getFile(), getEditor().getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, true, () -> {
1553         if (checked[0]) return;
1554         checked[0] = true;
1555         indicator[0] = myDaemonCodeAnalyzer.getUpdateProgress();
1556         typeInAlienEditor(alienEditor, 'x');
1557       });
1558     }
1559     catch (ProcessCanceledException ignored) {
1560       //DaemonProgressIndicator.setDebug(true);
1561       //System.out.println("indicator = " + indicator[0]);
1562       return;
1563     }
1564     finally {
1565       ProjectManagerEx.getInstanceEx().closeAndDispose(alienProject);
1566     }
1567     fail("must throw PCE");
1568   }
1569
1570   public void testPasteInAnonymousCodeBlock() throws Throwable {
1571     configureByText(StdFileTypes.JAVA, "class X{ void f() {" +
1572                                        "     int x=0;\n" +
1573                                        "    Runnable r = new Runnable() { public void run() {\n" +
1574                                        " <caret>\n" +
1575                                        "    }};\n" +
1576                                        "    <selection>int y = x;</selection>\n " +
1577                                        "\n} }");
1578     assertEmpty(highlightErrors());
1579     copy();
1580     assertEquals("int y = x;", getEditor().getSelectionModel().getSelectedText());
1581     getEditor().getSelectionModel().removeSelection();
1582     paste();
1583     List<HighlightInfo> errors = highlightErrors();
1584     assertEquals(1, errors.size());
1585   }
1586
1587   private void paste() {
1588     EditorActionManager actionManager = EditorActionManager.getInstance();
1589     final EditorActionHandler actionHandler = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
1590     WriteCommandAction.runWriteCommandAction(null, () -> actionHandler.execute(getEditor(), null, DataManager.getInstance().getDataContext()));
1591   }
1592
1593   private void copy() {
1594     EditorActionManager actionManager = EditorActionManager.getInstance();
1595     final EditorActionHandler actionHandler = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_COPY);
1596     WriteCommandAction.runWriteCommandAction(null, () -> actionHandler.execute(getEditor(), null, DataManager.getInstance().getDataContext()));
1597   }
1598
1599   public void testReactivityPerformance() throws Throwable {
1600     @NonNls String filePath = "/psi/resolve/Thinlet.java";
1601     configureByFile(filePath);
1602     type(' ');
1603     CompletionContributor.forLanguage(getFile().getLanguage());
1604     highlightErrors();
1605
1606     final DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
1607     int N = Math.max(5, Timings.adjustAccordingToMySpeed(80, true));
1608     System.out.println("N = " + N);
1609     final long[] interruptTimes = new long[N];
1610     for (int i = 0; i < N; i++) {
1611       codeAnalyzer.restart();
1612       final int finalI = i;
1613       final long start = System.currentTimeMillis();
1614       final AtomicLong typingStart = new AtomicLong();
1615       final AtomicReference<RuntimeException> exception = new AtomicReference<>();
1616       Thread watcher = new Thread("reactivity watcher") {
1617         @Override
1618         public void run() {
1619           while (true) {
1620             final long start1 = typingStart.get();
1621             if (start1 == -1) break;
1622             if (start1 == 0) {
1623               try {
1624                 Thread.sleep(5);
1625               }
1626               catch (InterruptedException e1) {
1627                 throw new RuntimeException(e1);
1628               }
1629               continue;
1630             }
1631             long elapsed = System.currentTimeMillis() - start1;
1632             if (elapsed > 500) {
1633               // too long, see WTF
1634               String message = "Too long interrupt: " + elapsed +
1635                                "; Progress: " + codeAnalyzer.getUpdateProgress() +
1636                                "\n----------------------------";
1637               dumpThreadsToConsole();
1638               exception.set(new RuntimeException(message));
1639               throw exception.get();
1640             }
1641           }
1642         }
1643       };
1644       try {
1645         PsiFile file = getFile();
1646         Editor editor = getEditor();
1647         Project project = file.getProject();
1648         CodeInsightTestFixtureImpl.ensureIndexesUpToDate(project);
1649         TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor);
1650         PsiDocumentManager.getInstance(myProject).commitAllDocuments();
1651         watcher.start();
1652         Runnable interrupt = () -> {
1653           long now = System.currentTimeMillis();
1654           if (now - start < 100) {
1655             // wait to engage all highlighting threads
1656             return;
1657           }
1658           typingStart.set(System.currentTimeMillis());
1659           type(' ');
1660           long end = System.currentTimeMillis();
1661           long interruptTime = end - now;
1662           interruptTimes[finalI] = interruptTime;
1663           assertNull(codeAnalyzer.getUpdateProgress());
1664           System.out.println(interruptTime);
1665           throw new ProcessCanceledException();
1666         };
1667         long hiStart = System.currentTimeMillis();
1668         codeAnalyzer.runPasses(file, editor.getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, false, interrupt);
1669         long hiEnd = System.currentTimeMillis();
1670         DaemonProgressIndicator progress = codeAnalyzer.getUpdateProgress();
1671         String message = "Should have been interrupted: " + progress + "; Elapsed: " + (hiEnd - hiStart) + "ms";
1672         dumpThreadsToConsole();
1673         throw new RuntimeException(message);
1674       }
1675       catch (ProcessCanceledException ignored) {
1676       }
1677       finally {
1678         typingStart.set(-1); // cancel watcher
1679         watcher.join();
1680         if (exception.get() != null) {
1681           throw exception.get();
1682         }
1683       }
1684     }
1685
1686     long ave = ArrayUtil.averageAmongMedians(interruptTimes, 3);
1687     System.out.println("Average among the N/3 median times: " + ave + "ms");
1688     assertTrue(ave < 300);
1689   }
1690
1691   private static void dumpThreadsToConsole() {
1692     System.err.println("----all threads---");
1693     for (Thread thread : Thread.getAllStackTraces().keySet()) {
1694
1695       boolean canceled = CoreProgressManager.isCanceledThread(thread);
1696       if (canceled) {
1697         System.err.println("Thread " + thread + " indicator is canceled");
1698       }
1699     }
1700     PerformanceWatcher.dumpThreadsToConsole("");
1701     System.err.println("----///////---");
1702   }
1703
1704   public void testTypingLatencyPerformance() throws Throwable {
1705     @NonNls String filePath = "/psi/resolve/ThinletBig.java";
1706
1707     configureByFile(filePath);
1708
1709     type(' ');
1710     CompletionContributor.forLanguage(getFile().getLanguage());
1711     long s = System.currentTimeMillis();
1712     highlightErrors();
1713     long e = System.currentTimeMillis();
1714     //System.out.println("Hi elapsed: "+(e-s));
1715
1716     final DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
1717     int N = Math.max(5, Timings.adjustAccordingToMySpeed(80, true));
1718     //System.out.println("N = " + N);
1719     final long[] interruptTimes = new long[N];
1720     for (int i = 0; i < N; i++) {
1721       codeAnalyzer.restart();
1722       final int finalI = i;
1723       final long start = System.currentTimeMillis();
1724       Runnable interrupt = () -> {
1725         long now = System.currentTimeMillis();
1726         if (now - start < 100) {
1727           // wait to engage all highlighting threads
1728           return;
1729         }
1730         type(' ');
1731         long end = System.currentTimeMillis();
1732         long interruptTime = end - now;
1733         interruptTimes[finalI] = interruptTime;
1734         assertNull(codeAnalyzer.getUpdateProgress());
1735         //System.out.println(interruptTime);
1736         throw new ProcessCanceledException();
1737       };
1738       try {
1739         PsiFile file = getFile();
1740         Editor editor = getEditor();
1741         Project project = file.getProject();
1742         CodeInsightTestFixtureImpl.ensureIndexesUpToDate(project);
1743         TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor);
1744         PsiDocumentManager.getInstance(myProject).commitAllDocuments();
1745         codeAnalyzer.runPasses(file, editor.getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, false, interrupt);
1746
1747         throw new RuntimeException("should have been interrupted");
1748       }
1749       catch (ProcessCanceledException ignored) {
1750       }
1751       backspace();
1752       //highlightErrors();
1753     }
1754
1755     long mean = ArrayUtil.averageAmongMedians(interruptTimes, 3);
1756     long avg = Arrays.stream(interruptTimes).sum() / interruptTimes.length;
1757     long max = Arrays.stream(interruptTimes).max().getAsLong();
1758     long min = Arrays.stream(interruptTimes).min().getAsLong();
1759     System.out.println("Average among the N/3 median times: " + mean + "ms; max: "+max+"; min:"+min+"; avg: "+avg);
1760     assertTrue(mean < 10);
1761   }
1762
1763   private static void startCPUProfiling() {
1764     try {
1765       Class<?> aClass = Class.forName("com.intellij.util.ProfilingUtil");
1766       Method method = ReflectionUtil.getDeclaredMethod(aClass, "startCPUProfiling");
1767       method.invoke(null);
1768     }
1769     catch (Exception e) {
1770       throw new RuntimeException(e);
1771     }
1772   }
1773   private static void stopCPUProfiling() {
1774     try {
1775       Class<?> aClass = Class.forName("com.intellij.util.ProfilingUtil");
1776       Method method = ReflectionUtil.getDeclaredMethod(aClass, "stopCPUProfiling");
1777       method.invoke(null);
1778     }
1779     catch (Exception e) {
1780       throw new RuntimeException(e);
1781     }
1782   }
1783
1784   private static String captureCPUSnapshot() {
1785     try {
1786       Class<?> aClass = Class.forName("com.intellij.util.ProfilingUtil");
1787       Method method = ReflectionUtil.getDeclaredMethod(aClass, "captureCPUSnapshot");
1788       return (String)method.invoke(null);
1789     }
1790     catch (Exception e) {
1791       throw new RuntimeException(e);
1792     }
1793   }
1794
1795   public void testPostHighlightingPassRunsOnEveryPsiModification() throws Exception {
1796     PsiFile x = createFile("X.java", "public class X { public static void ffffffffffffff(){} }");
1797     PsiFile use = createFile("Use.java", "public class Use { { <caret>X.ffffffffffffff(); } }");
1798     configureByExistingFile(use.getVirtualFile());
1799
1800     InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getCurrentProfile();
1801     HighlightDisplayKey myDeadCodeKey = HighlightDisplayKey.find(UnusedDeclarationInspectionBase.SHORT_NAME);
1802     if (myDeadCodeKey == null) {
1803       myDeadCodeKey = HighlightDisplayKey.register(UnusedDeclarationInspectionBase.SHORT_NAME, UnusedDeclarationInspectionBase.DISPLAY_NAME);
1804     }
1805     UnusedDeclarationInspectionBase myDeadCodeInspection = new UnusedDeclarationInspectionBase(true);
1806     enableInspectionTool(myDeadCodeInspection);
1807     assert profile.isToolEnabled(myDeadCodeKey, myFile);
1808
1809     Editor xEditor = createEditor(x.getVirtualFile());
1810     List<HighlightInfo> xInfos = filter(CodeInsightTestFixtureImpl.instantiateAndRun(x, xEditor, new int[0], false),
1811                                         HighlightSeverity.WARNING);
1812     HighlightInfo info = ContainerUtil.find(xInfos, xInfo -> xInfo.getDescription().equals("Method 'ffffffffffffff()' is never used"));
1813     assertNull(xInfos.toString(), info);
1814
1815     Editor useEditor = myEditor;
1816     List<HighlightInfo> useInfos = filter(CodeInsightTestFixtureImpl.instantiateAndRun(use, useEditor, new int[0], false), HighlightSeverity.ERROR);
1817     assertEmpty(useInfos);
1818
1819     type('/');
1820     type('/');
1821
1822     PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
1823     xInfos = filter(CodeInsightTestFixtureImpl.instantiateAndRun(x, xEditor, new int[0], false), HighlightSeverity.WARNING);
1824     info = ContainerUtil.find(xInfos, xInfo -> xInfo.getDescription().equals("Method 'ffffffffffffff()' is never used"));
1825     assertNotNull(xInfos.toString(), info);
1826   }
1827
1828
1829   public void testErrorDisappearsRightAfterTypingInsideVisibleAreaWhileDaemonContinuesToChugAlong() throws Throwable {
1830     String text = "class X{\nint xxx;\n{\nint i = <selection>null</selection><caret>;\n" + StringUtil.repeat("{ this.hashCode(); }\n\n\n", 10000) + "}}";
1831     configureByText(StdFileTypes.JAVA, text);
1832
1833     ((EditorImpl)myEditor).getScrollPane().getViewport().setSize(100, 100);
1834     DaemonCodeAnalyzerSettings.getInstance().setImportHintEnabled(true);
1835
1836     myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
1837     ((EditorImpl)myEditor).getScrollPane().getViewport().setViewPosition(new Point(0, 0));
1838     ((EditorImpl)myEditor).getScrollPane().getViewport().setExtentSize(new Dimension(100, 100000));
1839     ProperTextRange visibleRange = VisibleHighlightingPassFactory.calculateVisibleRange(getEditor());
1840     assertTrue(visibleRange.getLength() > 0);
1841     final Document document = myEditor.getDocument();
1842     assertTrue(visibleRange.getLength() < document.getTextLength());
1843
1844     List<HighlightInfo> err1 = highlightErrors();
1845     HighlightInfo info = assertOneElement(err1);
1846     final String errorDescription = "Incompatible types. Found: 'null', required: 'int'";
1847     assertEquals(errorDescription, info.getDescription());
1848
1849     MarkupModelEx model = (MarkupModelEx)DocumentMarkupModel.forDocument(document, myProject, false);
1850     final boolean[] errorRemoved = {false};
1851
1852     model.addMarkupModelListener(getTestRootDisposable(), new MarkupModelListener.Adapter() {
1853       @Override
1854       public void beforeRemoved(@NotNull RangeHighlighterEx highlighter) {
1855         Object tt = highlighter.getErrorStripeTooltip();
1856         if (!(tt instanceof HighlightInfo)) return;
1857         String description = ((HighlightInfo)tt).getDescription();
1858         if (errorDescription.equals(description)) {
1859           errorRemoved[0] = true;
1860
1861           List<TextEditorHighlightingPass> passes = myDaemonCodeAnalyzer.getPassesToShowProgressFor(document);
1862           GeneralHighlightingPass ghp = null;
1863           for (TextEditorHighlightingPass pass : passes) {
1864             if (pass instanceof GeneralHighlightingPass && pass.getId() == Pass.UPDATE_ALL) {
1865               assert ghp == null : ghp;
1866               ghp = (GeneralHighlightingPass)pass;
1867             }
1868           }
1869           assertNotNull(ghp);
1870           boolean finished = ghp.isFinished();
1871           assertFalse(finished);
1872         }
1873       }
1874     });
1875     type("1");
1876
1877     List<HighlightInfo> errors = highlightErrors();
1878     assertEmpty(errors);
1879     assertTrue(errorRemoved[0]);
1880   }
1881
1882   public void testDaemonWorksForDefaultProjectSinceItIsNeededInSettingsDialogForSomeReason() {
1883     assertNotNull(DaemonCodeAnalyzer.getInstance(ProjectManager.getInstance().getDefaultProject()));
1884   }
1885
1886   public void testChangeEventsAreNotAlwaysGeneric() throws Exception {
1887     String body = "class X {\n" +
1888                   "<caret>    @org.PPP\n" +
1889                   "    void dtoArrayDouble() {\n" +
1890                   "    }\n" +
1891                   "}";
1892     configureByText(JavaFileType.INSTANCE, body);
1893     makeEditorWindowVisible(new Point(), myEditor);
1894
1895     List<HighlightInfo> errors = highlightErrors();
1896     assertFalse(errors.isEmpty());
1897
1898     type("//");
1899     errors = highlightErrors();
1900     assertEmpty(errors);
1901
1902     backspace();
1903     backspace();
1904     errors = highlightErrors();
1905     assertFalse(errors.isEmpty());
1906   }
1907
1908   public void testInterruptOnTyping() throws Throwable {
1909     @NonNls String filePath = "/psi/resolve/Thinlet.java";
1910     configureByFile(filePath);
1911     highlightErrors();
1912
1913     final DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
1914     codeAnalyzer.restart();
1915     Runnable interrupt = () -> type(' ');
1916     try {
1917       PsiDocumentManager.getInstance(myProject).commitAllDocuments();
1918
1919       PsiFile file = getFile();
1920       Editor editor = getEditor();
1921       Project project = file.getProject();
1922       CodeInsightTestFixtureImpl.ensureIndexesUpToDate(project);
1923       TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor);
1924       codeAnalyzer.runPasses(file, editor.getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, true, interrupt);
1925     }
1926     catch (ProcessCanceledException ignored) {
1927       return;
1928     }
1929     fail("PCE must have been thrown");
1930   }
1931
1932   public void testAllPassesFinishAfterInterruptOnTyping_Performance() throws Throwable {
1933     @NonNls String filePath = "/psi/resolve/Thinlet.java";
1934     configureByFile(filePath);
1935     highlightErrors();
1936
1937     final DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
1938     Runnable interrupt = () -> type(' ');
1939     type(' ');
1940     for (int i=0; i<100; i++) {
1941       backspace();
1942       codeAnalyzer.restart();
1943       try {
1944         PsiDocumentManager.getInstance(myProject).commitAllDocuments();
1945
1946         PsiFile file = getFile();
1947         Editor editor = getEditor();
1948         Project project = file.getProject();
1949         CodeInsightTestFixtureImpl.ensureIndexesUpToDate(project);
1950         TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor);
1951         codeAnalyzer.runPasses(file, editor.getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, true, interrupt);
1952       }
1953       catch (ProcessCanceledException ignored) {
1954         codeAnalyzer.waitForTermination();
1955         continue;
1956       }
1957       fail("PCE must have been thrown");
1958     }
1959   }
1960
1961   public void testCodeFoldingInSplittedWindowAppliesToAllEditors() throws Exception {
1962     final Set<Editor> applied = new THashSet<>();
1963     final Set<Editor> collected = new THashSet<>();
1964     registerFakePass(applied, collected);
1965
1966     configureByText(PlainTextFileType.INSTANCE, "");
1967     Editor editor1 = getEditor();
1968     final Editor editor2 = EditorFactory.getInstance().createEditor(editor1.getDocument(),getProject());
1969     Disposer.register(getProject(), () -> EditorFactory.getInstance().releaseEditor(editor2));
1970     TextEditor textEditor1 = new PsiAwareTextEditorProvider().getTextEditor(editor1);
1971     TextEditor textEditor2 = new PsiAwareTextEditorProvider().getTextEditor(editor2);
1972
1973     List<HighlightInfo> errors = myDaemonCodeAnalyzer.runPasses(myFile, editor1.getDocument(), Arrays.asList(textEditor1,textEditor2), new int[0], false, null);
1974     assertEmpty(errors);
1975
1976     assertEquals(collected, ContainerUtil.newHashSet(editor1, editor2));
1977     assertEquals(applied, ContainerUtil.newHashSet(editor1, editor2));
1978   }
1979
1980   private void registerFakePass(@NotNull final Set<Editor> applied, @NotNull final Set<Editor> collected) {
1981     class Fac extends AbstractProjectComponent implements TextEditorHighlightingPassFactory {
1982       private Fac(Project project) {
1983         super(project);
1984       }
1985
1986       @Override
1987       public TextEditorHighlightingPass createHighlightingPass(@NotNull PsiFile file, @NotNull final Editor editor) {
1988         return new EditorBoundHighlightingPass(editor, file, false) {
1989           @Override
1990           public void doCollectInformation(@NotNull ProgressIndicator progress) {
1991             collected.add(editor);
1992           }
1993
1994           @Override
1995           public void doApplyInformationToEditor() {
1996             applied.add(editor);
1997           }
1998         };
1999       }
2000     }
2001     TextEditorHighlightingPassRegistrar registrar = TextEditorHighlightingPassRegistrar.getInstance(getProject());
2002     registrar.registerTextEditorHighlightingPass(new Fac(getProject()), null, null, false, -1);
2003   }
2004
2005   private volatile boolean runHeavyProcessing;
2006   public void testDaemonDisablesItselfDuringHeavyProcessing() throws Exception {
2007     runHeavyProcessing = false;
2008     DaemonCodeAnalyzerSettings settings = DaemonCodeAnalyzerSettings.getInstance();
2009     int delay = settings.AUTOREPARSE_DELAY;
2010     settings.AUTOREPARSE_DELAY = 0;
2011
2012     final Set<Editor> applied = Collections.synchronizedSet(new THashSet<>());
2013     final Set<Editor> collected = Collections.synchronizedSet(new THashSet<>());
2014     registerFakePass(applied, collected);
2015
2016     configureByText(PlainTextFileType.INSTANCE, "");
2017     Editor editor = getEditor();
2018     EditorTracker editorTracker = getProject().getComponent(EditorTracker.class);
2019     editorTracker.setActiveEditors(Collections.singletonList(editor));
2020     while (HeavyProcessLatch.INSTANCE.isRunning()) {
2021       UIUtil.dispatchAllInvocationEvents();
2022     }
2023     type("xxx"); // restart daemon
2024     assertTrue(editorTracker.getActiveEditors().contains(editor));
2025     assertSame(editor, FileEditorManager.getInstance(myProject).getSelectedTextEditor());
2026
2027     try {
2028       // wait for first pass to complete
2029       long start = System.currentTimeMillis();
2030       while (myDaemonCodeAnalyzer.isRunning() || !applied.contains(editor)) {
2031         UIUtil.dispatchAllInvocationEvents();
2032         if (System.currentTimeMillis() - start > 10000) {
2033           fail("Too long waiting for daemon");
2034         }
2035       }
2036
2037       runHeavyProcessing = true;
2038       ApplicationManager.getApplication().executeOnPooledThread(() -> {
2039         AccessToken token = HeavyProcessLatch.INSTANCE.processStarted("my own heavy op");
2040         try {
2041           while (runHeavyProcessing) {
2042           }
2043         }
2044         finally {
2045           token.finish();
2046         }
2047       });
2048       while (!HeavyProcessLatch.INSTANCE.isRunning()) {
2049         UIUtil.dispatchAllInvocationEvents();
2050       }
2051       applied.clear();
2052       collected.clear();
2053
2054       type("xxx"); // try to restart daemon
2055
2056       start = System.currentTimeMillis();
2057       while (System.currentTimeMillis() < start + 5000) {
2058         assertEmpty(applied);  // it should not restart
2059         assertEmpty(collected);
2060         UIUtil.dispatchAllInvocationEvents();
2061       }
2062     }
2063     finally {
2064       runHeavyProcessing = false;
2065       settings.AUTOREPARSE_DELAY = delay;
2066     }
2067   }
2068
2069   
2070   public void testModificationInsideCodeBlockDoesNotRehighlightWholeFile() throws Exception {
2071     configureByText(JavaFileType.INSTANCE, "class X { int f = \"error\"; int f() { int gg<caret> = 11; return 0;} }");
2072     List<HighlightInfo> errors = highlightErrors();
2073     assertEquals(1, errors.size());
2074     assertEquals("Incompatible types. Found: 'java.lang.String', required: 'int'", errors.get(0).getDescription());
2075
2076     errors.get(0).highlighter.dispose();
2077
2078     errors = highlightErrors();
2079     assertEmpty(errors);
2080
2081     type("23");
2082     errors = highlightErrors();
2083     assertEmpty(errors);
2084
2085     myEditor.getCaretModel().moveToOffset(0);
2086     type("/* */");
2087     errors = highlightErrors();
2088     assertEquals(1, errors.size());
2089     assertEquals("Incompatible types. Found: 'java.lang.String', required: 'int'", errors.get(0).getDescription());
2090   }
2091
2092   public void _testCaretMovementDoesNotRestartHighlighting() throws Exception {
2093     configureByText(JavaFileType.INSTANCE, "class X { int f = \"error\"; int f() { int gg<caret> = 11; return 0;} }");
2094
2095     TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(getEditor());
2096     final DaemonCodeAnalyzerImpl di = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject());
2097     final AtomicReference<ProgressIndicator> indicator = new AtomicReference<>();
2098     final List<HighlightInfo> errors = filter(
2099       di.runPasses(getFile(), getEditor().getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, false, () -> {
2100         if (indicator.get() == null) {
2101           indicator.set(di.getUpdateProgress());
2102         }
2103         assertSame(indicator.get(), di.getUpdateProgress());
2104         caretRight();
2105         if (getEditor().getCaretModel().getOffset() == getEditor().getDocument().getTextLength()-1) {
2106           getEditor().getCaretModel().moveToOffset(0);
2107         }
2108       }), HighlightSeverity.ERROR);
2109
2110     assertEquals(1, errors.size());
2111     assertEquals("Incompatible types. Found: 'java.lang.String', required: 'int'", errors.get(0).getDescription());
2112   }
2113
2114   
2115   public void testHighlightingDoesWaitForEmbarrassinglySlowExternalAnnotatorsToFinish() throws Exception {
2116     configureByText(JavaFileType.INSTANCE, "class X { int f() { int gg<caret> = 11; return 0;} }");
2117     final AtomicBoolean run = new AtomicBoolean();
2118     final int SLEEP = 20000;
2119     ExternalAnnotator<Integer, Integer> annotator = new ExternalAnnotator<Integer, Integer>() {
2120       @Nullable
2121       @Override
2122       public Integer collectInformation(@NotNull PsiFile file) {
2123         return 0;
2124       }
2125
2126       @Nullable
2127       @Override
2128       public Integer doAnnotate(final Integer collectedInfo) {
2129         TimeoutUtil.sleep(SLEEP);
2130         return 0;
2131       }
2132
2133       @Override
2134       public void apply(@NotNull final PsiFile file, final Integer annotationResult, @NotNull final AnnotationHolder holder) {
2135         run.set(true);
2136       }
2137     };
2138     ExternalLanguageAnnotators.INSTANCE.addExplicitExtension(JavaLanguage.INSTANCE, annotator);
2139
2140     try {
2141       long start = System.currentTimeMillis();
2142       List<HighlightInfo> errors = filter(CodeInsightTestFixtureImpl.instantiateAndRun(getFile(), getEditor(), new int[0], false),
2143                                           HighlightSeverity.ERROR);
2144       long elapsed = System.currentTimeMillis() - start;
2145
2146       assertEquals(0, errors.size());
2147       if (!run.get()) {
2148         fail(ThreadDumper.dumpThreadsToString());
2149       }
2150       assertTrue("Elapsed: "+elapsed, elapsed >= SLEEP);
2151     }
2152     finally {
2153       ExternalLanguageAnnotators.INSTANCE.removeExplicitExtension(JavaLanguage.INSTANCE, annotator);
2154     }
2155   }
2156
2157   public void testModificationInExcludedFileDoesNotCauseRehighlight() throws Exception {
2158     final PsiFile excluded = configureByText(JavaFileType.INSTANCE, "class EEE { void f(){} }");
2159     PsiTestUtil.addExcludedRoot(myModule, excluded.getVirtualFile().getParent());
2160
2161     configureByText(JavaFileType.INSTANCE, "class X { <caret> }");
2162     List<HighlightInfo> errors = highlightErrors();
2163     assertEmpty(errors);
2164     FileStatusMap me = DaemonCodeAnalyzerEx.getInstanceEx(getProject()).getFileStatusMap();
2165     TextRange scope = me.getFileDirtyScope(getEditor().getDocument(), Pass.UPDATE_ALL);
2166     assertNull(scope);
2167
2168     WriteCommandAction.runWriteCommandAction(getProject(), () -> ((PsiJavaFile)excluded).getClasses()[0].getMethods()[0].delete());
2169
2170     UIUtil.dispatchAllInvocationEvents();
2171     scope = me.getFileDirtyScope(getEditor().getDocument(), Pass.UPDATE_ALL);
2172     assertNull(scope);
2173   }
2174
2175   public void testModificationInWorkspaceXmlDoesNotCauseRehighlight() throws Exception {
2176     configureByText(JavaFileType.INSTANCE, "class X { <caret> }");
2177     ApplicationEx application = ApplicationManagerEx.getApplicationEx();
2178     boolean appSave = application.isDoNotSave();
2179     application.doNotSave(false);
2180     try {
2181       application.saveAll();
2182       final PsiFile excluded = PsiManager.getInstance(getProject()).findFile(getProject().getWorkspaceFile());
2183
2184       List<HighlightInfo> errors = highlightErrors();
2185       assertEmpty(errors);
2186       FileStatusMap me = DaemonCodeAnalyzerEx.getInstanceEx(getProject()).getFileStatusMap();
2187       TextRange scope = me.getFileDirtyScope(getEditor().getDocument(), Pass.UPDATE_ALL);
2188       assertNull(scope);
2189
2190       WriteCommandAction.runWriteCommandAction(getProject(), () -> {
2191         Document document = PsiDocumentManager.getInstance(getProject()).getDocument(excluded);
2192         document.insertString(0, "<!-- dsfsd -->");
2193         PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
2194       });
2195       UIUtil.dispatchAllInvocationEvents();
2196       scope = me.getFileDirtyScope(getEditor().getDocument(), Pass.UPDATE_ALL);
2197       assertNull(scope);
2198     }
2199     finally {
2200       application.doNotSave(appSave);
2201     }
2202   }
2203
2204   public void testLightBulbDoesNotUpdateIntentionsInEDT() throws Exception {
2205     final IntentionAction longLongUpdate = new AbstractIntentionAction() {
2206       @Override
2207       public void invoke(@NotNull Project project, Editor editor, PsiFile file) {
2208       }
2209
2210       @Nls
2211       @NotNull
2212       @Override
2213       public String getText() {
2214         return "LongAction";
2215       }
2216
2217       @Override
2218       public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
2219         if (ApplicationManager.getApplication().isDispatchThread()) {
2220           throw new RuntimeException("Must not update actions in EDT");
2221         }
2222         return true;
2223       }
2224     };
2225     IntentionManager.getInstance().addAction(longLongUpdate);
2226     Disposer.register(getTestRootDisposable(), () -> IntentionManager.getInstance().unregisterIntention(longLongUpdate));
2227     configureByText(JavaFileType.INSTANCE, "class X { <caret>  }");
2228     makeEditorWindowVisible(new Point(0, 0), myEditor);
2229     doHighlighting();
2230     myDaemonCodeAnalyzer.restart();
2231     DaemonCodeAnalyzerSettings mySettings = DaemonCodeAnalyzerSettings.getInstance();
2232     mySettings.AUTOREPARSE_DELAY = 0;
2233     for (int i=0; i<1000; i++) {
2234       caretRight();
2235       UIUtil.dispatchAllInvocationEvents();
2236       caretLeft();
2237       DaemonProgressIndicator updateProgress = myDaemonCodeAnalyzer.getUpdateProgress();
2238       while(myDaemonCodeAnalyzer.getUpdateProgress() == updateProgress) { // wait until daemon started
2239         UIUtil.dispatchAllInvocationEvents();
2240       }
2241       long start = System.currentTimeMillis();
2242       while (myDaemonCodeAnalyzer.isRunning() && System.currentTimeMillis() < start + 500) {
2243         UIUtil.dispatchAllInvocationEvents(); // wait for a bit more until ShowIntentionsPass.doApplyInformationToEditor() called
2244       }
2245     }
2246   }
2247
2248   public void testLightBulbIsHiddenWhenFixRangeIsCollapsed() {
2249     configureByText(StdFileTypes.JAVA, "class S { void foo() { boolean var; if (<selection>va<caret>r</selection>) {}} }");
2250     ((EditorImpl)myEditor).getScrollPane().getViewport().setSize(1000, 1000);
2251
2252     final Set<LightweightHint> visibleHints = ContainerUtil.newIdentityTroveSet();
2253     getProject().getMessageBus().connect(getTestRootDisposable()).subscribe(EditorHintListener.TOPIC, new EditorHintListener() {
2254       @Override
2255       public void hintShown(final Project project, final LightweightHint hint, final int flags) {
2256         visibleHints.add(hint);
2257         hint.addHintListener(new HintListener() {
2258           @Override
2259           public void hintHidden(EventObject event) {
2260             visibleHints.remove(hint);
2261             hint.removeHintListener(this);
2262           }
2263         });
2264       }
2265     });
2266
2267     highlightErrors();
2268     IntentionHintComponent lastHintBeforeDeletion = myDaemonCodeAnalyzer.getLastIntentionHint();
2269     assertNotNull(lastHintBeforeDeletion);
2270
2271     delete(myEditor);
2272     highlightErrors();
2273     IntentionHintComponent lastHintAfterDeletion = myDaemonCodeAnalyzer.getLastIntentionHint();
2274     assertSame(lastHintBeforeDeletion, lastHintAfterDeletion);
2275
2276     assertEmpty(visibleHints);
2277   }
2278   
2279   public void testCodeFoldingPassRestartsOnRegionUnfolding() throws Exception {
2280     DaemonCodeAnalyzerSettings settings = DaemonCodeAnalyzerSettings.getInstance();
2281     int savedDelay = settings.AUTOREPARSE_DELAY;
2282     settings.AUTOREPARSE_DELAY = 0;
2283     try {
2284       configureByText(StdFileTypes.JAVA, "class Foo {\n" +
2285                                          "    void m() {\n" +
2286                                          "\n" +
2287                                          "    }\n" +
2288                                          "}");
2289       CodeFoldingManager.getInstance(getProject()).buildInitialFoldings(myEditor);
2290       waitForDaemon();
2291       EditorTestUtil.executeAction(myEditor, IdeActions.ACTION_COLLAPSE_ALL_REGIONS);
2292       waitForDaemon();
2293       checkFoldingState("[FoldRegion +(25:33), placeholder='{...}']");
2294
2295       new WriteCommandAction<Void>(myProject){
2296         @Override
2297         protected void run(@NotNull Result<Void> result) throws Throwable {
2298           myEditor.getDocument().insertString(0, "/*");
2299         }
2300       }.execute();
2301       waitForDaemon();
2302       checkFoldingState("[FoldRegion -(0:37), placeholder='/.../', FoldRegion +(27:35), placeholder='{...}']");
2303       
2304       EditorTestUtil.executeAction(myEditor, IdeActions.ACTION_EXPAND_ALL_REGIONS);
2305       waitForDaemon();
2306       checkFoldingState("[FoldRegion -(0:37), placeholder='/.../']");
2307     }
2308     finally {
2309       settings.AUTOREPARSE_DELAY = savedDelay;
2310     }
2311   }
2312
2313   private void checkFoldingState(String expected) {
2314     assertEquals(expected, Arrays.toString(myEditor.getFoldingModel().getAllFoldRegions()));
2315   }
2316
2317   private void waitForDaemon() {
2318     long deadline = System.currentTimeMillis() + 60_000;
2319     while (!daemonIsWorkingOrPending()) {
2320       if (System.currentTimeMillis() > deadline) fail("Too long waiting for daemon to start");
2321       UIUtil.dispatchInvocationEvent();
2322     }
2323     while (daemonIsWorkingOrPending()) {
2324       if (System.currentTimeMillis() > deadline) fail("Too long waiting for daemon to finish");
2325       UIUtil.dispatchInvocationEvent();
2326     }
2327   }
2328   
2329   private boolean daemonIsWorkingOrPending() {
2330     return PsiDocumentManager.getInstance(myProject).isUncommited(myEditor.getDocument()) || myDaemonCodeAnalyzer.isRunningOrPending();
2331   }
2332
2333   public void testRehighlightInDebuggerExpressionFragment() throws Exception {
2334     PsiExpressionCodeFragment fragment = JavaCodeFragmentFactory.getInstance(getProject()).createExpressionCodeFragment("+ <caret>\"a\"", null,
2335                                     PsiType.getJavaLangObject(getPsiManager(), GlobalSearchScope.allScope(getProject())), true);
2336     myFile = fragment;
2337     Document document = PsiDocumentManager.getInstance(getProject()).getDocument(fragment);
2338     myEditor = EditorFactory.getInstance().createEditor(document, getProject(), StdFileTypes.JAVA, false);
2339
2340     ProperTextRange visibleRange = makeEditorWindowVisible(new Point(0, 0), myEditor);
2341     assertEquals(document.getTextLength(), visibleRange.getLength());
2342
2343     try {
2344       final EditorInfo editorInfo = new EditorInfo(document.getText());
2345
2346       final String newFileText = editorInfo.getNewFileText();
2347       ApplicationManager.getApplication().runWriteAction(() -> {
2348         if (!document.getText().equals(newFileText)) {
2349           document.setText(newFileText);
2350         }
2351
2352         editorInfo.applyToEditor(myEditor);
2353       });
2354
2355       PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
2356
2357
2358       List<HighlightInfo> errors = highlightErrors();
2359       HighlightInfo error = assertOneElement(errors);
2360       assertEquals("Operator '+' cannot be applied to 'java.lang.String'", error.getDescription());
2361
2362       type(" ");
2363
2364       Collection<HighlightInfo> afterTyping = highlightErrors();
2365       HighlightInfo after = assertOneElement(afterTyping);
2366       assertEquals("Operator '+' cannot be applied to 'java.lang.String'", after.getDescription());
2367     }
2368     finally {
2369       EditorFactory.getInstance().releaseEditor(myEditor);
2370     }
2371   }
2372
2373   public void testFileReload() throws Exception {
2374     VirtualFile file = createFile("a.java", "").getVirtualFile();
2375     Document document = getDocument(file);
2376     assertNotNull(document);
2377
2378     FileStatusMap fileStatusMap = myDaemonCodeAnalyzer.getFileStatusMap();
2379
2380     WriteCommandAction.runWriteCommandAction(getProject(), () -> {
2381       PlatformTestUtil.tryGcSoftlyReachableObjects();
2382       assertNull(PsiDocumentManager.getInstance(getProject()).getCachedPsiFile(document));
2383
2384       document.insertString(0, "class X { void foo() {}}");
2385       assertEquals(TextRange.from(0, document.getTextLength()), fileStatusMap.getFileDirtyScope(document, Pass.UPDATE_ALL));
2386
2387       FileContentUtilCore.reparseFiles(file);
2388       assertEquals(TextRange.from(0, document.getTextLength()), fileStatusMap.getFileDirtyScope(document, Pass.UPDATE_ALL));
2389
2390       findClass("X").getMethods()[0].delete();
2391       assertEquals(TextRange.from(0, document.getTextLength()), fileStatusMap.getFileDirtyScope(document, Pass.UPDATE_ALL));
2392     });
2393   }
2394 }
2395