diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java index fce402f66006..081db54b4cd9 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java @@ -245,14 +245,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh @Override public void visitClass(PsiClass aClass) { super.visitClass(aClass); if (aClass instanceof JspClass) return; - if (aClass.isAnnotationType()) { - if (!PsiUtil.isLanguageLevel5OrHigher(aClass)) { - HighlightInfo info = HighlightInfo - .createHighlightInfo(HighlightInfoType.ERROR, aClass.getNameIdentifier(), JavaErrorMessages.message("annotations.prior.15")); - QuickFixAction.registerQuickFixAction(info, new IncreaseLanguageLevelFix(LanguageLevel.JDK_1_5)); - myHolder.add(info); - } - } if (!myHolder.hasErrorResults()) myHolder.add(GenericsHighlightUtil.checkInterfaceMultipleInheritance(aClass)); if (!myHolder.hasErrorResults()) myHolder.add(HighlightClassUtil.checkDuplicateTopLevelClass(aClass)); if (!myHolder.hasErrorResults()) myHolder.add(GenericsHighlightUtil.checkEnumMustNotBeLocal(aClass)); @@ -391,10 +383,17 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh myHolder.add(HighlightUtil.checkVariableAlreadyDefined((PsiVariable)parent)); } else if (parent instanceof PsiClass) { - myHolder.add(HighlightClassUtil.checkClassAlreadyImported((PsiClass)parent, identifier)); - myHolder.add(HighlightClassUtil.checkExternalizableHasPublicNoArgsConstructor((PsiClass)parent, identifier)); - if (!(parent instanceof PsiAnonymousClass)) { - myHolder.add(HighlightNamesUtil.highlightClassName((PsiClass)parent, ((PsiClass)parent).getNameIdentifier())); + PsiClass aClass = (PsiClass)parent; + if (aClass.isAnnotationType() && !PsiUtil.isLanguageLevel5OrHigher(aClass)) { + HighlightInfo info = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, identifier, JavaErrorMessages.message("annotations.prior.15")); + QuickFixAction.registerQuickFixAction(info, new IncreaseLanguageLevelFix(LanguageLevel.JDK_1_5)); + myHolder.add(info); + } + + myHolder.add(HighlightClassUtil.checkClassAlreadyImported(aClass, identifier)); + myHolder.add(HighlightClassUtil.checkExternalizableHasPublicNoArgsConstructor(aClass, identifier)); + if (!(parent instanceof PsiAnonymousClass) && aClass.getNameIdentifier() == identifier) { + myHolder.add(HighlightNamesUtil.highlightClassName(aClass, identifier)); } } else if (parent instanceof PsiMethod) { diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/ImportClassFixBase.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/ImportClassFixBase.java index 7f293d34e2bd..663eaeaa3c49 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/ImportClassFixBase.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/ImportClassFixBase.java @@ -103,7 +103,7 @@ public abstract class ImportClassFixBase im public enum Result { POPUP_SHOWN, - CLASS_IMPORTED, + CLASS_AUTO_IMPORTED, POPUP_NOT_SHOWN } @@ -149,7 +149,7 @@ public abstract class ImportClassFixBase im action.execute(); } }); - return Result.CLASS_IMPORTED; + return Result.CLASS_AUTO_IMPORTED; } if (doShow && canImportHere) { @@ -174,7 +174,7 @@ public abstract class ImportClassFixBase im return false; } Result result = doFix(editor, true, false); - return result == Result.POPUP_SHOWN || result == Result.CLASS_IMPORTED; + return result == Result.POPUP_SHOWN || result == Result.CLASS_AUTO_IMPORTED; } @NotNull diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/ImportHelperTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/ImportHelperTest.java index 69d663eeb995..32aa22a22739 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/ImportHelperTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/ImportHelperTest.java @@ -205,7 +205,7 @@ public class ImportHelperTest extends DaemonAnalyzerTestCase { myEditor.getCaretModel().moveToOffset(offset - 1); result = new ImportClassFix((PsiJavaCodeReferenceElement)ref).doFix(getEditor(), true, false); - assertEquals(ImportClassFixBase.Result.CLASS_IMPORTED, result); + assertEquals(ImportClassFixBase.Result.CLASS_AUTO_IMPORTED, result); UIUtil.dispatchAllInvocationEvents(); assertEmpty(filter(doHighlighting(), HighlightSeverity.ERROR)); @@ -234,7 +234,7 @@ public class ImportHelperTest extends DaemonAnalyzerTestCase { assertTrue(ref instanceof PsiJavaCodeReferenceElement); ImportClassFixBase.Result result = new ImportClassFix((PsiJavaCodeReferenceElement)ref).doFix(getEditor(), true, false); - assertEquals(ImportClassFixBase.Result.CLASS_IMPORTED, result); + assertEquals(ImportClassFixBase.Result.CLASS_AUTO_IMPORTED, result); UIUtil.dispatchAllInvocationEvents(); assertEmpty(filter(doHighlighting(), HighlightSeverity.ERROR)); @@ -265,7 +265,7 @@ public class ImportHelperTest extends DaemonAnalyzerTestCase { (PsiJavaCodeReferenceElement)getFile().findReferenceAt(getEditor().getCaretModel().getOffset() - 2); ImportClassFix fix = new ImportClassFix(element); ImportClassFixBase.Result result = fix.doFix(getEditor(), false, false); - assertEquals(ImportClassFixBase.Result.CLASS_IMPORTED, result); + assertEquals(ImportClassFixBase.Result.CLASS_AUTO_IMPORTED, result); assertNotSame(0, ((PsiJavaFile)getFile()).getImportList().getAllImportStatements().length); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/Divider.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/Divider.java index e0084d7fcefc..9d2558a1de27 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/Divider.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/Divider.java @@ -52,19 +52,21 @@ public class Divider { } } } - + private static void getInsideAndOutside(@NotNull PsiFile root, int startOffset, int endOffset, @NotNull TextRange range, @NotNull List inside, - @NotNull List outside) { + @NotNull List outside + ) { final int currentOffset = root.getTextRange().getStartOffset(); final Condition[] filters = Extensions.getExtensions(CollectHighlightsUtil.EP_NAME); int offset = currentOffset; final TIntStack starts = new TIntStack(STARTING_TREE_HEIGHT); + starts.push(startOffset); final Stack elements = new Stack(STARTING_TREE_HEIGHT); final Stack children = new Stack(STARTING_TREE_HEIGHT); PsiElement element = root; @@ -96,7 +98,6 @@ public class Divider { offset += element.getTextLength(); } - if (elements.isEmpty()) break; int start = starts.pop(); if (startOffset <= start && offset <= endOffset) { if (range.containsRange(start, offset)) { @@ -107,6 +108,7 @@ public class Divider { } } + if (elements.isEmpty()) break; element = elements.pop(); child = children.pop(); } @@ -120,7 +122,5 @@ public class Divider { child = PsiUtilBase.NULL_PSI_ELEMENT; } } - - outside.add(root); } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/GeneralHighlightingPass.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/GeneralHighlightingPass.java index b340d77e8f4c..d4aef53a4b23 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/GeneralHighlightingPass.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/GeneralHighlightingPass.java @@ -31,12 +31,15 @@ import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.HighlighterColors; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.openapi.editor.ex.MarkupModelEx; +import com.intellij.openapi.editor.markup.MarkupModel; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.fileTypes.SyntaxHighlighter; @@ -52,6 +55,7 @@ import com.intellij.problems.Problem; import com.intellij.problems.WolfTheProblemSolver; import com.intellij.psi.*; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; +import com.intellij.psi.impl.source.tree.injected.Place; import com.intellij.psi.search.PsiSearchHelper; import com.intellij.psi.search.TodoItem; import com.intellij.psi.tree.IElementType; @@ -60,6 +64,7 @@ import com.intellij.util.SmartList; import com.intellij.util.ui.UIUtil; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.awt.*; import java.util.*; @@ -74,10 +79,10 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP private final int myStartOffset; private final int myEndOffset; private final boolean myUpdateAll; - private final TextRange myPriorityRange; + private final ProperTextRange myPriorityRange; + private final Editor myEditor; - private volatile Collection myHighlights = Collections.emptyList(); - private final Map> myInjectedPsiHighlights = new HashMap>(); + private final Collection myHighlights = new ArrayList(); protected volatile boolean myHasErrorElement; private volatile boolean myErrorFound; @@ -86,15 +91,15 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP return o1.order() - o2.order(); } }; + private Runnable myApplyCommand; public GeneralHighlightingPass(@NotNull Project project, @NotNull PsiFile file, @NotNull Document document, int startOffset, int endOffset, - boolean updateAll - ) { - this(project, file, document, startOffset, endOffset, updateAll, new TextRange(0,0)); + boolean updateAll) { + this(project, file, document, startOffset, endOffset, updateAll, new ProperTextRange(0,document.getTextLength()), null); } public GeneralHighlightingPass(@NotNull Project project, @NotNull PsiFile file, @@ -102,18 +107,29 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP int startOffset, int endOffset, boolean updateAll, - @NotNull TextRange priorityRange) { + @NotNull ProperTextRange priorityRange, + @Nullable Editor editor) { super(project, document, PRESENTABLE_NAME, file, true); myStartOffset = startOffset; myEndOffset = endOffset; myUpdateAll = updateAll; myPriorityRange = priorityRange; + myEditor = editor; LOG.assertTrue(file.isValid()); setId(Pass.UPDATE_ALL); myHasErrorElement = !isWholeFileHighlighting() && Boolean.TRUE.equals(myFile.getUserData(HAS_ERROR_ELEMENT)); FileStatusMap fileStatusMap = ((DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(myProject)).getFileStatusMap(); myErrorFound = !isWholeFileHighlighting() && fileStatusMap.wasErrorFound(myDocument); + + myApplyCommand = new Runnable() { + public void run() { + ProperTextRange range = new ProperTextRange(myStartOffset, myEndOffset); + MarkupModel model = myDocument.getMarkupModel(myProject); + UpdateHighlightersUtil.cleanFileLevelHighlights(myProject, Pass.UPDATE_ALL,myFile); + UpdateHighlightersUtil.setHighlightersInRange(range, myHighlights, (MarkupModelEx)model, Pass.UPDATE_ALL, myDocument, myProject); + } + }; } private static final Key HIGHLIGHT_VISITOR_INSTANCE_COUNT = new Key("HIGHLIGHT_VISITOR_INSTANCE_COUNT"); @@ -144,26 +160,94 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP } protected void collectInformationWithProgress(final ProgressIndicator progress) { - final Collection result = new THashSet(100); + final Set result = new THashSet(100); + final Set outsideResult = new THashSet(100); + DaemonCodeAnalyzer daemonCodeAnalyzer = DaemonCodeAnalyzer.getInstance(myProject); - FileStatusMap fileStatusMap = ((DaemonCodeAnalyzerImpl)daemonCodeAnalyzer).getFileStatusMap(); + final FileStatusMap fileStatusMap = ((DaemonCodeAnalyzerImpl)daemonCodeAnalyzer).getFileStatusMap(); HighlightVisitor[] highlightVisitors = createHighlightVisitors(); - HighlightVisitor[] filteredVisitors = filterVisitors(highlightVisitors, myFile); - List inside = new ArrayList(); - List outside = new ArrayList(); + final HighlightVisitor[] filteredVisitors = filterVisitors(highlightVisitors, myFile); + final List inside = new ArrayList(); + final List outside = new ArrayList(); try { Divider.getInsideAndOutside(myFile, myStartOffset, myEndOffset, myPriorityRange, inside, outside, HighlightLevelUtil.AnalysisLevel.HIGHLIGHT); + final List injectedInside = new ArrayList(); + final List injectedOutside = new ArrayList(); + divideInjectedPsiHighlights(inside, outside, progress, injectedInside, injectedOutside); - List elements = inside; - elements.addAll(outside); + setProgressLimit((long)(inside.size()+outside.size()) /** filteredVisitors.length*/); - result.addAll(collectHighlights(elements, progress, filteredVisitors)); - if (!addInjectedPsiHighlights(elements, progress)) throw new ProcessCanceledException(); + final boolean forceHighlightParents = forceHighlightParents(); if (!isDumbMode()) { - result.addAll(highlightTodos(myFile, myDocument.getCharsSequence(), 0, myFile.getTextLength(), progress)); + highlightTodos(myFile, myDocument.getCharsSequence(), myStartOffset, myEndOffset, progress, myPriorityRange, result, outsideResult); } + collectHighlights(inside, new Runnable() { + @Override + public void run() { + if (!addInjectedPsiHighlights(injectedInside, progress, Collections.synchronizedSet(result))) throw new ProcessCanceledException(); + + if (!outside.isEmpty() || !injectedOutside.isEmpty()) { + if (!inside.isEmpty()) { // do not apply when there were no elements to highlight + // clear infos found in visible area to avoid applying them twice + final List toApply = new ArrayList(result.size()); + for (HighlightInfo info : result) { + if (myPriorityRange.containsRange(info.getStartOffset(), info.getEndOffset())) { + toApply.add(info); + } + else { + outsideResult.add(info); + } + } + myHighlights.addAll(toApply); + result.clear(); + result.addAll(outsideResult); + + UIUtil.invokeLaterIfNeeded(new Runnable() { + @Override + public void run() { + if (progress.isCanceled()) return; + MarkupModel markupModel = myDocument.getMarkupModel(myProject); + UpdateHighlightersUtil.cleanFileLevelHighlights(myProject, Pass.UPDATE_ALL, myFile); + + UpdateHighlightersUtil.setHighlightersInRange(myPriorityRange, toApply, (MarkupModelEx)markupModel, Pass.UPDATE_ALL, myDocument, myProject); + } + }); + UIUtil.invokeLaterIfNeeded(new Runnable() { + @Override + public void run() { + if (progress.isCanceled() || myEditor == null) return; + new ShowAutoImportPass(myProject, myFile, myEditor).applyInformationToEditor(); + } + }); + } + + final List injectedOutsideInfos = Collections.synchronizedList(new ArrayList()); + if (!addInjectedPsiHighlights(injectedOutside, progress, injectedOutsideInfos)) throw new ProcessCanceledException(); + + myApplyCommand = new Runnable() { + @Override + public void run() { + UpdateHighlightersUtil.setHighlightersToEditorOutsideRange(myProject, myDocument, result, myStartOffset, myEndOffset, myPriorityRange, Pass.UPDATE_ALL); + Map> myInjectedPsiHighlights = new HashMap>(); + for (HighlightInfo info : injectedOutsideInfos) { + TextRange textRange = new TextRange(info.getStartOffset(), info.getEndOffset()); + Collection storedInfos = myInjectedPsiHighlights.get(textRange); + if (storedInfos == null) { + storedInfos = new SmartList(); + myInjectedPsiHighlights.put(textRange, storedInfos); + } + storedInfos.add(info); + } + + UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument, myInjectedPsiHighlights, Pass.UPDATE_ALL); + } + }; + } + } + }, outside, progress, filteredVisitors, result, forceHighlightParents); + if (myUpdateAll) { fileStatusMap.setErrorFoundFlag(myDocument, myErrorFound); } @@ -171,17 +255,21 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP finally { incVisitorUsageCount(-1); } - myHighlights = result; + myHighlights.addAll(result); } - // returns false if canceled - private boolean addInjectedPsiHighlights(@NotNull final List elements, final ProgressIndicator progress) { + private void divideInjectedPsiHighlights(@NotNull final List elements1, + @NotNull final List elements2, + @NotNull final ProgressIndicator progress, + @NotNull List inside, + @NotNull List outside) { List injected = InjectedLanguageUtil.getCachedInjectedDocuments(myFile); - Collection hosts = new THashSet(elements.size() + injected.size()); + Collection hosts = new THashSet(elements1.size() + elements2.size() + injected.size()); // rehighlight all injected PSI regardless the range, // since change in one place can lead to invalidation of injected PSI in (completely) other place. for (DocumentWindow documentRange : injected) { + progress.checkCanceled(); if (!documentRange.isValid()) continue; PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(documentRange); if (file == null) continue; @@ -193,40 +281,50 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP hosts.add(context); } } - hosts.addAll(elements); - - final Collection injectedFiles = new THashSet(); - EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); - final TextAttributes injectedAttributes = scheme.getAttributes(EditorColors.INJECTED_LANGUAGE_FRAGMENT); + hosts.addAll(elements1); + hosts.addAll(elements2); for (PsiElement element : hosts) { + progress.checkCanceled(); + final List destination = myPriorityRange.contains(element.getTextRange()) ? inside : outside; InjectedLanguageUtil.enumerate(element, myFile, new PsiLanguageInjectionHost.InjectedPsiVisitor() { public void visit(@NotNull PsiFile injectedPsi, @NotNull List places) { - if (injectedFiles.add(injectedPsi)) { // for concatenations there can be many injection hosts with only one injected PSI - for (PsiLanguageInjectionHost.Shred place : places) { - TextRange textRange = place.getRangeInsideHost().shiftRight(place.host.getTextRange().getStartOffset()); - if (textRange.isEmpty()) continue; - String desc = injectedPsi.getLanguage().getDisplayName() + ": " + injectedPsi.getText(); - HighlightInfo info = HighlightInfo.createHighlightInfo(HighlightInfoType.INJECTED_LANGUAGE_FRAGMENT, textRange, null, desc, injectedAttributes); - addHighlightInfo(textRange, info); - } - } + destination.add(injectedPsi); // for concatenations there can be many injection hosts with only one injected PSI } }, false); } + } + + // returns false if canceled + private boolean addInjectedPsiHighlights(@NotNull final List injectedFiles, + final ProgressIndicator progress, + final Collection infos) { + EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); + final TextAttributes injectedAttributes = scheme.getAttributes(EditorColors.INJECTED_LANGUAGE_FRAGMENT); + if (injectedFiles.isEmpty()) return true; final InjectedLanguageManager injectedLanguageManager = InjectedLanguageManager.getInstance(myProject); return JobUtil.invokeConcurrentlyUnderMyProgress(new ArrayList(injectedFiles), new Processor() { public boolean process(final PsiFile injectedPsi) { DocumentWindow documentWindow = (DocumentWindow)PsiDocumentManager.getInstance(myProject).getCachedDocument(injectedPsi); + + Place places = InjectedLanguageUtil.getShreds(injectedPsi); + for (PsiLanguageInjectionHost.Shred place : places) { + TextRange textRange = place.getRangeInsideHost().shiftRight(place.host.getTextRange().getStartOffset()); + if (textRange.isEmpty()) continue; + String desc = injectedPsi.getLanguage().getDisplayName() + ": " + injectedPsi.getText(); + HighlightInfo info = HighlightInfo.createHighlightInfo(HighlightInfoType.INJECTED_LANGUAGE_FRAGMENT, textRange, null, desc, injectedAttributes); + infos.add(info); + } + HighlightInfoHolder holder = createInfoHolder(injectedPsi); runHighlightVisitosForInjected(injectedPsi, holder, progress); for (int i=0; i todos = highlightTodos(injectedPsi, injectedPsi.getText(), 0, injectedPsi.getTextLength(), progress); + List todos = new ArrayList(); + highlightTodos(injectedPsi, injectedPsi.getText(), 0, injectedPsi.getTextLength(), progress, myPriorityRange, todos, todos); for (HighlightInfo info : todos) { - addPatchedInfos(info, injectedPsi, documentWindow, injectedLanguageManager, null); + addPatchedInfos(info, injectedPsi, documentWindow, injectedLanguageManager, null, infos); } } return true; @@ -272,8 +371,12 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP return fixedTextRange; } - private void addPatchedInfos(HighlightInfo info, PsiFile injectedPsi, DocumentWindow documentWindow, InjectedLanguageManager injectedLanguageManager, - TextRange fixedTextRange) { + private static void addPatchedInfos(HighlightInfo info, + PsiFile injectedPsi, + DocumentWindow documentWindow, + InjectedLanguageManager injectedLanguageManager, + TextRange fixedTextRange, + Collection out) { ProperTextRange textRange = new ProperTextRange(info.startOffset, info.endOffset); List editables = injectedLanguageManager.intersectWithAllEditableFragments(injectedPsi, textRange); for (TextRange editable : editables) { @@ -297,18 +400,7 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP } } } - addHighlightInfo(hostRange, patched); - } - } - - private void addHighlightInfo(@NotNull TextRange textRange, @NotNull HighlightInfo highlightInfo) { - synchronized (myInjectedPsiHighlights) { - Collection infos = myInjectedPsiHighlights.get(textRange); - if (infos == null) { - infos = new SmartList(); - myInjectedPsiHighlights.put(textRange, infos); - } - infos.add(highlightInfo); + out.add(patched); } } @@ -393,14 +485,7 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP protected void applyInformationWithProgress() { myFile.putUserData(HAS_ERROR_ELEMENT, myHasErrorElement); - ProperTextRange range = new ProperTextRange(myStartOffset, myEndOffset); - Collection infos = myInjectedPsiHighlights.get(range); - if (infos == null) { - infos = new ArrayList(myHighlights.size()); - myInjectedPsiHighlights.put(range, infos); - } - infos.addAll(myHighlights); - UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument, myInjectedPsiHighlights, Pass.UPDATE_ALL); + myApplyCommand.run(); if (myUpdateAll) { reportErrorsToWolf(); @@ -409,27 +494,24 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP @NotNull Collection getHighlights() { - ArrayList list = new ArrayList(myHighlights); - for (Collection infos : myInjectedPsiHighlights.values()) { - list.addAll(infos); - } - return list; + return new ArrayList(myHighlights); } - private Collection collectHighlights(@NotNull final List elements, @NotNull final ProgressIndicator progress, - final HighlightVisitor[] visitors) { + private void collectHighlights(@NotNull final List elements1, + @NotNull final Runnable after1, + @NotNull final List elements2, + @NotNull final ProgressIndicator progress, + @NotNull final HighlightVisitor[] visitors, + @NotNull final Set gotHighlights, + final boolean forceHighlightParents) { final Set skipParentsSet = new THashSet(); - final Set gotHighlights = new THashSet(); - - final boolean forceHighlightParents = forceHighlightParents(); - final HighlightInfoHolder holder = createInfoHolder(myFile); - setProgressLimit((long)elements.size() * visitors.length); - final int chunkSize = Math.max(1, elements.size() / 100); // one percent precision is enough - for (final HighlightVisitor visitor : visitors) { - Runnable action = new Runnable() { - public void run() { + final int chunkSize = Math.max(1, (elements1.size()+elements2.size()) / 100); // one percent precision is enough + + final Runnable action = new Runnable() { + public void run() { + for (List elements : new List[]{elements1, elements2}) { int nextLimit = chunkSize; for (int i = 0; i < elements.size(); i++) { PsiElement element = elements.get(i); @@ -445,7 +527,10 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP } holder.clear(); - visitor.visit(element, holder); + for (final HighlightVisitor visitor : visitors) { + visitor.visit(element, holder); + } + if (i == nextLimit) { advanceProgress(chunkSize); nextLimit = i + chunkSize; @@ -466,21 +551,35 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP } UIUtil.invokeLaterIfNeeded(new Runnable() { public void run() { - if (myProject.isDisposed()) return; + if (progress.isCanceled()) return; UpdateHighlightersUtil.addHighlighterToEditorIncrementally(myProject, myDocument, myFile, 0, myDocument.getTextLength(), info, Pass.UPDATE_ALL); } }); } } advanceProgress(elements.size() - (nextLimit-chunkSize)); + if (elements == elements1) after1.run(); } - }; - if (!visitor.analyze(action, myUpdateAll, myFile)) { + } + }; + + analyzeByVisitors(progress, visitors, action, 0); + } + + private void analyzeByVisitors(final ProgressIndicator progress, final HighlightVisitor[] visitors, final Runnable action, final int i) { + if (i == visitors.length) { + action.run(); + } + else { + if (!visitors[i].analyze(new Runnable() { + @Override + public void run() { + analyzeByVisitors(progress, visitors, action, i+1); + } + }, myUpdateAll, myFile)) { cancelAndRestartDaemonLater(progress, myProject, this); } } - - return gotHighlights; } private static HighlightVisitor[] filterVisitors(HighlightVisitor[] highlightVisitors, final PsiFile file) { @@ -529,17 +628,19 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP return new HighlightInfoHolder(file, filters); } - private static Collection highlightTodos(@NotNull PsiFile file, - @NotNull CharSequence text, - int startOffset, - int endOffset, - @NotNull ProgressIndicator progress) { + private static void highlightTodos(@NotNull PsiFile file, + @NotNull CharSequence text, + int startOffset, + int endOffset, + @NotNull ProgressIndicator progress, + @NotNull ProperTextRange priorityRange, + @NotNull Collection result, + @NotNull Collection outsideResult) { PsiManager psiManager = file.getManager(); PsiSearchHelper helper = psiManager.getSearchHelper(); TodoItem[] todoItems = helper.findTodoItems(file, startOffset, endOffset); - if (todoItems.length == 0) return Collections.emptyList(); + if (todoItems.length == 0) return; - List list = new ArrayList(todoItems.length); for (TodoItem todoItem : todoItems) { progress.checkCanceled(); TextRange range = todoItem.getTextRange(); @@ -547,9 +648,13 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP TextAttributes attributes = todoItem.getPattern().getAttributes().getTextAttributes(); HighlightInfo info = HighlightInfo.createHighlightInfo(HighlightInfoType.TODO, range, description, description, attributes); assert info != null; - list.add(info); + if (priorityRange.containsRange(info.getStartOffset(), info.getEndOffset())) { + result.add(info); + } + else { + outsideResult.add(info); + } } - return list; } private void reportErrorsToWolf() { @@ -576,7 +681,8 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP return myUpdateAll ? super.getProgress() : -1; } - private static List convertToProblems(final Collection infos, final VirtualFile file, + private static List convertToProblems(final Collection infos, + final VirtualFile file, final boolean hasErrorElement) { List problems = new SmartList(); for (HighlightInfo info : infos) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/GeneralHighlightingPassFactory.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/GeneralHighlightingPassFactory.java index 4e9b7c5c7967..444c10e90f93 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/GeneralHighlightingPassFactory.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/GeneralHighlightingPassFactory.java @@ -23,6 +23,7 @@ import com.intellij.codeHighlighting.TextEditorHighlightingPassRegistrar; import com.intellij.openapi.components.AbstractProjectComponent; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.ProperTextRange; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiFile; import org.jetbrains.annotations.NonNls; @@ -51,7 +52,7 @@ public class GeneralHighlightingPassFactory extends AbstractProjectComponent imp TextRange textRange = FileStatusMap.getDirtyTextRange(editor, Pass.UPDATE_ALL); if (textRange == null) return new ProgressableTextEditorHighlightingPass.EmptyPass(myProject, editor.getDocument() ); - TextRange visibleRange = VisibleHighlightingPassFactory.calculateVisibleRange(editor); - return new GeneralHighlightingPass(myProject, file, editor.getDocument(), textRange.getStartOffset(), textRange.getEndOffset(), true, visibleRange); + ProperTextRange visibleRange = VisibleHighlightingPassFactory.calculateVisibleRange(editor); + return new GeneralHighlightingPass(myProject, file, editor.getDocument(), textRange.getStartOffset(), textRange.getEndOffset(), true, visibleRange, editor); } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/UpdateHighlightersUtil.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/UpdateHighlightersUtil.java index 03ae1d3aadba..185de4885ce6 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/UpdateHighlightersUtil.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/UpdateHighlightersUtil.java @@ -38,10 +38,7 @@ import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.TextEditor; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.*; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; @@ -94,14 +91,8 @@ public class UpdateHighlightersUtil { return o1.getStartOffset() - o2.getStartOffset(); } }; - private static final Comparator BY_START_OFFSET_OR_CONTAINS = new Comparator() { - public int compare(final TextRange o1, final TextRange o2) { - if (o1.contains(o2) || o2.contains(o1)) return 0; - return o1.getStartOffset() - o2.getStartOffset(); - } - }; - private static void cleanFileLevelHighlights(@NotNull Project project, final int group, PsiFile psiFile) { + public static void cleanFileLevelHighlights(@NotNull Project project, final int group, PsiFile psiFile) { if (psiFile == null || !psiFile.getViewProvider().isPhysical()) return; VirtualFile vFile = psiFile.getViewProvider().getVirtualFile(); final FileEditorManager manager = FileEditorManager.getInstance(project); @@ -191,11 +182,10 @@ public class UpdateHighlightersUtil { return; } - boolean success = createOrReuseHighlighterFor(info, document, group, file, (MarkupModelEx)markup, null, null, startOffset, endOffset, - SeverityRegistrar.getInstance(project)); - if (!success) { - return; - } + if (info.getStartOffset() < startOffset || info.getEndOffset() > endOffset) return; + + createOrReuseHighlighterFor(info, document, group, file, (MarkupModelEx)markup, null, null, + SeverityRegistrar.getInstance(project)); DaemonCodeAnalyzerImpl.addHighlight(markup, project, info); clearWhiteSpaceOptimizationFlag(document); @@ -241,7 +231,24 @@ public class UpdateHighlightersUtil { } } - private static void setHighlightersInRange(final TextRange range, + // set highlights inside startOffset,endOffset but outside range + public static void setHighlightersToEditorOutsideRange(@NotNull Project project, + @NotNull Document document, + @NotNull Collection infos, + int startOffset, int endOffset, @NotNull ProperTextRange range, + final int group) { + ApplicationManager.getApplication().assertIsDispatchThread(); + + PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document); + cleanFileLevelHighlights(project, group, psiFile); + + MarkupModel markup = document.getMarkupModel(project); + assertMarkupConsistent(markup, project); + + setHighlightersOutsideRange(startOffset, endOffset, range, infos, (MarkupModelEx)markup, group, document, project); + } + + public static void setHighlightersInRange(final TextRange range, Collection highlightsCo, final MarkupModelEx markup, final int group, @@ -293,10 +300,88 @@ public class UpdateHighlightersUtil { if (isWarningCoveredByError(info, overlappingIntervals, severityRegistrar)) { return true; } - boolean success = createOrReuseHighlighterFor(info, document, group, psiFile, markup, infosToRemove, - ranges2markersCache, range.getStartOffset(), range.getEndOffset(), + if (info.getStartOffset() >= range.getStartOffset() && info.getEndOffset() <= range.getEndOffset()) { + createOrReuseHighlighterFor(info, document, group, psiFile, markup, infosToRemove, + ranges2markersCache, + severityRegistrar); + changed[0] = true; + } + return true; + } + }); + for (RangeHighlighter highlighter : infosToRemove.forAllInGarbageBin()) { + markup.removeHighlighter(highlighter); + changed[0] = true; + } + + if (changed[0]) { + clearWhiteSpaceOptimizationFlag(document); + } + assertMarkupConsistent(markup, project); + } + + private static void setHighlightersOutsideRange(final int startOffset, final int endOffset, final TextRange range, + Collection highlightsCo, + final MarkupModelEx markup, + final int group, + final Document document, + final Project project) { + final List highlights = new ArrayList(highlightsCo); + + final SeverityRegistrar severityRegistrar = SeverityRegistrar.getInstance(project); + final HighlightersRecycler infosToRemove = new HighlightersRecycler(); + DaemonCodeAnalyzerImpl.processHighlights(document, project, null, startOffset, endOffset, new Processor() { + @Override + public boolean process(HighlightInfo info) { + if (info.group == group) { + RangeHighlighter highlighter = info.highlighter; + int endOffset = highlighter.getEndOffset(); + int startOffset = highlighter.getStartOffset(); + boolean willBeRemoved = endOffset == document.getTextLength() && range.getEndOffset() != document.getTextLength() + || !range.contains(startOffset) + && !range.containsRange(startOffset, endOffset); + if (willBeRemoved) { + infosToRemove.recycleHighlighter(highlighter); + info.highlighter = null; + } + } + return true; + } + }); + + Collections.sort(highlights, BY_START_OFFSET_NODUPS); + final Map ranges2markersCache = new THashMap(10); + final PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document); + final boolean[] changed = {false}; + RangeMarkerTree.sweep(new RangeMarkerTree.Generator(){ + @Override + public boolean generate(final Processor processor) { + return ContainerUtil.process(highlights, new Processor() { + @Override + public boolean process(HighlightInfo info) { + return info.getStartOffset() < startOffset || info.getEndOffset() > endOffset || processor.process(info); + } + }); + } + }, new MarkupModelEx.SweepProcessor() { + @Override + public boolean process(int offset, HighlightInfo info, boolean atStart, Collection overlappingIntervals) { + if (!atStart) { + return true; + } + if (info.isFileLevelAnnotation && psiFile != null && psiFile.getViewProvider().isPhysical()) { + addFileLevelHighlight(project, group, info, psiFile); + changed[0] = true; + return true; + } + if (isWarningCoveredByError(info, overlappingIntervals, severityRegistrar)) { + return true; + } + if (new TextRange(startOffset,endOffset).containsRange(info.getStartOffset(), info.getEndOffset()) && + (info.getStartOffset() < range.getStartOffset() || info.getEndOffset() > range.getEndOffset())) { + createOrReuseHighlighterFor(info, document, group, psiFile, markup, infosToRemove, + ranges2markersCache, severityRegistrar); - if (success) { changed[0] = true; } return true; @@ -332,19 +417,16 @@ public class UpdateHighlightersUtil { } // return true if changed - private static boolean createOrReuseHighlighterFor(@NotNull final HighlightInfo info, - @NotNull final Document document, - final int group, - @NotNull final PsiFile psiFile, - @NotNull MarkupModelEx markup, - @Nullable HighlightersRecycler infosToRemove, - @Nullable final Map ranges2markersCache, - int rangeStartOffset, - int rangeEndOffset, - SeverityRegistrar severityRegistrar) { + private static void createOrReuseHighlighterFor(@NotNull final HighlightInfo info, + @NotNull final Document document, + final int group, + @NotNull final PsiFile psiFile, + @NotNull MarkupModelEx markup, + @Nullable HighlightersRecycler infosToRemove, + @Nullable final Map ranges2markersCache, + SeverityRegistrar severityRegistrar) { final int infoStartOffset = info.startOffset; int infoEndOffset = info.endOffset; - if (infoStartOffset < rangeStartOffset || infoEndOffset > rangeEndOffset) return false; if (infoEndOffset == infoStartOffset && !info.isAfterEndOfLine) { infoEndOffset++; //show something in case of empty highlightinfo @@ -398,7 +480,6 @@ public class UpdateHighlightersUtil { info.getTextAttributes(psiFile) + "; highlighter:" + highlighter.getTextAttributes(); - return true; } @NotNull diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/VisibleHighlightingPassFactory.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/VisibleHighlightingPassFactory.java index 83ce78d6afb3..1b2a01232de7 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/VisibleHighlightingPassFactory.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/VisibleHighlightingPassFactory.java @@ -24,6 +24,7 @@ import com.intellij.openapi.components.AbstractProjectComponent; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.ProperTextRange; import com.intellij.openapi.util.TextRange; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -36,7 +37,7 @@ public abstract class VisibleHighlightingPassFactory extends AbstractProjectComp } @NotNull - public static TextRange calculateVisibleRange(@NotNull Editor editor) { + public static ProperTextRange calculateVisibleRange(@NotNull Editor editor) { Rectangle rect = editor.getScrollingModel().getVisibleArea(); LogicalPosition startPosition = editor.xyToLogicalPosition(new Point(rect.x, rect.y)); @@ -45,7 +46,7 @@ public abstract class VisibleHighlightingPassFactory extends AbstractProjectComp int visibleEnd = editor.logicalPositionToOffset(new LogicalPosition(endPosition.line + 1, 0)); - return new TextRange(visibleStart, visibleEnd); + return new ProperTextRange(visibleStart, visibleEnd); } @Nullable