diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPass.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPass.java index 4179614848b6..935ea843ee62 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPass.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPass.java @@ -46,7 +46,6 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.roots.ProjectRootManager; @@ -75,7 +74,6 @@ import gnu.trove.TObjectIntHashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.PropertyKey; -import org.jetbrains.annotations.TestOnly; import java.util.ArrayList; import java.util.Collection; @@ -104,12 +102,12 @@ public class PostHighlightingPass extends TextEditorHighlightingPass { private HighlightDisplayKey myDeadCodeKey; private HighlightInfoType myDeadCodeInfoType; - private PostHighlightingPass(@NotNull Project project, - @NotNull PsiFile file, - @Nullable Editor editor, - @NotNull Document document, - int startOffset, - int endOffset) { + PostHighlightingPass(@NotNull Project project, + @NotNull PsiFile file, + @Nullable Editor editor, + @NotNull Document document, + int startOffset, + int endOffset) { super(project, document, true); myFile = file; myEditor = editor; @@ -122,14 +120,6 @@ public class PostHighlightingPass extends TextEditorHighlightingPass { myImplicitUsageProviders = Extensions.getExtensions(ImplicitUsageProvider.EP_NAME); } - PostHighlightingPass(@NotNull Project project, @NotNull PsiFile file, @NotNull Editor editor, int startOffset, int endOffset) { - this(project, file, editor, editor.getDocument(), startOffset, endOffset); - } - - public PostHighlightingPass(@NotNull Project project, @NotNull PsiFile file, @NotNull Document document, int startOffset, int endOffset) { - this(project, file, null, document, startOffset, endOffset); - } - public void doCollectInformation(final ProgressIndicator progress) { DaemonCodeAnalyzer daemonCodeAnalyzer = DaemonCodeAnalyzer.getInstance(myProject); final FileStatusMap fileStatusMap = ((DaemonCodeAnalyzerImpl)daemonCodeAnalyzer).getFileStatusMap(); @@ -174,19 +164,23 @@ public class PostHighlightingPass extends TextEditorHighlightingPass { DaemonCodeAnalyzer daemonCodeAnalyzer = DaemonCodeAnalyzer.getInstance(myProject); ((DaemonCodeAnalyzerImpl)daemonCodeAnalyzer).getFileStatusMap().markFileUpToDate(myDocument, myFile, getId()); - if (timeToOptimizeImports() && myEditor != null) { - optimizeImportsOnTheFly(); + Editor editor = myEditor; + if (editor != null && timeToOptimizeImports()) { + optimizeImportsOnTheFly(editor); } } - private void optimizeImportsOnTheFly() { + private void optimizeImportsOnTheFly(@NotNull final Editor editor) { if (myHasRedundantImports || myHasMissortedImports) { invokeOnTheFlyImportOptimizer(new Runnable() { public void run() { + if (myProject.isDisposed() || editor.isDisposed()) { + return; + } OptimizeImportsFix optimizeImportsFix = new OptimizeImportsFix(); - if (optimizeImportsFix.isAvailable(myProject, myEditor, myFile) && myFile.isWritable()) { + if (optimizeImportsFix.isAvailable(myProject, editor, myFile) && myFile.isWritable()) { PsiDocumentManager.getInstance(myProject).commitAllDocuments(); - optimizeImportsFix.invoke(myProject, myEditor, myFile); + optimizeImportsFix.invoke(myProject, editor, myFile); } } }); @@ -205,11 +199,6 @@ public class PostHighlightingPass extends TextEditorHighlightingPass { }); } - @TestOnly - public Collection getHighlights() { - return myHighlights; - } - private void collectHighlights(@NotNull Collection elements, @NotNull final List result, @NotNull ProgressIndicator progress) throws ProcessCanceledException { ApplicationManager.getApplication().assertReadAccessAllowed(); @@ -251,7 +240,7 @@ public class PostHighlightingPass extends TextEditorHighlightingPass { else if (unusedImportEnabled && element instanceof PsiImportList) { final PsiImportStatementBase[] imports = ((PsiImportList)element).getAllImportStatements(); for (PsiImportStatementBase statement : imports) { - ProgressManager.checkCanceled(); + progress.checkCanceled(); final HighlightInfo info = processImport(statement, unusedImportKey); if (info != null) { result.add(info); @@ -266,28 +255,23 @@ public class PostHighlightingPass extends TextEditorHighlightingPass { if (InspectionManagerEx.inspectionResultSuppressed(identifier, myUnusedSymbolInspection)) return null; PsiElement parent = identifier.getParent(); if (PsiUtilBase.hasErrorElementChild(parent)) return null; - HighlightInfo info; if (parent instanceof PsiLocalVariable && myUnusedSymbolInspection.LOCAL_VARIABLE) { - info = processLocalVariable((PsiLocalVariable)parent, progress); + return processLocalVariable((PsiLocalVariable)parent, progress); } - else if (parent instanceof PsiField && myUnusedSymbolInspection.FIELD) { - final PsiField psiField = (PsiField)parent; - info = processField(psiField, identifier, progress); + if (parent instanceof PsiField && myUnusedSymbolInspection.FIELD) { + return processField((PsiField)parent, identifier, progress); } - else if (parent instanceof PsiParameter && myUnusedSymbolInspection.PARAMETER) { - info = processParameter((PsiParameter)parent, progress); + if (parent instanceof PsiParameter && myUnusedSymbolInspection.PARAMETER) { + return processParameter((PsiParameter)parent, progress); } - else if (parent instanceof PsiMethod && myUnusedSymbolInspection.METHOD) { - info = processMethod((PsiMethod)parent, progress); + if (parent instanceof PsiMethod && myUnusedSymbolInspection.METHOD) { + return processMethod((PsiMethod)parent, progress); } - else if (parent instanceof PsiClass && identifier.equals(((PsiClass)parent).getNameIdentifier()) && myUnusedSymbolInspection.CLASS) { - info = processClass((PsiClass)parent, progress); + if (parent instanceof PsiClass && myUnusedSymbolInspection.CLASS) { + return processClass((PsiClass)parent, progress); } - else { - return null; - } - return info; + return null; } @@ -486,8 +470,11 @@ public class PostHighlightingPass extends TextEditorHighlightingPass { } else { //class maybe used in some weird way, e.g. from XML, therefore the only constructor is used too - if (containingClass != null && method.isConstructor() && containingClass.getConstructors().length == 1 && isClassUnused(containingClass, - progress) == USED) return null; + if (containingClass != null && method.isConstructor() + && containingClass.getConstructors().length == 1 + && isClassUnused(containingClass, progress) == USED) { + return null; + } if (isImplicitUsage(method, progress)) return null; if (method.findSuperMethods().length != 0) { @@ -691,20 +678,19 @@ public class PostHighlightingPass extends TextEditorHighlightingPass { } private boolean containsErrorsPreventingOptimize(PsiFile file) { - List errors = DaemonCodeAnalyzerImpl.getHighlights(myDocument, HighlightSeverity.ERROR, myProject); - // ignore unresolved imports errors PsiImportList importList = ((PsiJavaFile)file).getImportList(); - if (importList != null) { - TextRange importsRange = importList.getTextRange(); - for (HighlightInfo error : errors) { - if (!error.type.equals(HighlightInfoType.WRONG_REF)) return true; - if (!importsRange.contains(error.getActualStartOffset()) || !importsRange.contains(error.getActualEndOffset())) { - return true; - } + final TextRange importsRange = importList == null ? new TextRange(0,0) : importList.getTextRange(); + boolean hasErrorsExceptUnresolvedImports = !DaemonCodeAnalyzerImpl.processHighlights(myDocument, myProject, HighlightSeverity.ERROR, 0, myDocument.getTextLength(), new Processor() { + public boolean process(HighlightInfo error) { + int infoStart = error.getActualStartOffset(); + int infoEnd = error.getActualEndOffset(); + + return importsRange.containsRange(infoStart,infoEnd) && error.type.equals(HighlightInfoType.WRONG_REF); } - } - return false; + }); + + return hasErrorsExceptUnresolvedImports; } private static boolean isIntentionalPrivateConstructor(PsiMethod method, PsiClass containingClass) { diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPassFactory.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPassFactory.java index bab38bfcd5dd..69e3382faba7 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPassFactory.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPassFactory.java @@ -50,6 +50,6 @@ public class PostHighlightingPassFactory extends AbstractProjectComponent implem int startOffset = 0; int endOffset = editor.getDocument().getTextLength(); - return new PostHighlightingPass(myProject, file, editor, startOffset, endOffset); + return new PostHighlightingPass(myProject, file, editor, editor.getDocument(), startOffset, endOffset); } } diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateMethodFromUsageFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateMethodFromUsageFix.java index 3ada412925fa..7473fff624ce 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateMethodFromUsageFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateMethodFromUsageFix.java @@ -31,11 +31,13 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.refactoring.util.FieldConflictsResolver; import com.intellij.util.IncorrectOperationException; +import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -88,11 +90,15 @@ public class CreateMethodFromUsageFix extends CreateFromUsageBaseFix { if (document == null) return true; PsiExpressionList argumentList = call.getArgumentList(); - List errorsInArgList = DaemonCodeAnalyzerImpl.getHighlights(document, HighlightSeverity.ERROR, project, + final TextRange argRange = argumentList.getTextRange(); + return !DaemonCodeAnalyzerImpl.processHighlights(document, project, HighlightSeverity.ERROR, //strictly inside arg list - argumentList.getTextRange().getStartOffset()+1, - argumentList.getTextRange().getEndOffset()-1); - return !errorsInArgList.isEmpty(); + argRange.getStartOffset()+1, + argRange.getEndOffset()-1, new Processor() { + public boolean process(HighlightInfo info) { + return !(info.getActualStartOffset() > argRange.getStartOffset() && info.getActualEndOffset() < argRange.getEndOffset()); + } + }); } protected PsiElement getElement() { diff --git a/java/java-impl/src/com/intellij/openapi/vcs/impl/CodeSmellDetectorImpl.java b/java/java-impl/src/com/intellij/openapi/vcs/impl/CodeSmellDetectorImpl.java index 49a23aad1604..63c8437dbc36 100644 --- a/java/java-impl/src/com/intellij/openapi/vcs/impl/CodeSmellDetectorImpl.java +++ b/java/java-impl/src/com/intellij/openapi/vcs/impl/CodeSmellDetectorImpl.java @@ -16,8 +16,12 @@ package com.intellij.openapi.vcs.impl; import com.intellij.codeInsight.CodeSmellInfo; +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; import com.intellij.codeInsight.daemon.HighlightDisplayKey; -import com.intellij.codeInsight.daemon.impl.*; +import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl; +import com.intellij.codeInsight.daemon.impl.HighlightInfo; +import com.intellij.codeInsight.daemon.impl.HighlightInfoType; +import com.intellij.codeInsight.daemon.impl.SeverityRegistrar; import com.intellij.ide.errorTreeView.NewErrorTreeViewPanel; import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.openapi.application.ApplicationManager; @@ -37,6 +41,7 @@ import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.util.ui.MessageCategory; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; @@ -131,24 +136,12 @@ public class CodeSmellDetectorImpl extends CodeSmellDetector { return result; } - private List findCodeSmells(final PsiFile psiFile, final ProgressIndicator progress, final Document document) { - + private List findCodeSmells(@NotNull PsiFile psiFile, final ProgressIndicator progress, @NotNull Document document) { final List result = new ArrayList(); - GeneralHighlightingPass action1 = new GeneralHighlightingPass(myProject, psiFile, document, 0, psiFile.getTextLength(), true); - action1.doCollectInformation(progress); - - collectErrorsAndWarnings(action1.getHighlights(), result, document); - - PostHighlightingPass action2 = new PostHighlightingPass(myProject, psiFile, document, 0, psiFile.getTextLength()); - action2.doCollectInformation(progress); - - collectErrorsAndWarnings(action2.getHighlights(), result, document); - - LocalInspectionsPass action3 = new LocalInspectionsPass(psiFile, document, 0, psiFile.getTextLength()); - action3.doCollectInformation(progress); - - collectErrorsAndWarnings(action3.getHighlights(), result, document); + DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(myProject); + List infos = codeAnalyzer.runMainPasses(psiFile, document, progress); + collectErrorsAndWarnings(infos, result, document); return result; 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 adb1fdd49dbf..69d663eeb995 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/ImportHelperTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/ImportHelperTest.java @@ -135,7 +135,7 @@ public class ImportHelperTest extends DaemonAnalyzerTestCase { } protected Sdk getTestProjectJdk() { - return JavaSdkImpl.getMockJdk17("mock 1.5"); + return JavaSdkImpl.getMockJdk17(); } private void checkAddImport(PsiJavaFile file, String fqn, String... expectedOrder) { @@ -313,9 +313,7 @@ public class ImportHelperTest extends DaemonAnalyzerTestCase { assertEquals(1, ((PsiJavaFile)getFile()).getImportList().getAllImportStatements().length); type("/* */"); - errs = filter(doHighlighting(), HighlightSeverity.ERROR); - assertEquals(1, errs.size()); - + doHighlighting(); UIUtil.dispatchAllInvocationEvents(); assertEmpty(((PsiJavaFile)getFile()).getImportList().getAllImportStatements()); diff --git a/java/testFramework/src/com/intellij/codeInsight/daemon/DaemonAnalyzerTestCase.java b/java/testFramework/src/com/intellij/codeInsight/daemon/DaemonAnalyzerTestCase.java index 5d5943dbe922..2def1664c8a7 100644 --- a/java/testFramework/src/com/intellij/codeInsight/daemon/DaemonAnalyzerTestCase.java +++ b/java/testFramework/src/com/intellij/codeInsight/daemon/DaemonAnalyzerTestCase.java @@ -290,7 +290,6 @@ public abstract class DaemonAnalyzerTestCase extends CodeInsightTestCase { toIgnore.add(Pass.POPUP_HINTS); toIgnore.add(Pass.POST_UPDATE_ALL); toIgnore.add(Pass.UPDATE_ALL); - toIgnore.add(Pass.UPDATE_VISIBLE); toIgnore.add(Pass.UPDATE_OVERRIDEN_MARKERS); toIgnore.add(Pass.VISIBLE_LINE_MARKERS); } diff --git a/platform/lang-impl/src/com/intellij/codeHighlighting/Pass.java b/platform/lang-impl/src/com/intellij/codeHighlighting/Pass.java index 7de606e5c05b..b43a35dbe5ca 100644 --- a/platform/lang-impl/src/com/intellij/codeHighlighting/Pass.java +++ b/platform/lang-impl/src/com/intellij/codeHighlighting/Pass.java @@ -19,6 +19,7 @@ package com.intellij.codeHighlighting; public interface Pass { int UPDATE_FOLDING = 1; + @Deprecated /** there is no visible highlighting pass anymore, use {@link #UPDATE_ALL} instead */ int UPDATE_VISIBLE = 2; int POPUP_HINTS = 3; int UPDATE_ALL = 4; diff --git a/platform/lang-impl/src/com/intellij/codeHighlighting/TextEditorHighlightingPassRegistrar.java b/platform/lang-impl/src/com/intellij/codeHighlighting/TextEditorHighlightingPassRegistrar.java index 1f43e12309f1..f6b311336dd5 100644 --- a/platform/lang-impl/src/com/intellij/codeHighlighting/TextEditorHighlightingPassRegistrar.java +++ b/platform/lang-impl/src/com/intellij/codeHighlighting/TextEditorHighlightingPassRegistrar.java @@ -86,7 +86,7 @@ public abstract class TextEditorHighlightingPassRegistrar { //todo ids = new int[]{Pass.UPDATE_ALL, Pass.UPDATE_FOLDING, Pass.UPDATE_OVERRIDEN_MARKERS, - Pass.UPDATE_VISIBLE, Pass.EXTERNAL_TOOLS, + Pass.EXTERNAL_TOOLS, Pass.LOCAL_INSPECTIONS, Pass.POPUP_HINTS, Pass.POST_UPDATE_ALL}; break; } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java index 029bf5729575..c19fb0674d33 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java @@ -34,7 +34,10 @@ 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.ex.DocumentEx; import com.intellij.openapi.editor.ex.EditorMarkupModel; +import com.intellij.openapi.editor.ex.MarkupModelEx; +import com.intellij.openapi.editor.ex.RangeHighlighterEx; import com.intellij.openapi.editor.markup.MarkupModel; import com.intellij.openapi.editor.markup.RangeHighlighter; import com.intellij.openapi.extensions.Extensions; @@ -44,6 +47,7 @@ import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.StdFileTypes; import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.*; import com.intellij.openapi.vfs.VirtualFile; @@ -57,8 +61,11 @@ import com.intellij.psi.search.scope.packageSet.NamedScope; import com.intellij.psi.search.scope.packageSet.NamedScopeManager; import com.intellij.psi.search.scope.packageSet.NamedScopesHolder; import com.intellij.util.Alarm; +import com.intellij.util.CommonProcessors; +import com.intellij.util.Processor; import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.UIUtil; import gnu.trove.THashMap; import gnu.trove.THashSet; import org.jdom.Element; @@ -67,6 +74,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; +import javax.swing.*; import java.util.*; /** @@ -75,37 +83,37 @@ import java.util.*; public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzer implements JDOMExternalizable { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl"); - private static final Key> HIGHLIGHTS_IN_EDITOR_DOCUMENT_KEY = Key.create("HIGHLIGHTS_IN_EDITOR_DOCUMENT"); - private static final Key> MARKERS_IN_EDITOR_DOCUMENT_KEY = Key.create("MARKERS_IN_EDITOR_DOCUMENT"); + //private static final Key> HIGHLIGHTS_IN_EDITOR_DOCUMENT_KEY = Key.create("HIGHLIGHTS_IN_EDITOR_DOCUMENT"); + private static final Key> HIGHLIGHTS_TO_REMOVE_KEY = Key.create("HIGHLIGHTS_TO_REMOVE"); + private static final Key> MARKERS_IN_EDITOR_DOCUMENT_KEY = Key.create("MARKERS_IN_EDITOR_DOCUMENT"); private final Project myProject; private final DaemonCodeAnalyzerSettings mySettings; private final EditorTracker myEditorTracker; private DaemonProgressIndicator myUpdateProgress = new DaemonProgressIndicator(); //guarded by this + private DaemonProgressIndicator myUpdateVisibleProgress = new DaemonProgressIndicator(); //guarded by this private final Runnable myUpdateRunnable = createUpdateRunnable(); private final Alarm myAlarm = new Alarm(); - private boolean myUpdateByTimerEnabled = true; private final Collection myDisabledHintsFiles = new THashSet(); private final Collection myDisabledHighlightingFiles = new THashSet(); + private final FileStatusMap myFileStatusMap; - private DaemonCodeAnalyzerSettings myLastSettings; + private IntentionHintComponent myLastIntentionHint; //guarded by this - - private boolean myDisposed; + private volatile boolean myDisposed; private boolean myInitialized; - @NonNls private static final String DISABLE_HINTS_TAG = "disable_hints"; + @NonNls private static final String DISABLE_HINTS_TAG = "disable_hints"; @NonNls private static final String FILE_TAG = "file"; @NonNls private static final String URL_ATT = "url"; private DaemonListeners myDaemonListeners; private StatusBarUpdater myStatusBarUpdater; private final PassExecutorService myPassExecutorService; - private static final Key> HIGHLIGHTS_TO_REMOVE_KEY = Key.create("HIGHLIGHTS_TO_REMOVE"); private int myModificationCount = 0; public DaemonCodeAnalyzerImpl(Project project, DaemonCodeAnalyzerSettings daemonCodeAnalyzerSettings, EditorTracker editorTracker) { @@ -135,6 +143,87 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzer implements JDOMEx Disposer.register(project, myFileStatusMap); } + static boolean hasErrors(Project project, Document document) { + return !processHighlights(document, project, HighlightSeverity.ERROR, 0, document.getTextLength(), CommonProcessors.alwaysFalse()); + } + + @NotNull + @TestOnly + public static List getHighlights(Document document, HighlightSeverity minSeverity, Project project) { + List infos = new ArrayList(); + processHighlights(document, project, minSeverity, 0, document.getTextLength(), new CommonProcessors.CollectProcessor(infos)); + return infos; + } + + public List runMainPasses(@NotNull PsiFile psiFile, + @NotNull Document document, + @NotNull final ProgressIndicator progress) { + GeneralHighlightingPass action1 = new GeneralHighlightingPass(myProject, psiFile, document, 0, psiFile.getTextLength(), true); + action1.doCollectInformation(progress); + + List result = new ArrayList(); + result.addAll(action1.getHighlights()); + + LocalInspectionsPass action3 = new LocalInspectionsPass(psiFile, document, 0, psiFile.getTextLength()); + action3.doCollectInformation(progress); + + result.addAll(action3.getHighlights()); + + return result; + } + + @TestOnly + public List runPasses(@NotNull PsiFile file, + @NotNull Document document, + @NotNull TextEditor textEditor, + @NotNull final ProgressIndicator progress, + @NotNull int[] toIgnore, + boolean allowDirt, + final boolean apply) { + // pump first so that queued event do not interfere + if (SwingUtilities.isEventDispatchThread()) { + UIUtil.dispatchAllInvocationEvents(); + } + else { + UIUtil.pump(); + } + + Project project = file.getProject(); + setUpdateByTimerEnabled(false); + FileStatusMap fileStatusMap = getFileStatusMap(); + for (int ignoreId : toIgnore) { + fileStatusMap.markFileUpToDate(document, file, ignoreId); + } + fileStatusMap.allowDirt(allowDirt); + try { + TextEditorBackgroundHighlighter highlighter = (TextEditorBackgroundHighlighter)textEditor.getBackgroundHighlighter(); + final List passes = highlighter.getPasses(toIgnore); + ProgressManager.getInstance().runProcess(new Runnable() { + public void run() { + for (TextEditorHighlightingPass pass : passes) { + pass.collectInformation(progress); + if (apply) { + // apply incremental highlights scheduled for AWT thread + if (SwingUtilities.isEventDispatchThread()) { + UIUtil.dispatchAllInvocationEvents(); + } + else { + UIUtil.pump(); + } + pass.applyInformationToEditor(); + } + } + } + }, progress); + + return getHighlights(document, null, project); + } + finally { + fileStatusMap.allowDirt(true); + myPassExecutorService.cancelAll(true); + } + } + @NotNull public String getComponentName() { return "DaemonCodeAnalyzer"; @@ -222,6 +311,8 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzer implements JDOMEx public void updateVisibleHighlighters(@NotNull Editor editor) { ApplicationManager.getApplication().assertIsDispatchThread(); + if (true) return; // no need, will not work anyway + if (!myUpdateByTimerEnabled) return; if (editor instanceof EditorWindow) editor = ((EditorWindow)editor).getDelegate(); @@ -380,76 +471,66 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzer implements JDOMEx return myUpdateProgress; } - @Nullable - public static List getHighlights(Document document, Project project) { + public static boolean processHighlights(@NotNull Document document, + @NotNull Project project, + @Nullable("null means all") final HighlightSeverity minSeverity, + final int startOffset, + final int endOffset, + @NotNull final Processor processor) { LOG.assertTrue(ApplicationManager.getApplication().isReadAccessAllowed()); - MarkupModel markup = document.getMarkupModel(project); - return getHighlights(markup); - } - static List getHighlights(MarkupModel markup) { - return markup.getUserData(HIGHLIGHTS_IN_EDITOR_DOCUMENT_KEY); - } - - @NotNull - public static List getHighlights(Document document, HighlightSeverity minSeverity, Project project) { - return getHighlights(document, minSeverity, project, Integer.MIN_VALUE, Integer.MAX_VALUE); - } - - @NotNull - public static List getHighlights(Document document, HighlightSeverity minSeverity, Project project, int startOffset, int endOffset) { - LOG.assertTrue(ApplicationManager.getApplication().isReadAccessAllowed()); - List highlights = getHighlights(document, project); - if (highlights == null) return Collections.emptyList(); - List array = new ArrayList(); - final SeverityRegistrar instance = SeverityRegistrar.getInstance(project); - - for (HighlightInfo info : highlights) { - if (instance.compare(info.getSeverity(), minSeverity) >= 0 && - info.startOffset >= startOffset && - info.endOffset <= endOffset) { - array.add(info); + final SeverityRegistrar severityRegistrar = SeverityRegistrar.getInstance(project); + MarkupModelEx model = (MarkupModelEx)((DocumentEx)document).getMarkupModel(project); + return model.processHighlightsOverlappingWith(startOffset, endOffset, new Processor() { + public boolean process(RangeHighlighterEx marker) { + Object tt = marker.getErrorStripeTooltip(); + if (!(tt instanceof HighlightInfo)) return true; + HighlightInfo info = (HighlightInfo)tt; + return minSeverity != null && severityRegistrar.compare(info.getSeverity(), minSeverity) < 0 || processor.process(info); } - } - return array; + }); } - @NotNull - public static List getHighlightsAround(Document document, Project project, int offset) { - LOG.assertTrue(ApplicationManager.getApplication().isReadAccessAllowed()); - List highlights = getHighlights(document, project); - if (highlights == null) return Collections.emptyList(); - List array = new ArrayList(); - for (HighlightInfo info : highlights) { - if (isOffsetInsideHighlightInfo(offset, info, true)) { - array.add(info); - } - } - return array; - } + public static boolean processHighlightsNearOffset(@NotNull Document document, + @NotNull Project project, + @NotNull final HighlightSeverity minSeverity, + final int offset, + final boolean includeFixRange, + @NotNull final Processor processor) { + return processHighlights(document, project, null, 0, document.getTextLength(), new Processor() { + public boolean process(HighlightInfo info) { + if (!isOffsetInsideHighlightInfo(offset, info, includeFixRange)) return true; - @Nullable - public HighlightInfo findHighlightByOffset(Document document, int offset, boolean includeFixRange) { - List highlights = getHighlights(document, myProject); - if (highlights == null) return null; - - List foundInfoList = new SmartList(); - for (HighlightInfo info : highlights) { - if (!isOffsetInsideHighlightInfo(offset, info, includeFixRange)) continue; - - if (!foundInfoList.isEmpty()) { - HighlightInfo foundInfo = foundInfoList.get(0); - int compare = foundInfo.getSeverity().compareTo(info.getSeverity()); + int compare = info.getSeverity().compareTo(minSeverity); if (compare < 0) { - foundInfoList.clear(); - } - else if (compare > 0) { - continue; + return true; } + + return processor.process(info); } - foundInfoList.add(info); - } + }); + } + + @Nullable + public HighlightInfo findHighlightByOffset(Document document, final int offset, final boolean includeFixRange) { + final List foundInfoList = new SmartList(); + processHighlightsNearOffset(document, myProject, HighlightSeverity.INFORMATION, offset, includeFixRange, new Processor() { + public boolean process(HighlightInfo info) { + if (!foundInfoList.isEmpty()) { + HighlightInfo foundInfo = foundInfoList.get(0); + int compare = foundInfo.getSeverity().compareTo(info.getSeverity()); + if (compare < 0) { + foundInfoList.clear(); + } + else if (compare > 0) { + return true; + } + } + foundInfoList.add(info); + return true; + } + }); if (foundInfoList.isEmpty()) return null; if (foundInfoList.size() == 1) return foundInfoList.get(0); @@ -457,9 +538,10 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzer implements JDOMEx } private static boolean isOffsetInsideHighlightInfo(int offset, HighlightInfo info, boolean includeFixRange) { - if (info.highlighter == null || !info.highlighter.isValid()) return false; - int startOffset = info.highlighter.getStartOffset(); - int endOffset = info.highlighter.getEndOffset(); + RangeHighlighterEx highlighter = info.highlighter; + if (highlighter == null || !highlighter.isValid()) return false; + int startOffset = highlighter.getStartOffset(); + int endOffset = highlighter.getEndOffset(); if (startOffset > offset || offset > endOffset) { if (!includeFixRange) return false; if (info.fixMarker == null || !info.fixMarker.isValid()) return false; @@ -470,47 +552,106 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzer implements JDOMEx return true; } - static void setHighlights(MarkupModel markup, Project project, List highlightsToSet, List highlightsToRemove) { + static void addHighlight(MarkupModel markup, + Project project, + HighlightInfo toAdd) { ApplicationManager.getApplication().assertIsDispatchThread(); - stripWarningsCoveredByErrors(project, highlightsToSet, markup); - markup.putUserData(HIGHLIGHTS_IN_EDITOR_DOCUMENT_KEY, Collections.unmodifiableList(highlightsToSet)); - markup.putUserData(HIGHLIGHTS_TO_REMOVE_KEY, Collections.unmodifiableList(highlightsToRemove)); - - DaemonCodeAnalyzer codeAnalyzer = DaemonCodeAnalyzer.getInstance(project); - if (codeAnalyzer instanceof DaemonCodeAnalyzerImpl && ((DaemonCodeAnalyzerImpl)codeAnalyzer).myStatusBarUpdater != null) { - ((DaemonCodeAnalyzerImpl)codeAnalyzer).myStatusBarUpdater.updateStatus(); - } + stripWarningsCoveredByErrors(project, markup, toAdd); + + //DaemonCodeAnalyzer codeAnalyzer = DaemonCodeAnalyzer.getInstance(project); + //if (codeAnalyzer instanceof DaemonCodeAnalyzerImpl && ((DaemonCodeAnalyzerImpl)codeAnalyzer).myStatusBarUpdater != null) { + // ((DaemonCodeAnalyzerImpl)codeAnalyzer).myStatusBarUpdater.updateStatus(); + //} + } - private static void stripWarningsCoveredByErrors(final Project project, List highlights, MarkupModel markup) { + private static void stripWarningsCoveredByErrors(Project project, MarkupModel markup, final HighlightInfo toAdd) { final SeverityRegistrar severityRegistrar = SeverityRegistrar.getInstance(project); - Collection errors = new ArrayList(); - for (HighlightInfo highlight : highlights) { - if (severityRegistrar.compare(highlight.getSeverity(), HighlightSeverity.ERROR) >= 0) { - errors.add(highlight); - } - } + final Set covered = new THashSet(); - for (Iterator it = highlights.iterator(); it.hasNext();) { - HighlightInfo highlight = it.next(); - if (severityRegistrar.compare(HighlightSeverity.ERROR, highlight.getSeverity()) > 0 && highlight.getSeverity().myVal > 0) { - for (HighlightInfo errorInfo : errors) { - if (isCoveredBy(highlight, errorInfo)) { - it.remove(); - RangeHighlighter highlighter = highlight.highlighter; - if (highlighter != null) { - markup.removeHighlighter(highlighter); - } - break; + // either toAdd is warning and covered by one of errors in highlightsToSet or toAdd is an error and covers warnings in highlightsToSet or it is OK + final boolean addingError = severityRegistrar.compare(HighlightSeverity.ERROR, toAdd.getSeverity()) <= 0; + boolean toAddIsVisible = processHighlights(markup.getDocument(), project, null, toAdd.getActualStartOffset(), + toAdd.getActualEndOffset(), new Processor() { + public boolean process(HighlightInfo interval) { + boolean isError = severityRegistrar.compare(HighlightSeverity.ERROR, interval.getSeverity()) <= 0; + if (addingError && !isError && isCoveredBy(interval, toAdd)) { + covered.add(interval); } + return addingError || !isError || !isCoveredBy(toAdd, interval); } + }); + if (toAddIsVisible) { + // ok + //highlightsToSet.add(toAdd); + } + else { + // toAdd is covered by + markup.removeHighlighter(toAdd.highlighter); + } + for (HighlightInfo warning : covered) { + RangeHighlighter highlighter = warning.highlighter; + if (highlighter != null) { + markup.removeHighlighter(highlighter); } } } - private static boolean isCoveredBy(HighlightInfo info, HighlightInfo coveredBy) { - return info.startOffset >= coveredBy.startOffset && info.endOffset <= coveredBy.endOffset && info.getGutterIconRenderer() == null; + //private static void stripWarningsCoveredByErrors(final Project project, IntervalTree highlights, MarkupModel markup) { + // final SeverityRegistrar severityRegistrar = SeverityRegistrar.getInstance(project); + // + // final Set currentErrors = new THashSet(); + // final Set covered = new THashSet(); + // + // + // final PriorityQueue ends = new PriorityQueue(1, new Comparator() { + // public int compare(HighlightInfo o1, HighlightInfo o2) { + // return o1.getActualEndOffset() - o2.getActualEndOffset(); + // } + // }); + // highlights.process(new Processor() { + // public boolean process(HighlightInfo info) { + // int startOffset = info.getActualStartOffset(); + // + // for (HighlightInfo nearestEnd = ends.peek(); nearestEnd != null && nearestEnd.getActualEndOffset() <= startOffset; nearestEnd = ends.peek()) { + // currentErrors.remove(nearestEnd); + // Interval t = ends.poll(); + // assert t == nearestEnd; + // } + // + // boolean isError = severityRegistrar.compare(HighlightSeverity.ERROR, info.getSeverity()) <= 0; + // if (isError) { + // currentErrors.add(info); + // } + // else { + // if (info.getSeverity().myVal > 0) { + // // warning, check for everlaps + // for (HighlightInfo error : currentErrors) { + // if (isCoveredBy(info, error)) { + // covered.add(info); + // } + // } + // } + // } + // ends.add(info); + // + // return true; + // } + // }); + // + // for (HighlightInfo warning : covered) { + // highlights.remove(warning); + // + // RangeHighlighter highlighter = warning.highlighter; + // if (highlighter != null) { + // markup.removeHighlighter(highlighter); + // } + // } + //} + + static boolean isCoveredBy(HighlightInfo info, HighlightInfo coveredBy) { + return coveredBy.startOffset <= info.startOffset && info.endOffset <= coveredBy.endOffset && info.getGutterIconRenderer() == null; } @Nullable @@ -633,9 +774,4 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzer implements JDOMEx public static List getFileLevelHighlights(Project project,PsiFile file ) { return UpdateHighlightersUtil.getFileLeveleHighlights(project, file); } - - @TestOnly - public void clearPasses() { - myPassExecutorService.cancelAll(true); - } } 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 new file mode 100644 index 000000000000..e0084d7fcefc --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/Divider.java @@ -0,0 +1,126 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInsight.daemon.impl; + +import com.intellij.codeInsight.daemon.impl.analysis.HighlightLevelUtil; +import com.intellij.lang.Language; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.FileViewProvider; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.util.containers.Stack; +import gnu.trove.TIntStack; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * User: cdr + */ +public class Divider { + private static final int STARTING_TREE_HEIGHT = 10; + + public static void getInsideAndOutside(@NotNull PsiFile file, + int startOffset, + int endOffset, + @NotNull TextRange range, + @NotNull List inside, + @NotNull List outside, + @NotNull HighlightLevelUtil.AnalysisLevel level) { + final FileViewProvider viewProvider = file.getViewProvider(); + for (Language language : viewProvider.getLanguages()) { + final PsiFile psiRoot = viewProvider.getPsi(language); + if (HighlightLevelUtil.shouldAnalyse(psiRoot, level)) { + getInsideAndOutside(psiRoot, startOffset, endOffset, range, inside, outside); + } + } + } + + private static void getInsideAndOutside(@NotNull PsiFile root, + int startOffset, + int endOffset, + @NotNull TextRange range, + @NotNull List inside, + @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); + final Stack elements = new Stack(STARTING_TREE_HEIGHT); + final Stack children = new Stack(STARTING_TREE_HEIGHT); + PsiElement element = root; + + PsiElement child = PsiUtilBase.NULL_PSI_ELEMENT; + while (true) { + ProgressManager.checkCanceled(); + + for (Condition filter : filters) { + if (!filter.value(element)) { + assert child == PsiUtilBase.NULL_PSI_ELEMENT; + child = null; // do not want to process children + break; + } + } + + boolean startChildrenVisiting; + if (child == PsiUtilBase.NULL_PSI_ELEMENT) { + startChildrenVisiting = true; + child = element.getFirstChild(); + } + else { + startChildrenVisiting = false; + } + + if (child == null) { + if (startChildrenVisiting) { + // leaf element + offset += element.getTextLength(); + } + + if (elements.isEmpty()) break; + int start = starts.pop(); + if (startOffset <= start && offset <= endOffset) { + if (range.containsRange(start, offset)) { + inside.add(element); + } + else { + outside.add(element); + } + } + + element = elements.pop(); + child = children.pop(); + } + else { + // composite element + if (offset > endOffset) break; + children.push(child.getNextSibling()); + starts.push(offset); + elements.push(element); + element = child; + 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 3cd90369920a..a24a44a49926 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 @@ -57,6 +57,7 @@ import com.intellij.psi.search.TodoItem; import com.intellij.psi.tree.IElementType; import com.intellij.util.Processor; import com.intellij.util.SmartList; +import com.intellij.util.ui.UIUtil; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; @@ -75,6 +76,7 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP private final int myStartOffset; private final int myEndOffset; private final boolean myUpdateAll; + private final TextRange myPriorityRange; private volatile Collection myHighlights = Collections.emptyList(); private final Map> myInjectedPsiHighlights = new HashMap>(); @@ -86,18 +88,28 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP return o1.order() - o2.order(); } }; - private volatile UserDataHolder myProgress; public GeneralHighlightingPass(@NotNull Project project, @NotNull PsiFile file, @NotNull Document document, int startOffset, int endOffset, - boolean updateAll) { + boolean updateAll + ) { + this(project, file, document, startOffset, endOffset, updateAll, new TextRange(0,0)); + } + public GeneralHighlightingPass(@NotNull Project project, + @NotNull PsiFile file, + @NotNull Document document, + int startOffset, + int endOffset, + boolean updateAll, + @NotNull TextRange priorityRange) { super(project, document, IN_PROGRESS_ICON, PRESENTABLE_NAME, file, true); myStartOffset = startOffset; myEndOffset = endOffset; myUpdateAll = updateAll; + myPriorityRange = priorityRange; LOG.assertTrue(file.isValid()); setId(Pass.UPDATE_ALL); @@ -134,37 +146,24 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP } protected void collectInformationWithProgress(final ProgressIndicator progress) { - myProgress = (UserDataHolder)progress; final Collection result = new THashSet(100); DaemonCodeAnalyzer daemonCodeAnalyzer = DaemonCodeAnalyzer.getInstance(myProject); FileStatusMap fileStatusMap = ((DaemonCodeAnalyzerImpl)daemonCodeAnalyzer).getFileStatusMap(); HighlightVisitor[] highlightVisitors = createHighlightVisitors(); - HighlightVisitor[] filtered = filterVisitors(highlightVisitors, myFile); + HighlightVisitor[] filteredVisitors = filterVisitors(highlightVisitors, myFile); + List inside = new ArrayList(); + List outside = new ArrayList(); try { - final FileViewProvider viewProvider = myFile.getViewProvider(); - final Set relevantLanguages = viewProvider.getLanguages(); - List elements = null; - for (Language language : relevantLanguages) { - PsiElement psiRoot = viewProvider.getPsi(language); - if (!HighlightLevelUtil.shouldHighlight(psiRoot)) continue; - List underRoot = CollectHighlightsUtil.getElementsInRange(psiRoot, myStartOffset, myEndOffset); - if (elements == null) { - elements = underRoot; - } - else { - elements.addAll(underRoot); - } - if (underRoot.isEmpty()) { - elements.add(psiRoot); - } - } - if (elements != null) { - result.addAll(collectHighlights(elements, progress, filtered)); - if (!addInjectedPsiHighlights(elements, progress)) throw new ProcessCanceledException(); - } + Divider.getInsideAndOutside(myFile, myStartOffset, myEndOffset, myPriorityRange, inside, outside, HighlightLevelUtil.AnalysisLevel.HIGHLIGHT); + + List elements = inside; + elements.addAll(outside); + + result.addAll(collectHighlights(elements, progress, filteredVisitors)); + if (!addInjectedPsiHighlights(elements, progress)) throw new ProcessCanceledException(); if (!isDumbMode()) { - result.addAll(highlightTodos(myFile, myDocument.getCharsSequence(), myStartOffset, myEndOffset, progress)); + result.addAll(highlightTodos(myFile, myDocument.getCharsSequence(), 0, myFile.getTextLength(), progress)); } if (myUpdateAll) { @@ -383,43 +382,27 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP Color fore = attributes.getForegroundColor() == null ? globalScheme.getDefaultForeground() : attributes.getForegroundColor(); forcedAttributes = new TextAttributes(fore, back, attributes.getEffectColor(), attributes.getEffectType(), attributes.getFontType()); } - + HighlightInfo info = HighlightInfo.createHighlightInfo(HighlightInfoType.INJECTED_LANGUAGE_FRAGMENT, annRange, null,null,forcedAttributes); holder.add(info); } } private boolean isWholeFileHighlighting() { - return myUpdateAll && myStartOffset == 0 && myEndOffset == myFile.getTextLength(); + return myUpdateAll && myStartOffset == 0 && myEndOffset == myDocument.getTextLength(); } - private static final Key> UPDATE_ALL_FINISHED = Key.create("UPDATE_ALL_FINISHED"); protected void applyInformationWithProgress() { myFile.putUserData(HAS_ERROR_ELEMENT, myHasErrorElement); - // highlights from both passes should be in the same layer - TextRange range = new TextRange(myStartOffset, myEndOffset); - Collection collection = myInjectedPsiHighlights.get(range); - if (collection == null) { - collection = new ArrayList(myHighlights.size()); - } - collection.addAll(myHighlights); - myInjectedPsiHighlights.put(range, collection); - myHighlights = Collections.emptyList(); - - List updateAllFinished = myProgress.getUserData(UPDATE_ALL_FINISHED); - if (updateAllFinished == null || !updateAllFinished.contains(this)) { - // prevent situation when visible pass finished after updateAll pass and tries to wipe out its results - UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument, myInjectedPsiHighlights, Pass.UPDATE_ALL); - - if (myUpdateAll) { - if (updateAllFinished == null) { - updateAllFinished = new ArrayList(); - myProgress.putUserData(UPDATE_ALL_FINISHED, updateAllFinished); - } - updateAllFinished.add(this); - } + 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); if (myUpdateAll) { reportErrorsToWolf(); @@ -427,7 +410,7 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP } @NotNull - public Collection getHighlights() { + Collection getHighlights() { ArrayList list = new ArrayList(myHighlights); for (Collection infos : myInjectedPsiHighlights.values()) { list.addAll(infos); @@ -472,7 +455,7 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP //noinspection ForLoopReplaceableByForEach for (int j = 0; j < holder.size(); j++) { - HighlightInfo info = holder.get(j); + final HighlightInfo info = holder.get(j); assert info != null; // have to filter out already obtained highlights if (!gotHighlights.add(info)) continue; @@ -483,6 +466,12 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP } myErrorFound = true; } + UIUtil.invokeLaterIfNeeded(new Runnable() { + public void run() { + if (myProject.isDisposed()) return; + UpdateHighlightersUtil.addHighlighterToEditorIncrementally(myProject, myDocument, myFile, 0, myDocument.getTextLength(), info, Pass.UPDATE_ALL); + } + }); } } advanceProgress(elements.size() - (nextLimit-chunkSize)); @@ -575,8 +564,8 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP List problems = convertToProblems(getHighlights(), file, myHasErrorElement); WolfTheProblemSolver wolf = WolfTheProblemSolver.getInstance(project); - List errors = DaemonCodeAnalyzerImpl.getHighlights(getDocument(), HighlightSeverity.ERROR, project); - if (errors.isEmpty() || isWholeFileHighlighting()) { + boolean hasErrors = DaemonCodeAnalyzerImpl.hasErrors(project, getDocument()); + if (!hasErrors || isWholeFileHighlighting()) { wolf.reportProblems(file, problems); } else { 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 612ab9a0bba5..57e6d6de47db 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 @@ -36,7 +36,7 @@ public class GeneralHighlightingPassFactory extends AbstractProjectComponent imp public GeneralHighlightingPassFactory(Project project, TextEditorHighlightingPassRegistrar highlightingPassRegistrar) { super(project); highlightingPassRegistrar.registerTextEditorHighlightingPass(this, - new int[]{Pass.UPDATE_VISIBLE}, + null, new int[]{Pass.UPDATE_FOLDING}, false, Pass.UPDATE_ALL); } @@ -51,6 +51,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(), GeneralHighlightingPass.IN_PROGRESS_ICON, GeneralHighlightingPass.PRESENTABLE_NAME); - return new GeneralHighlightingPass(myProject, file, editor.getDocument(), textRange.getStartOffset(), textRange.getEndOffset(), true); + TextRange visibleRange = VisibleHighlightingPassFactory.calculateVisibleRange(editor); + return new GeneralHighlightingPass(myProject, file, editor.getDocument(), textRange.getStartOffset(), textRange.getEndOffset(), true, visibleRange); } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/GotoNextErrorHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/GotoNextErrorHandler.java index 2264afda4903..4d06d552ff97 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/GotoNextErrorHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/GotoNextErrorHandler.java @@ -30,10 +30,8 @@ import com.intellij.openapi.editor.ScrollingModel; import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiFile; +import com.intellij.util.Processor; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; public class GotoNextErrorHandler implements CodeInsightActionHandler { private final boolean myGoForward; @@ -53,60 +51,59 @@ public class GotoNextErrorHandler implements CodeInsightActionHandler { private void gotoNextError(Project project, Editor editor, PsiFile file, int caretOffset) { final SeverityRegistrar severityRegistrar = SeverityRegistrar.getInstance(project); - List highlights = DaemonCodeAnalyzerImpl.getHighlights(editor.getDocument(), severityRegistrar.getSeverityByIndex(0), project); - if (highlights.isEmpty()){ - showMessageWhenNoHighlights(project, file, editor); - return; - } DaemonCodeAnalyzerSettings settings = DaemonCodeAnalyzerSettings.getInstance(); - if (settings.NEXT_ERROR_ACTION_GOES_TO_ERRORS_FIRST) { - for (int idx = severityRegistrar.getSeveritiesCount() - 1; idx >= 0; idx--) { - final HighlightSeverity minSeverity = severityRegistrar.getSeverityByIndex(idx); - List errors = DaemonCodeAnalyzerImpl.getHighlights(editor.getDocument(), minSeverity, project); - if (!errors.isEmpty()) { - highlights = errors; - break; - } + int maxSeverity = settings.NEXT_ERROR_ACTION_GOES_TO_ERRORS_FIRST ? severityRegistrar.getSeveritiesCount() - 1 : 0; + + for (int idx = maxSeverity; idx >= 0; idx--) { + final HighlightSeverity minSeverity = severityRegistrar.getSeverityByIndex(idx); + HighlightInfo infoToGo = findInfo(project, editor, caretOffset, minSeverity); + if (infoToGo != null) { + navigateToError(project, editor, infoToGo); + return; } } - - HighlightInfo infoToGo = findInfoToGo(editor, caretOffset, highlights, true); - if (infoToGo == null) { - infoToGo = findInfoToGo(editor, caretOffset, highlights, false); - } - if (infoToGo != null) { - navigateToError(project, editor, infoToGo); - } + showMessageWhenNoHighlights(project, file, editor); } - @Nullable - private HighlightInfo findInfoToGo(Editor editor, int caretOffset, List highlights, boolean skip) { - int offsetToGo = myGoForward ? Integer.MAX_VALUE : Integer.MIN_VALUE; - int offsetToGoIfNoLuck = offsetToGo; - HighlightInfo infoToGo = null; - HighlightInfo infoToGoIfNoLuck = null; - int caretOffsetIfNoLuck = myGoForward ? -1 : editor.getDocument().getTextLength(); - for (HighlightInfo info : highlights) { - if (skip) { - if (SeverityRegistrar.skipSeverity(info.getSeverity())) continue; - } - int startOffset = getNavigationPositionFor(info, editor.getDocument()); - if (isBetter(caretOffset, offsetToGo, startOffset)) { - offsetToGo = startOffset; - infoToGo = info; - } - if (isBetter(caretOffsetIfNoLuck, offsetToGoIfNoLuck, startOffset)) { - offsetToGoIfNoLuck = startOffset; - infoToGoIfNoLuck = info; + private HighlightInfo findInfo(Project project, Editor editor, final int caretOffset, HighlightSeverity minSeverity) { + final Document document = editor.getDocument(); + final HighlightInfo[][] infoToGo = new HighlightInfo[2][2]; //HighlightInfo[luck-noluck][skip-noskip] + final int caretOffsetIfNoLuck = myGoForward ? -1 : document.getTextLength(); + + DaemonCodeAnalyzerImpl.processHighlights(document, project, minSeverity, 0, document.getTextLength(), new Processor() { + public boolean process(HighlightInfo info) { + int startOffset = getNavigationPositionFor(info, document); + if (SeverityRegistrar.isGotoBySeverityEnabled(info.getSeverity())) { + infoToGo[0][0] = getBetterInfoThan(infoToGo[0][0], caretOffset, startOffset, info); + infoToGo[1][0] = getBetterInfoThan(infoToGo[1][0], caretOffsetIfNoLuck, startOffset, info); + } + infoToGo[0][1] = getBetterInfoThan(infoToGo[0][1], caretOffset, startOffset, info); + infoToGo[1][1] = getBetterInfoThan(infoToGo[1][1], caretOffsetIfNoLuck, startOffset, info); + return true; } + }); + if (infoToGo[0][0] == null) infoToGo[0][0] = infoToGo[1][0]; + if (infoToGo[0][1] == null) infoToGo[0][1] = infoToGo[1][1]; + if (infoToGo[0][0] == null) infoToGo[0][0] = infoToGo[0][1]; + return infoToGo[0][0]; + } + + private HighlightInfo getBetterInfoThan(HighlightInfo infoToGo, int caretOffset, int startOffset, HighlightInfo info) { + if (isBetterThan(infoToGo, caretOffset, startOffset)) { + infoToGo = info; } - if (infoToGo == null) infoToGo = infoToGoIfNoLuck; return infoToGo; } - private boolean isBetter(int caretOffset, int offsetToGo, int startOffset) { - return myGoForward ? startOffset > caretOffset && startOffset < offsetToGo - : startOffset < caretOffset && startOffset > offsetToGo; + private boolean isBetterThan(HighlightInfo oldInfo, int caretOffset, int newOffset) { + if (oldInfo == null) return true; + int oldOffset = getNavigationPositionFor(oldInfo, oldInfo.highlighter.getDocument()); + if (myGoForward) { + return caretOffset < oldOffset != caretOffset < newOffset ? caretOffset < newOffset : newOffset < oldOffset; + } + else { + return caretOffset <= oldOffset != caretOffset <= newOffset ? caretOffset > newOffset : newOffset > oldOffset; + } } static void showMessageWhenNoHighlights(Project project, PsiFile file, Editor editor) { @@ -121,7 +118,7 @@ public class GotoNextErrorHandler implements CodeInsightActionHandler { int oldOffset = editor.getCaretModel().getOffset(); final int offset = getNavigationPositionFor(info, editor.getDocument()); - final int endOffset = info.highlighter.getEndOffset(); + final int endOffset = info.getActualEndOffset(); final ScrollingModel scrollingModel = editor.getScrollingModel(); if (offset != oldOffset) { @@ -146,12 +143,12 @@ public class GotoNextErrorHandler implements CodeInsightActionHandler { } private static int getNavigationPositionFor(HighlightInfo info, Document document) { - int start = info.highlighter.getStartOffset(); + int start = info.getActualStartOffset(); if (start >= document.getTextLength()) return document.getTextLength(); char c = document.getCharsSequence().charAt(start); int shift = info.isAfterEndOfLine && c != '\n' ? 1 : info.navigationShift; - int offset = info.highlighter.getStartOffset() + shift; + int offset = info.getActualStartOffset() + shift; return Math.min(offset, document.getTextLength()); } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfo.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfo.java index fe02a5a7bf62..81d40165de4d 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfo.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfo.java @@ -40,6 +40,7 @@ import com.intellij.openapi.editor.markup.GutterIconRenderer; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Segment; import com.intellij.openapi.util.TextRange; import com.intellij.profile.codeInspection.InspectionProjectProfileManager; import com.intellij.psi.PsiElement; @@ -55,17 +56,11 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; -import java.util.Comparator; import java.util.List; -public class HighlightInfo { +public class HighlightInfo implements Segment { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.HighlightInfo"); - public static final Comparator BY_START_OFFSET = new Comparator() { - public int compare(HighlightInfo o1, HighlightInfo o2) { - return o1.startOffset - o2.startOffset; - } - }; public static final HighlightInfo[] EMPTY_ARRAY = new HighlightInfo[0]; private final boolean myNeedsUpdateOnTyping; public JComponent fileLevelComponent; @@ -400,10 +395,15 @@ public class HighlightInfo { } public IntentionActionDescriptor(@NotNull IntentionAction action, final List options, final String displayName, Icon icon) { + this(action, options, displayName, icon, null); + } + + public IntentionActionDescriptor(@NotNull IntentionAction action, final List options, final String displayName, Icon icon, HighlightDisplayKey key) { myAction = action; myOptions = options; myDisplayName = displayName; myIcon = icon; + myKey = key; } @NotNull @@ -469,4 +469,14 @@ public class HighlightInfo { return myIcon; } } + + @Override + public int getStartOffset() { + return getActualStartOffset(); + } + + @Override + public int getEndOffset() { + return getActualEndOffset(); + } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IdentifierHighlighterPass.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IdentifierHighlighterPass.java index d83f41429e3f..86deb95a8e35 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IdentifierHighlighterPass.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IdentifierHighlighterPass.java @@ -143,8 +143,6 @@ public class IdentifierHighlighterPass extends TextEditorHighlightingPass { public static void clearMyHighlights(Document document, Project project) { MarkupModel markupModel = document.getMarkupModel(project); - List old = DaemonCodeAnalyzerImpl.getHighlights(document, project); - List result = new ArrayList(old == null ? Collections.emptyList() : old); for (RangeHighlighter highlighter : markupModel.getAllHighlighters()) { Object tooltip = highlighter.getErrorStripeTooltip(); if (!(tooltip instanceof HighlightInfo)) { @@ -152,10 +150,8 @@ public class IdentifierHighlighterPass extends TextEditorHighlightingPass { } HighlightInfo info = (HighlightInfo)tooltip; if (info.type == ourReadHighlightInfoType || info.type == ourWriteHighlightInfoType) { - result.remove(info); markupModel.removeHighlighter(highlighter); } } - DaemonCodeAnalyzerImpl.setHighlights(markupModel, project, result, Collections.emptyList()); } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IdentifierHighlighterPassFactory.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IdentifierHighlighterPassFactory.java index 33a14383e27f..3201849c4779 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IdentifierHighlighterPassFactory.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IdentifierHighlighterPassFactory.java @@ -33,7 +33,7 @@ import org.jetbrains.annotations.NotNull; public class IdentifierHighlighterPassFactory extends AbstractProjectComponent implements TextEditorHighlightingPassFactory { public IdentifierHighlighterPassFactory(Project project, TextEditorHighlightingPassRegistrar highlightingPassRegistrar) { super(project); - highlightingPassRegistrar.registerTextEditorHighlightingPass(this, new int[]{Pass.UPDATE_VISIBLE}, null, false, -1); + highlightingPassRegistrar.registerTextEditorHighlightingPass(this, new int[]{Pass.UPDATE_ALL}, null, false, -1); } public TextEditorHighlightingPass createHighlightingPass(@NotNull final PsiFile file, @NotNull final Editor editor) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPass.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPass.java index b236ddca9314..7a474ae19089 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPass.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPass.java @@ -46,14 +46,17 @@ import com.intellij.openapi.progress.util.ProgressWrapper; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.Trinity; import com.intellij.openapi.util.text.StringUtil; import com.intellij.profile.codeInspection.InspectionProjectProfileManager; import com.intellij.psi.*; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; +import com.intellij.util.ConcurrencyUtil; import com.intellij.util.Processor; -import com.intellij.util.SmartList; -import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.ConcurrentHashMap; +import com.intellij.util.ui.UIUtil; import com.intellij.xml.util.XmlStringUtil; import gnu.trove.THashMap; import gnu.trove.THashSet; @@ -63,6 +66,9 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.*; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicBoolean; /** * @author max @@ -71,10 +77,8 @@ public class LocalInspectionsPass extends ProgressableTextEditorHighlightingPass private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.LocalInspectionsPass"); private final int myStartOffset; private final int myEndOffset; - @NotNull private List myDescriptors = Collections.emptyList(); - @NotNull private List myLevels = Collections.emptyList(); - @NotNull private List myTools = Collections.emptyList(); - @NotNull private List myInjectedPsiInspectionResults = Collections.emptyList(); + private final TextRange myPriorityRange; + private final ConcurrentMap> result = new ConcurrentHashMap>(); static final String PRESENTABLE_NAME = DaemonBundle.message("pass.inspection"); private volatile List myInfos = Collections.emptyList(); static final Icon IN_PROGRESS_ICON = IconLoader.getIcon("/general/inspectionInProgress.png"); @@ -84,9 +88,13 @@ public class LocalInspectionsPass extends ProgressableTextEditorHighlightingPass private boolean myFailFastOnAcquireReadAction; public LocalInspectionsPass(@NotNull PsiFile file, @Nullable Document document, int startOffset, int endOffset) { + this(file, document, startOffset, endOffset, new TextRange(0, 0)); + } + public LocalInspectionsPass(@NotNull PsiFile file, @Nullable Document document, int startOffset, int endOffset, @NotNull TextRange priorityRange) { super(file.getProject(), document, IN_PROGRESS_ICON, PRESENTABLE_NAME, file, true); myStartOffset = startOffset; myEndOffset = endOffset; + myPriorityRange = priorityRange; setId(Pass.LOCAL_INSPECTIONS); final KeymapManager keymapManager = KeymapManager.getInstance(); @@ -103,79 +111,75 @@ public class LocalInspectionsPass extends ProgressableTextEditorHighlightingPass } protected void collectInformationWithProgress(final ProgressIndicator progress) { - myDescriptors = new ArrayList(); - myLevels = new ArrayList(); - myTools = new ArrayList(); if (!HighlightLevelUtil.shouldInspect(myFile)) return; final InspectionManagerEx iManager = (InspectionManagerEx)InspectionManager.getInstance(myProject); final InspectionProfileWrapper profile = myProfileWrapper; final List tools = DumbService.getInstance(myProject).filterByDumbAwareness(getInspectionTools(profile)); - inspect(tools, iManager, true, true, true); + inspect(tools, iManager, true, true, true, progress); } public void doInspectInBatch(final InspectionManagerEx iManager, List toolWrappers, boolean ignoreSuppressed) { - myDescriptors = new ArrayList(); - myLevels = new ArrayList(); - myTools = new ArrayList(); - Map tool2Wrapper = new THashMap(toolWrappers.size()); for (InspectionProfileEntry toolWrapper : toolWrappers) { tool2Wrapper.put(((LocalInspectionToolWrapper)toolWrapper).getTool(), (LocalInspectionToolWrapper)toolWrapper); } List tools = new ArrayList(tool2Wrapper.keySet()); - inspect(tools, iManager, false, ignoreSuppressed, false); - addDescriptorsFromInjectedResults(tool2Wrapper, iManager); - for (int i = 0; i < myTools.size(); i++) { - final LocalInspectionTool tool = myTools.get(i); - ProblemDescriptor descriptor = myDescriptors.get(i); - LocalInspectionToolWrapper toolWrapper = tool2Wrapper.get(tool); - toolWrapper.addProblemDescriptors(Collections.singletonList(descriptor), true); + ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); + inspect(tools, iManager, false, ignoreSuppressed, false, progress); + addDescriptorsFromInjectedResults(tool2Wrapper, iManager); + List resultList = result.get(myFile); + if (resultList == null) return; + for (InspectionResult inspectionResult : resultList) { + LocalInspectionTool tool = inspectionResult.tool; + LocalInspectionToolWrapper toolWrapper = tool2Wrapper.get(tool); + for (ProblemDescriptor descriptor : inspectionResult.foundProblems) { + toolWrapper.addProblemDescriptors(Collections.singletonList(descriptor), ignoreSuppressed); + } } } - private void addDescriptorsFromInjectedResults(Map tool2Wrapper, - InspectionManagerEx iManager) { + private void addDescriptorsFromInjectedResults(Map tool2Wrapper, InspectionManagerEx iManager) { Set emptyActionRegistered = new THashSet(); InspectionProfile inspectionProfile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile(); InjectedLanguageManager ilManager = InjectedLanguageManager.getInstance(myProject); PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); - //noinspection ForLoopReplaceableByForEach - for (int i = 0; i < myInjectedPsiInspectionResults.size(); i++) { - InjectedPsiInspectionResult result = myInjectedPsiInspectionResults.get(i); - LocalInspectionTool tool = result.tool; - HighlightSeverity severity = inspectionProfile.getErrorLevel(HighlightDisplayKey.find(tool.getShortName()), myFile).getSeverity(); - PsiElement injectedPsi = result.injectedPsi; - DocumentWindow documentRange = (DocumentWindow)documentManager.getDocument((PsiFile)injectedPsi); - if (documentRange == null) continue; - //noinspection ForLoopReplaceableByForEach - for (int j = 0; j < result.foundProblems.size(); j++) { - ProblemDescriptor descriptor = result.foundProblems.get(j); - PsiElement psiElement = descriptor.getPsiElement(); - if (InspectionManagerEx.inspectionResultSuppressed(psiElement, tool)) continue; - HighlightInfoType level = highlightTypeFromDescriptor(descriptor, severity); - HighlightInfo info = createHighlightInfo(descriptor, tool, level,emptyActionRegistered); - if (info == null) continue; - List editables = ilManager.intersectWithAllEditableFragments((PsiFile)injectedPsi, new TextRange(info.startOffset, info.endOffset)); - for (TextRange editable : editables) { - TextRange hostRange = documentRange.injectedToHost(editable); - QuickFix[] fixes = descriptor.getFixes(); - LocalQuickFix[] localFixes = null; - if (fixes != null) { - localFixes = new LocalQuickFix[fixes.length]; - for (int k = 0; k < fixes.length; k++) { - QuickFix fix = fixes[k]; - localFixes[k] = (LocalQuickFix)fix; + for (Map.Entry> entry : result.entrySet()) { + PsiFile file = entry.getKey(); + if (file == myFile) continue; // not injected + DocumentWindow documentRange = (DocumentWindow)documentManager.getDocument(file); + List resultList = entry.getValue(); + for (InspectionResult inspectionResult : resultList) { + LocalInspectionTool tool = inspectionResult.tool; + HighlightSeverity severity = inspectionProfile.getErrorLevel(HighlightDisplayKey.find(tool.getShortName()), myFile).getSeverity(); + for (ProblemDescriptor descriptor : inspectionResult.foundProblems) { + + PsiElement psiElement = descriptor.getPsiElement(); + if (InspectionManagerEx.inspectionResultSuppressed(psiElement, tool)) continue; + HighlightInfoType level = highlightTypeFromDescriptor(descriptor, severity); + HighlightInfo info = createHighlightInfo(descriptor, tool, level,emptyActionRegistered); + if (info == null) continue; + List editables = ilManager.intersectWithAllEditableFragments(file, new TextRange(info.startOffset, info.endOffset)); + for (TextRange editable : editables) { + TextRange hostRange = documentRange.injectedToHost(editable); + QuickFix[] fixes = descriptor.getFixes(); + LocalQuickFix[] localFixes = null; + if (fixes != null) { + localFixes = new LocalQuickFix[fixes.length]; + for (int k = 0; k < fixes.length; k++) { + QuickFix fix = fixes[k]; + localFixes[k] = (LocalQuickFix)fix; + } } + ProblemDescriptor patchedDescriptor = iManager.createProblemDescriptor(myFile, hostRange, descriptor.getDescriptionTemplate(), + descriptor.getHighlightType(), true, localFixes); + LocalInspectionToolWrapper toolWrapper = tool2Wrapper.get(tool); + toolWrapper.addProblemDescriptors(Collections.singletonList(patchedDescriptor), true); } - ProblemDescriptor patchedDescriptor = iManager.createProblemDescriptor(myFile, hostRange, descriptor.getDescriptionTemplate(), - descriptor.getHighlightType(), true, localFixes); - LocalInspectionToolWrapper toolWrapper = tool2Wrapper.get(tool); - toolWrapper.addProblemDescriptors(Collections.singletonList(patchedDescriptor), true); } - } + } } } @@ -183,16 +187,36 @@ public class LocalInspectionsPass extends ProgressableTextEditorHighlightingPass final InspectionManagerEx iManager, final boolean isOnTheFly, final boolean ignoreSuppressed, - boolean failFastOnAcquireReadAction) { + boolean failFastOnAcquireReadAction, + @NotNull final ProgressIndicator indicator) { myFailFastOnAcquireReadAction = failFastOnAcquireReadAction; if (tools.isEmpty()) return; - final PsiElement[] elements = getElementsIntersectingRange(myFile, myStartOffset, myEndOffset); - setProgressLimit(1L * tools.size() * elements.length); + ArrayList inside = new ArrayList(); + ArrayList outside = new ArrayList(); + Divider.getInsideAndOutside(myFile, myStartOffset, myEndOffset, myPriorityRange, inside, outside, HighlightLevelUtil.AnalysisLevel.HIGHLIGHT_AND_INSPECT); + + setProgressLimit(1L * tools.size() * (inside.size() + outside.size())); final LocalInspectionToolSession session = new LocalInspectionToolSession(myFile, myStartOffset, myEndOffset); - final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); - LOG.assertTrue(indicator != null); + List> init = new ArrayList>(); + visitPriorityElementsAndInit(tools, iManager, isOnTheFly, ignoreSuppressed, indicator, inside, session, init); + visitRestElementsAndCleanup(tools,isOnTheFly,ignoreSuppressed, indicator, outside, session, init); + + indicator.checkCanceled(); + + myInfos = new ArrayList(); + addHighlightsFromResults(myInfos); + } + + private void visitPriorityElementsAndInit(List tools, + final InspectionManagerEx iManager, + final boolean isOnTheFly, + final boolean ignoreSuppressed, + final ProgressIndicator indicator, + final List elements, + final LocalInspectionToolSession session, + final List> init) { boolean result = JobUtil.invokeConcurrentlyUnderMyProgress(tools, new Processor() { public boolean process(final LocalInspectionTool tool) { final ProgressManager progressManager = ProgressManager.getInstance(); @@ -204,8 +228,17 @@ public class LocalInspectionsPass extends ProgressableTextEditorHighlightingPass ApplicationManager.getApplication().assertReadAccessAllowed(); - ProblemsHolder holder = new ProblemsHolder(iManager, myFile, isOnTheFly); + ProblemsHolder holder = new ProblemsHolder(iManager, myFile, isOnTheFly) { + @Override + public void registerProblem(@NotNull ProblemDescriptor descriptor) { + super.registerProblem(descriptor); + addDescriptorIncrementally(myDocument, descriptor, tool, ignoreSuppressed, indicator); + } + }; PsiElementVisitor elementVisitor = tool.buildVisitor(holder, isOnTheFly); + synchronized (init) { + init.add(Trinity.create(tool, holder, elementVisitor)); + } //noinspection ConstantConditions if(elementVisitor == null) { LOG.error("Tool " + tool + " must not return null from the buildVisitor() method"); @@ -215,27 +248,68 @@ public class LocalInspectionsPass extends ProgressableTextEditorHighlightingPass indicator.checkCanceled(); element.accept(elementVisitor); } - tool.inspectionFinished(session); - advanceProgress(elements.length); + + advanceProgress(elements.size()); if (holder.hasResults()) { - appendDescriptors(holder.getResults(), tool, ignoreSuppressed, indicator); + appendDescriptors(myFile, holder.getResults(), tool, ignoreSuppressed); } return true; } }, myFailFastOnAcquireReadAction); if (!result) throw new ProcessCanceledException(); - - indicator.checkCanceled(); - inspectInjectedPsi(elements, tools); - - myInfos = new ArrayList(myDescriptors.size()); - addHighlightsFromDescriptors(myInfos); - addHighlightsFromInjectedPsiProblems(myInfos); + inspectInjectedPsi(elements, tools, isOnTheFly, ignoreSuppressed, indicator); } - void inspectInjectedPsi(final PsiElement[] elements, final List tools) { - myInjectedPsiInspectionResults = ContainerUtil.createEmptyCOWList(); + private void visitRestElementsAndCleanup( + List tools, + final boolean isOnTheFly, + final boolean ignoreSuppressed, + final ProgressIndicator indicator, + final List elements, + final LocalInspectionToolSession session, + List> init) { + boolean result = JobUtil.invokeConcurrentlyUnderMyProgress(init, new Processor>() { + @Override + public boolean process(Trinity i) { + LocalInspectionTool tool = i.first; + final ProgressManager progressManager = ProgressManager.getInstance(); + indicator.checkCanceled(); + ProgressIndicator localIndicator = progressManager.getProgressIndicator(); + + ProgressIndicator original = ((ProgressWrapper)localIndicator).getOriginalProgressIndicator(); + LOG.assertTrue(original == indicator, original); + + ApplicationManager.getApplication().assertReadAccessAllowed(); + + ProblemsHolder holder = i.second; + PsiElementVisitor elementVisitor = i.third; + for (PsiElement element : elements) { + indicator.checkCanceled(); + element.accept(elementVisitor); + } + + advanceProgress(elements.size()); + + tool.inspectionFinished(session); + + if (holder.hasResults()) { + appendDescriptors(myFile, holder.getResults(), tool, ignoreSuppressed); + } + return true; + } + }, myFailFastOnAcquireReadAction); + if (!result) { + throw new ProcessCanceledException(); + } + inspectInjectedPsi(elements, tools, isOnTheFly, ignoreSuppressed, indicator); + + } + + private void inspectInjectedPsi(final List elements, + final List tools, + final boolean onTheFly, + final boolean ignoreSuppressed, final ProgressIndicator indicator) { final Set injected = new THashSet(); for (PsiElement element : elements) { InjectedLanguageUtil.enumerate(element, myFile, new PsiLanguageInjectionHost.InjectedPsiVisitor() { @@ -246,16 +320,16 @@ public class LocalInspectionsPass extends ProgressableTextEditorHighlightingPass } if (!JobUtil.invokeConcurrentlyUnderMyProgress(new ArrayList(injected), new Processor() { public boolean process(final PsiFile injectedPsi) { - inspectInjectedPsi(injectedPsi, myInjectedPsiInspectionResults, tools); + doInspectInjectedPsi(injectedPsi, tools,onTheFly,ignoreSuppressed,indicator); return true; } }, myFailFastOnAcquireReadAction)) throw new ProcessCanceledException(); } public Collection getHighlights() { - ArrayList highlights = new ArrayList(myDescriptors.size()); - addHighlightsFromDescriptors(highlights); - addHighlightsFromInjectedPsiProblems(highlights); + List highlights = new ArrayList(); + + addHighlightsFromResults(highlights); return highlights; } @@ -271,20 +345,67 @@ public class LocalInspectionsPass extends ProgressableTextEditorHighlightingPass highlightInfoType.getSeverity(element), problemDescriptor.isAfterEndOfLine(), null, isFileLevel); } - private synchronized void appendDescriptors(@NotNull List problemDescriptors, - @NotNull LocalInspectionTool tool, - boolean ignoreSuppressed, - @NotNull ProgressIndicator progress) { - InspectionProfile inspectionProfile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile(); - final HighlightSeverity severity = inspectionProfile.getErrorLevel(HighlightDisplayKey.find(tool.getShortName()), myFile).getSeverity(); - for (ProblemDescriptor problemDescriptor : problemDescriptors) { - progress.checkCanceled(); - if (!(ignoreSuppressed && InspectionManagerEx.inspectionResultSuppressed(problemDescriptor.getPsiElement(), tool))) { - myDescriptors.add(problemDescriptor); - HighlightInfoType type = highlightTypeFromDescriptor(problemDescriptor, severity); - myLevels.add(type); - myTools.add(tool); + private final AtomicBoolean haveInfosToProcess = new AtomicBoolean(); + private final ConcurrentLinkedQueue> infosToAdd = new ConcurrentLinkedQueue>(); + private final Set emptyActionRegistered = Collections.synchronizedSet(new HashSet()); + + private void addDescriptorIncrementally(@NotNull final Document document, + @NotNull final ProblemDescriptor descriptor, + @NotNull final LocalInspectionTool tool, + boolean ignoreSuppressed, + @NotNull final ProgressIndicator indicator) { + if (ignoreSuppressed && InspectionManagerEx.inspectionResultSuppressed(descriptor.getPsiElement(), tool)) { + return; + } + + infosToAdd.offer(Pair.create(descriptor, tool)); + if (haveInfosToProcess.getAndSet(true)) return; + // extra invoke later is harmless, missing invoke is not + UIUtil.invokeLaterIfNeeded(new Runnable() { + public void run() { + if (myProject.isDisposed()) return; + InspectionProfile inspectionProfile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile(); + InjectedLanguageManager ilManager = InjectedLanguageManager.getInstance(myProject); + List infos = new ArrayList(2); + while (haveInfosToProcess.compareAndSet(true, false)) { + for (Pair pair = infosToAdd.poll(); pair != null; pair = infosToAdd.poll()) { + if (indicator.isCanceled()) { + infosToAdd.clear(); + return; + } + + ProblemDescriptor descriptor = pair.first; + LocalInspectionTool tool = pair.second; + PsiElement psiElement = descriptor.getPsiElement(); + if (psiElement == null) continue; + PsiFile file = psiElement.getContainingFile(); + Document thisDocument = file == myFile ? document : PsiDocumentManager.getInstance(myProject).getDocument(file); + + HighlightSeverity severity = inspectionProfile.getErrorLevel(HighlightDisplayKey.find(tool.getShortName()), file).getSeverity(); + + infos.clear(); + createHighlightsForDescriptor(infos, emptyActionRegistered, ilManager, file, thisDocument, tool, severity, descriptor); + for (HighlightInfo info : infos) { + UpdateHighlightersUtil.addHighlighterToEditorIncrementally(myProject, myDocument, myFile, myStartOffset, myEndOffset, info, getId()); + } + } + } } + }); + } + + private void appendDescriptors(PsiFile file, List descriptors, LocalInspectionTool tool, boolean ignoreSuppressed) { + InspectionResult res = new InspectionResult(tool, descriptors); + appendResult(file, res); + } + + private void appendResult(PsiFile file, InspectionResult res) { + List resultList = result.get(file); + if (resultList == null) { + resultList = ConcurrencyUtil.cacheOrGet(result, file, new ArrayList()); + } + synchronized (resultList) { + resultList.add(res); } } @@ -328,58 +449,63 @@ public class LocalInspectionsPass extends ProgressableTextEditorHighlightingPass UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument, myStartOffset, myEndOffset, myInfos, getId()); } - private void addHighlightsFromDescriptors(final List toInfos) { - Set emptyActionRegistered = new THashSet(); - for (int i = 0; i < myDescriptors.size(); i++) { - ProblemDescriptor descriptor = myDescriptors.get(i); - LocalInspectionTool tool = myTools.get(i); - final HighlightInfoType level = myLevels.get(i); - HighlightInfo highlightInfo = createHighlightInfo(descriptor, tool, level, emptyActionRegistered); - if (highlightInfo != null) { - toInfos.add(highlightInfo); - } - } - } - - private void addHighlightsFromInjectedPsiProblems(final List infos) { - Set emptyActionRegistered = new THashSet(); + private void addHighlightsFromResults(final List outInfos) { InspectionProfile inspectionProfile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile(); PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); InjectedLanguageManager ilManager = InjectedLanguageManager.getInstance(myProject); - for (InjectedPsiInspectionResult result : myInjectedPsiInspectionResults) { - LocalInspectionTool tool = result.tool; - HighlightSeverity severity = inspectionProfile.getErrorLevel(HighlightDisplayKey.find(tool.getShortName()), myFile).getSeverity(); + Set emptyActionRegistered = new THashSet(); - PsiElement injectedPsi = result.injectedPsi; - DocumentWindow documentRange = (DocumentWindow)documentManager.getDocument((PsiFile)injectedPsi); + for (Map.Entry> entry : result.entrySet()) { + PsiFile file = entry.getKey(); + Document documentRange = documentManager.getDocument(file); if (documentRange == null) continue; - //noinspection ForLoopReplaceableByForEach - for (int j = 0; j < result.foundProblems.size(); j++) { - ProblemDescriptor descriptor = result.foundProblems.get(j); - PsiElement psiElement = descriptor.getPsiElement(); - if (InspectionManagerEx.inspectionResultSuppressed(psiElement, tool)) continue; - HighlightInfoType level = highlightTypeFromDescriptor(descriptor, severity); - HighlightInfo info = createHighlightInfo(descriptor, tool, level, emptyActionRegistered); - if (info == null) continue; - - // todo we got to separate our "internal" prefixes/suffixes from user-defined ones - // todo in the latter case the erors should be highlighted, otherwise not - List editables = - ilManager.intersectWithAllEditableFragments((PsiFile)injectedPsi, new TextRange(info.startOffset, info.endOffset)); - for (TextRange editable : editables) { - TextRange hostRange = documentRange.injectedToHost(editable); - HighlightInfo patched = HighlightInfo.createHighlightInfo(info.type, psiElement, hostRange.getStartOffset(), hostRange.getEndOffset(), info.description, info.toolTip); - if (patched != null) { - registerQuickFixes(tool, descriptor, patched, emptyActionRegistered); - infos.add(patched); - } + List resultList = entry.getValue(); + for (InspectionResult inspectionResult : resultList) { + LocalInspectionTool tool = inspectionResult.tool; + HighlightSeverity severity = inspectionProfile.getErrorLevel(HighlightDisplayKey.find(tool.getShortName()), file).getSeverity(); + for (ProblemDescriptor descriptor : inspectionResult.foundProblems) { + createHighlightsForDescriptor(outInfos, emptyActionRegistered, ilManager, file, documentRange, tool, severity, descriptor); } } } } + private void createHighlightsForDescriptor(List outInfos, + Set emptyActionRegistered, + InjectedLanguageManager ilManager, + PsiFile file, + Document documentRange, + LocalInspectionTool tool, + HighlightSeverity severity, + ProblemDescriptor descriptor) { + PsiElement psiElement = descriptor.getPsiElement(); + if (InspectionManagerEx.inspectionResultSuppressed(psiElement, tool)) return; + HighlightInfoType level = highlightTypeFromDescriptor(descriptor, severity); + HighlightInfo info = createHighlightInfo(descriptor, tool, level, emptyActionRegistered); + if (info == null) return; + + if (file == myFile) { + // not injected + outInfos.add(info); + return; + } + // todo we got to separate our "internal" prefixes/suffixes from user-defined ones + // todo in the latter case the erors should be highlighted, otherwise not + List editables = ilManager.intersectWithAllEditableFragments(file, new TextRange(info.startOffset, info.endOffset)); + for (TextRange editable : editables) { + TextRange hostRange = ((DocumentWindow)documentRange).injectedToHost(editable); + HighlightInfo patched = HighlightInfo.createHighlightInfo(info.type, psiElement, hostRange.getStartOffset(), hostRange.getEndOffset(), info.description, info.toolTip); + if (patched != null) { + registerQuickFixes(tool, descriptor, patched, emptyActionRegistered); + outInfos.add(patched); + } + } + } + @Nullable - private HighlightInfo createHighlightInfo(final ProblemDescriptor descriptor, final LocalInspectionTool tool, final HighlightInfoType level, + private HighlightInfo createHighlightInfo(final ProblemDescriptor descriptor, + final LocalInspectionTool tool, + final HighlightInfoType level, final Set emptyActionRegistered) { PsiElement psiElement = descriptor.getPsiElement(); if (psiElement == null) return null; @@ -451,27 +577,39 @@ public class LocalInspectionsPass extends ProgressableTextEditorHighlightingPass return profile.getHighlightingLocalInspectionTools(myFile); } - private static void inspectInjectedPsi(PsiFile injectedPsi, List result, List tools) { - InspectionManager inspectionManager = InspectionManager.getInstance(injectedPsi.getProject()); - final ProblemsHolder problemsHolder = new ProblemsHolder(inspectionManager, injectedPsi, true); + private void doInspectInjectedPsi(@NotNull PsiFile injectedPsi, + @NotNull List tools, + boolean isOnTheFly, + final boolean ignoreSuppressed, final ProgressIndicator indicator) { + InspectionManager iManager = InspectionManager.getInstance(injectedPsi.getProject()); final PsiElement host = injectedPsi.getContext(); final PsiElement[] elements = getElementsIntersectingRange(injectedPsi, 0, injectedPsi.getTextLength()); - if (elements.length != 0) { - for (LocalInspectionTool tool : tools) { - if (host != null && InspectionManagerEx.inspectionResultSuppressed(host, tool)) { - continue; - } - final PsiElementVisitor visitor = tool.buildVisitor(problemsHolder, true); - assert !(visitor instanceof PsiRecursiveElementVisitor) : "The visitor returned from LocalInspectionTool.buildVisitor() must not be recursive. "+tool; - for (PsiElement element : elements) { - element.accept(visitor); - } - List problems = problemsHolder.getResults(); - if (problems != null && !problems.isEmpty()) { - InjectedPsiInspectionResult res = new InjectedPsiInspectionResult(tool, injectedPsi, new SmartList(problems)); - result.add(res); + if (elements.length == 0) { + return; + } + for (final LocalInspectionTool tool : tools) { + indicator.checkCanceled(); + if (host != null && InspectionManagerEx.inspectionResultSuppressed(host, tool)) { + continue; + } + ProblemsHolder holder = new ProblemsHolder(iManager, injectedPsi, isOnTheFly) { + @Override + public void registerProblem(@NotNull ProblemDescriptor descriptor) { + super.registerProblem(descriptor); + addDescriptorIncrementally(myDocument, descriptor, tool, ignoreSuppressed, indicator); } + }; + + final PsiElementVisitor visitor = tool.buildVisitor(holder, true); + assert !(visitor instanceof PsiRecursiveElementVisitor) : "The visitor returned from LocalInspectionTool.buildVisitor() must not be recursive. "+tool; + for (PsiElement element : elements) { + element.accept(visitor); + } + List problems = holder.getResults(); + if (problems != null && !problems.isEmpty()) { + InspectionResult res = new InspectionResult(tool, problems); + appendResult(injectedPsi, res); } } } @@ -480,14 +618,12 @@ public class LocalInspectionsPass extends ProgressableTextEditorHighlightingPass return myInfos; } - private static class InjectedPsiInspectionResult { + private static class InspectionResult { public final LocalInspectionTool tool; - public final PsiElement injectedPsi; public final List foundProblems; - private InjectedPsiInspectionResult(final LocalInspectionTool tool, final PsiElement injectedPsi, final List foundProblems) { + private InspectionResult(final LocalInspectionTool tool, final List foundProblems) { this.tool = tool; - this.injectedPsi = injectedPsi; this.foundProblems = foundProblems; } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPassFactory.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPassFactory.java index 799f3eb739be..44a3304bfe5d 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPassFactory.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPassFactory.java @@ -54,7 +54,8 @@ public class LocalInspectionsPassFactory extends AbstractProjectComponent implem TextRange textRange = calculateRangeToProcess(editor); if (textRange == null) return new ProgressableTextEditorHighlightingPass.EmptyPass(myProject, editor.getDocument(), LocalInspectionsPass.IN_PROGRESS_ICON, LocalInspectionsPass.PRESENTABLE_NAME); - return new LocalInspectionsPass(file, editor.getDocument(), textRange.getStartOffset(), textRange.getEndOffset()) { + TextRange visibleRange = VisibleHighlightingPassFactory.calculateVisibleRange(editor); + return new LocalInspectionsPass(file, editor.getDocument(), textRange.getStartOffset(), textRange.getEndOffset(), visibleRange){ List getInspectionTools(InspectionProfileWrapper profile) { List tools = super.getInspectionTools(profile); List result = new ArrayList(tools.size()); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/PassExecutorService.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/PassExecutorService.java index 2edfbe482f05..7a357134fba3 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/PassExecutorService.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/PassExecutorService.java @@ -17,7 +17,6 @@ package com.intellij.codeInsight.daemon.impl; import com.intellij.codeHighlighting.HighlightingPass; -import com.intellij.codeHighlighting.Pass; import com.intellij.codeHighlighting.TextEditorHighlightingPass; import com.intellij.concurrency.Job; import com.intellij.concurrency.JobImpl; @@ -243,11 +242,6 @@ public abstract class PassExecutorService implements Disposable { ScheduledPass predecessor = toBeSubmitted.get(predkey); if (predecessor == null) { TextEditorHighlightingPass textEditorPass = findPassById(predecessorId, textEditorHighlightingPasses); - if (textEditorPass == null && predecessorId == Pass.UPDATE_VISIBLE && passId != Pass.UPDATE_ALL && findPassById(Pass.UPDATE_ALL, textEditorHighlightingPasses) != null) { - // when UPDATE_VISIBLE pass is not going to run, pretend that all dependent passes are depend on UPDATE_ALL pass instead - return findOrCreatePredecessorPass(fileEditors, document, toBeSubmitted, textEditorHighlightingPasses, freePasses, updateProgress, - myThreadsToStartCountdown, jobPriority, Pass.UPDATE_ALL, passId); - } predecessor = textEditorPass == null ? null : createScheduledPass(fileEditors, textEditorPass, toBeSubmitted, textEditorHighlightingPasses, freePasses, updateProgress, myThreadsToStartCountdown, jobPriority); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/SeverityRegistrar.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/SeverityRegistrar.java index 45893afb33d8..5147a57a061f 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/SeverityRegistrar.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/SeverityRegistrar.java @@ -289,11 +289,11 @@ public class SeverityRegistrar implements JDOMExternalizable, Comparator getVisibleHighlights(int startOffset, int endOffset, Project project, Editor editor) { - List highlights = DaemonCodeAnalyzerImpl.getHighlights(editor.getDocument(), project); - if (highlights == null) return Collections.emptyList(); - - List array = new ArrayList(); - for (HighlightInfo info : highlights) { - if (info.hasHint() - && startOffset <= info.startOffset && info.endOffset <= endOffset - && !editor.getFoldingModel().isOffsetCollapsed(info.startOffset)) { - array.add(info); + private static List getVisibleHighlights(final int startOffset, final int endOffset, Project project, final Editor editor) { + final List highlights = new ArrayList(); + DaemonCodeAnalyzerImpl.processHighlights(editor.getDocument(), project, null, startOffset, endOffset, new Processor() { + public boolean process(HighlightInfo info) { + if (info.hasHint() && !editor.getFoldingModel().isOffsetCollapsed(info.startOffset)) { + highlights.add(info); + } + return true; } - } - return array; + }); + return highlights; } private boolean showAddImportHint(HighlightInfo info) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowAutoImportPassFactory.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowAutoImportPassFactory.java index 087010321ed7..44913b638c41 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowAutoImportPassFactory.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowAutoImportPassFactory.java @@ -40,7 +40,7 @@ import org.jetbrains.annotations.Nullable; public class ShowAutoImportPassFactory extends AbstractProjectComponent implements TextEditorHighlightingPassFactory { public ShowAutoImportPassFactory(Project project, TextEditorHighlightingPassRegistrar highlightingPassRegistrar) { super(project); - highlightingPassRegistrar.registerTextEditorHighlightingPass(this, new int[]{Pass.UPDATE_VISIBLE,}, null, false, -1); + highlightingPassRegistrar.registerTextEditorHighlightingPass(this, new int[]{Pass.UPDATE_ALL,}, null, false, -1); } @NonNls @@ -53,4 +53,4 @@ public class ShowAutoImportPassFactory extends AbstractProjectComponent implemen public TextEditorHighlightingPass createHighlightingPass(@NotNull PsiFile file, @NotNull final Editor editor) { return new ShowAutoImportPass(myProject, file, editor); } -} \ No newline at end of file +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowIntentionsPass.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowIntentionsPass.java index 66543f28bc7f..7c839d969221 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowIntentionsPass.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowIntentionsPass.java @@ -53,6 +53,7 @@ import com.intellij.psi.PsiFile; import com.intellij.ui.awt.RelativePoint; import com.intellij.util.IncorrectOperationException; import com.intellij.util.PairProcessor; +import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -112,7 +113,9 @@ public class ShowIntentionsPass extends TextEditorHighlightingPass { myEditor = editor; - myFile = PsiDocumentManager.getInstance(myProject).getPsiFile(myEditor.getDocument()); + PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); + + myFile = documentManager.getPsiFile(myEditor.getDocument()); assert myFile != null : FileDocumentManager.getInstance().getFile(myEditor.getDocument()); } @@ -188,7 +191,7 @@ public class ShowIntentionsPass extends TextEditorHighlightingPass { } } - public static void getActionsToShow(@NotNull final Editor hostEditor, @NotNull final PsiFile hostFile, @NotNull IntentionsInfo intentions, int passIdToShowIntentionsFor) { + public static void getActionsToShow(@NotNull final Editor hostEditor, @NotNull final PsiFile hostFile, @NotNull final IntentionsInfo intentions, int passIdToShowIntentionsFor) { final PsiElement psiElement = hostFile.findElementAt(hostEditor.getCaretModel().getOffset()); LOG.assertTrue(psiElement == null || psiElement.isValid(), psiElement); @@ -221,34 +224,39 @@ public class ShowIntentionsPass extends TextEditorHighlightingPass { intentions.inspectionFixesToShow.addAll(actions); } final int line = document.getLineNumber(offset); - final List infoList = DaemonCodeAnalyzerImpl.getHighlights(document, HighlightSeverity.INFORMATION, project, - document.getLineStartOffset(line), - document.getLineEndOffset(line)); - for (HighlightInfo info : infoList) { - final GutterIconRenderer renderer = info.getGutterIconRenderer(); - if (renderer != null) { - final AnAction action = renderer.getClickAction(); - if (action != null) { - final String text = renderer.getTooltipText(); - if (text != null) { - final IntentionAction actionAdapter = new AbstractIntentionAction() { - public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { - final RelativePoint relativePoint = JBPopupFactory.getInstance().guessBestPopupLocation(editor); - action.actionPerformed( - new AnActionEvent(relativePoint.toMouseEvent(), DataManager.getInstance().getDataContext(), text, new Presentation(), - ActionManager.getInstance(), 0)); - } - - @NotNull - public String getText() { - return text; - } - }; - intentions.guttersToShow.add(new HighlightInfo.IntentionActionDescriptor(actionAdapter, Collections.emptyList(), text, renderer.getIcon())); + DaemonCodeAnalyzerImpl.processHighlights(document, project, null, + document.getLineStartOffset(line), + document.getLineEndOffset(line), new Processor() { + public boolean process(HighlightInfo info) { + final GutterIconRenderer renderer = info.getGutterIconRenderer(); + if (renderer == null) { + return true; } - } - } - } - } + final AnAction action = renderer.getClickAction(); + if (action == null) { + return true; + } + final String text = renderer.getTooltipText(); + if (text == null) { + return true; + } + final IntentionAction actionAdapter = new AbstractIntentionAction() { + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + final RelativePoint relativePoint = JBPopupFactory.getInstance().guessBestPopupLocation(editor); + action.actionPerformed( + new AnActionEvent(relativePoint.toMouseEvent(), DataManager.getInstance().getDataContext(), text, new Presentation(), + ActionManager.getInstance(), 0)); + } + @NotNull + public String getText() { + return text; + } + }; + intentions.guttersToShow.add(new HighlightInfo.IntentionActionDescriptor(actionAdapter, Collections.emptyList(), text, renderer.getIcon())); + return true; + } + }); + } } + diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/TextEditorBackgroundHighlighter.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/TextEditorBackgroundHighlighter.java index be263c876532..bf2748c29824 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/TextEditorBackgroundHighlighter.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/TextEditorBackgroundHighlighter.java @@ -35,7 +35,6 @@ import java.util.List; public class TextEditorBackgroundHighlighter implements BackgroundEditorHighlighter { private static final int[] EXCEPT_OVERRIDDEN = { Pass.UPDATE_FOLDING, - Pass.UPDATE_VISIBLE, Pass.POPUP_HINTS, Pass.UPDATE_ALL, Pass.POST_UPDATE_ALL, @@ -99,26 +98,6 @@ public class TextEditorBackgroundHighlighter implements BackgroundEditorHighligh @NotNull public TextEditorHighlightingPass[] createPassesForVisibleArea() { List passes = getPasses(EXCEPT_VISIBLE); - int updateAllIndex = -1; - int updateVisibleIndex = -1; - for (int i = 0, passesSize = passes.size(); i < passesSize; i++) { - TextEditorHighlightingPass pass = passes.get(i); - if (pass instanceof GeneralHighlightingPass) { - if (pass.getId() == Pass.UPDATE_ALL) updateAllIndex = i; - if (pass.getId() == Pass.UPDATE_VISIBLE) updateVisibleIndex = i; - } - } - - if (updateVisibleIndex != -1 && updateAllIndex == -1) { - // ok - } - else if (updateVisibleIndex == -1 && updateAllIndex != -1) { - // use updateAll pass instead of updateVisible - } - else if (updateVisibleIndex != -1 && updateAllIndex != -1) { - // run only updateVisible, not both - passes.remove(updateAllIndex); - } return passes.isEmpty() ? TextEditorHighlightingPass.EMPTY_ARRAY : passes.toArray(new TextEditorHighlightingPass[passes.size()]); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/TrafficLightRenderer.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/TrafficLightRenderer.java index 29281a4f3042..56be391719ca 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/TrafficLightRenderer.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/TrafficLightRenderer.java @@ -30,6 +30,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiFile; import com.intellij.ui.LayeredIcon; import com.intellij.util.ArrayUtil; +import com.intellij.util.Processor; import com.intellij.util.ui.EmptyIcon; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.Nullable; @@ -37,7 +38,8 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; import java.net.URL; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public class TrafficLightRenderer implements ErrorStripeRenderer { @@ -128,46 +130,78 @@ public class TrafficLightRenderer implements ErrorStripeRenderer { return status; } - protected void fillDaemonCodeAnalyzerErrorsStatus(DaemonCodeAnalyzerStatus status, boolean fillErrorsCount) { + protected void fillDaemonCodeAnalyzerErrorsStatus(final DaemonCodeAnalyzerStatus status, final boolean fillErrorsCount) { final SeverityRegistrar severityRegistrar = SeverityRegistrar.getInstance(myProject); - List highlights = DaemonCodeAnalyzerImpl.getHighlights(myDocument, myProject); - if (highlights == null) highlights = Arrays.asList(HighlightInfo.EMPTY_ARRAY); - if (fillErrorsCount) { - highlights = new ArrayList(highlights); - Collections.sort(highlights, new Comparator() { - public int compare(HighlightInfo o1, HighlightInfo o2) { - return o1.getSeverity().myVal - o2.getSeverity().myVal; + if (fillErrorsCount) Arrays.fill(status.errorCount, 0); + final int count = severityRegistrar.getSeveritiesCount() - 1; + final HighlightSeverity maxSeverity = severityRegistrar.getSeverityByIndex(count); + final HighlightSeverity[] maxFoundSeverity = {null}; + + DaemonCodeAnalyzerImpl.processHighlights(myDocument, myProject, null, 0, myDocument.getTextLength(), new Processor() { + public boolean process(HighlightInfo info) { + HighlightSeverity infoSeverity = info.getSeverity(); + if (fillErrorsCount) { + final int severityIdx = severityRegistrar.getSeverityIdx(infoSeverity); + if (severityIdx != -1) { + status.errorCount[severityIdx] ++; + } } - }); - Arrays.fill(status.errorCount, 0); - for (HighlightInfo highlight : highlights) { - final HighlightSeverity severity = highlight.getSeverity(); - final int severityIdx = severityRegistrar.getSeverityIdx(severity); - if (severityIdx != -1) { - status.errorCount[severityIdx] ++; - } - } - } - else { - HighlightSeverity severity = null; - final int count = severityRegistrar.getSeveritiesCount() - 1; - HighlightSeverity maxSeverity = severityRegistrar.getSeverityByIndex(count); - for (HighlightInfo info : highlights) { - if (info.getSeverity() == maxSeverity) { - status.errorCount[count] = 1; - return; - } - if (severity == null || severityRegistrar.compare(severity, info.getSeverity()) <= 0) { - severity = info.getSeverity(); - } - } - if (severity != null) { - final int severityIdx = severityRegistrar.getSeverityIdx(severity); - if (severityIdx != -1) { - status.errorCount[severityIdx] = 1; + else { + if (infoSeverity == maxSeverity) { + status.errorCount[count] = 1; + return false; + } + if (maxFoundSeverity[0] == null || severityRegistrar.compare(maxFoundSeverity[0], infoSeverity) <= 0) { + maxFoundSeverity[0] = infoSeverity; + } } + return true; + } + }); + if (maxFoundSeverity[0] != null) { + final int severityIdx = severityRegistrar.getSeverityIdx(maxFoundSeverity[0]); + if (severityIdx != -1) { + status.errorCount[severityIdx] = 1; } } + + //if (highlights == null) highlights = Arrays.asList(HighlightInfo.EMPTY_ARRAY); + //if (fillErrorsCount) { + // highlights = new ArrayList(highlights); + // Collections.sort(highlights, new Comparator() { + // public int compare(HighlightInfo o1, HighlightInfo o2) { + // return o1.getSeverity().myVal - o2.getSeverity().myVal; + // } + // }); + // Arrays.fill(status.errorCount, 0); + // for (HighlightInfo highlight : highlights) { + // final HighlightSeverity severity = highlight.getSeverity(); + // final int severityIdx = severityRegistrar.getSeverityIdx(severity); + // if (severityIdx != -1) { + // status.errorCount[severityIdx] ++; + // } + // } + //} + //else { + // HighlightSeverity severity = null; + // final int count = severityRegistrar.getSeveritiesCount() - 1; + // HighlightSeverity maxSeverity = severityRegistrar.getSeverityByIndex(count); + // for (HighlightInfo info : highlights) { + // if (info.getSeverity() == maxSeverity) { + // status.errorCount[count] = 1; + // return; + // } + // if (severity == null || severityRegistrar.compare(severity, info.getSeverity()) <= 0) { + // severity = info.getSeverity(); + // } + // } + // if (severity != null) { + // final int severityIdx = severityRegistrar.getSeverityIdx(severity); + // if (severityIdx != -1) { + // status.errorCount[severityIdx] = 1; + // } + // } + //} } public final Project getProject() { 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 4ad566e6502a..93b0b48d1dff 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 @@ -21,20 +21,18 @@ import com.intellij.codeInsight.intention.impl.FileLevelIntentionComponent; import com.intellij.lang.annotation.HighlightSeverity; 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.EditorFactory; import com.intellij.openapi.editor.RangeMarker; -import com.intellij.openapi.editor.colors.EditorColorsManager; -import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.ex.DocumentEx; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.ex.MarkupModelEx; import com.intellij.openapi.editor.ex.RangeHighlighterEx; import com.intellij.openapi.editor.highlighter.HighlighterIterator; -import com.intellij.openapi.editor.impl.RangeHighlighterImpl; +import com.intellij.openapi.editor.impl.RangeMarkerTree; +import com.intellij.openapi.editor.impl.RedBlackTree; import com.intellij.openapi.editor.markup.*; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.FileEditorManager; @@ -49,11 +47,13 @@ import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; +import com.intellij.util.Processor; import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import gnu.trove.THashMap; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; import java.awt.*; @@ -61,8 +61,31 @@ import java.util.*; import java.util.List; public class UpdateHighlightersUtil { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil"); - private static final boolean DEBUG = LOG.isDebugEnabled(); + private static final Comparator BY_START_OFFSET_NODUPS = new Comparator() { + public int compare(HighlightInfo o1, HighlightInfo o2) { + int d = o1.getActualStartOffset() - o2.getActualStartOffset(); + if (d != 0) return d; + d = o1.getActualEndOffset() - o2.getActualEndOffset(); + if (d != 0) return d; + + d = Comparing.compare(o1.getSeverity(), o2.getSeverity()); + if (d != 0) return -d; // higher severity first, to prevent warnings overlap errors + + if (!Comparing.equal(o1.type, o2.type)) { + return String.valueOf(o1.type).compareTo(String.valueOf(o2.type)); + } + + if (!Comparing.equal(o1.getGutterIconRenderer(), o2.getGutterIconRenderer())) { + return String.valueOf(o1.getGutterIconRenderer()).compareTo(String.valueOf(o2.getGutterIconRenderer())); + } + + if (!Comparing.equal(o1.forcedTextAttributes, o2.forcedTextAttributes)) { + return String.valueOf(o1.getGutterIconRenderer()).compareTo(String.valueOf(o2.getGutterIconRenderer())); + } + + return Comparing.compare(o1.description, o2.description); + } + }; private static final Key> FILE_LEVEL_HIGHLIGHTS = Key.create("FILE_LEVEL_HIGHLIGHTS"); private static final Comparator BY_START_OFFSET = new Comparator() { @@ -112,8 +135,8 @@ public class UpdateHighlightersUtil { return false; } - private static class RangeHighlightersToReuse { - private final MultiMap info2Pass = new MultiMap(){ + private static class HighlightersRecycler { + private final MultiMap incinerator = new MultiMap(){ @Override protected Map> createMap() { return new THashMap>(); @@ -125,36 +148,69 @@ public class UpdateHighlightersUtil { } }; - void add(RangeHighlighter highlighter) { - info2Pass.putValue(InjectedLanguageUtil.toTextRange(highlighter), highlighter); + void recycleHighlighter(RangeHighlighter highlighter) { + incinerator.putValue(InjectedLanguageUtil.toTextRange(highlighter), highlighter); } - RangeHighlighter reuseHighlighterAt(int startOffset, int endOffset){ + RangeHighlighter pickupHighlighterFromGarbageBin(int startOffset, int endOffset, int layer){ TextRange range = new TextRange(startOffset, endOffset); - Collection collection = info2Pass.get(range); + Collection collection = incinerator.get(range); for (RangeHighlighter highlighter : collection) { - if (!highlighter.isValid()) continue; - info2Pass.removeValue(range, highlighter); - return highlighter; + if (highlighter.isValid() && highlighter.getLayer() == layer) { + incinerator.removeValue(range, highlighter); + return highlighter; + } } return null; } - Collection forAll() { - return info2Pass.values(); + Collection forAllInGarbageBin() { + return incinerator.values(); } } + public static void addHighlighterToEditorIncrementally(@NotNull Project project, + @NotNull Document document, + @NotNull PsiFile file, + int startOffset, + int endOffset, + @NotNull final HighlightInfo info, + final int group) { + ApplicationManager.getApplication().assertIsDispatchThread(); + if (info.isFileLevelAnnotation) return; + + MarkupModel markup = document.getMarkupModel(project); + Processor otherHighlightInTheWayProcessor = new Processor() { + public boolean process(HighlightInfo oldInfo) { + return oldInfo.group != group || !oldInfo.equalsByActualOffset(info); + } + }; + if (!DaemonCodeAnalyzerImpl.processHighlights(document, project, + null, info.getActualStartOffset(), info.getActualEndOffset(), + otherHighlightInTheWayProcessor)) { + return; + } + + boolean success = createOrReuseHighlighterFor(info, document, group, file, (MarkupModelEx)markup, null, null, startOffset, endOffset, + SeverityRegistrar.getInstance(project)); + if (!success) { + return; + } + + DaemonCodeAnalyzerImpl.addHighlight(markup, project, info); + clearWhiteSpaceOptimizationFlag(document); + assertMarkupConsistent(markup, project); + } + public static void setHighlightersToEditor(@NotNull Project project, @NotNull Document document, @NotNull Map> infos, - int group) { + final int group) { ApplicationManager.getApplication().assertIsDispatchThread(); - //serialized implicitly by the dispatch thread PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document); cleanFileLevelHighlights(project, group, psiFile); - List ranges = new ArrayList(infos.keySet()); + final List ranges = new ArrayList(infos.keySet()); Collections.sort(ranges, BY_START_OFFSET); //merge intersecting for (int i = 1; i < ranges.size(); i++) { @@ -175,91 +231,157 @@ public class UpdateHighlightersUtil { } MarkupModel markup = document.getMarkupModel(project); - List oldHighlights = DaemonCodeAnalyzerImpl.getHighlights(markup); - List oldHighlightsToRemove = DaemonCodeAnalyzerImpl.getHighlightsToRemove(markup); - assertMarkupConsistent(markup, oldHighlights, oldHighlightsToRemove); + assertMarkupConsistent(markup, project); - List result = new ArrayList(); - RangeHighlightersToReuse infosToRemove = new RangeHighlightersToReuse(); + for (Map.Entry> entry : infos.entrySet()) { + TextRange range = entry.getKey(); + Collection highlights = entry.getValue(); + setHighlightersInRange(range, highlights, (MarkupModelEx)markup, group, document, project); + } + } - boolean changed = false; - List toRemoveInAnotherPass = new SmartList(); + private static void setHighlightersInRange(final TextRange range, + Collection highlightsCo, + final MarkupModelEx markup, + final int group, + final Document document, + final Project project) { + final List highlights = new ArrayList(highlightsCo); - if (oldHighlights != null) { - List list = ContainerUtil.concat(oldHighlightsToRemove, oldHighlights); - for (int i = 0; i < list.size(); i++) { - HighlightInfo info = list.get(i); - RangeHighlighter highlighter = info.highlighter; - boolean toRemove = isRemoving(group, ranges, info, highlighter); - if (toRemove) { - infosToRemove.add(highlighter); - changed = true; + final SeverityRegistrar severityRegistrar = SeverityRegistrar.getInstance(project); + final HighlightersRecycler infosToRemove = new HighlightersRecycler(); + DaemonCodeAnalyzerImpl.processHighlights(document, project, null, range.getStartOffset(), range.getEndOffset(), 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; + } } - else if (i >= oldHighlightsToRemove.size()) { - result.add(info); + 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(Processor processor) { + return ContainerUtil.process(highlights, processor); + } + }, new MarkupModelEx.SweepProcessor() { + @Override + public boolean process(int offset, HighlightInfo info, boolean atStart, Collection overlappingIntervals) { + if (!atStart) { + return true; } - else { - toRemoveInAnotherPass.add(info); + 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; + } + boolean success = createOrReuseHighlighterFor(info, document, group, psiFile, markup, infosToRemove, + ranges2markersCache, range.getStartOffset(), range.getEndOffset(), + severityRegistrar); + if (success) { + 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 boolean isWarningCoveredByError(HighlightInfo info, + Collection overlappingIntervals, + SeverityRegistrar severityRegistrar) { + if (!isError(info, severityRegistrar)) { + for (HighlightInfo overlapping : overlappingIntervals) { + boolean overlapIsError = isError(overlapping, severityRegistrar); + if (overlapIsError && DaemonCodeAnalyzerImpl.isCoveredBy(info, overlapping)) { + return true; } } } + return false; + } - Map ranges2markersCache = new THashMap(oldHighlights == null ? 10 : oldHighlights.size()); - for (TextRange range : ranges) { - int rangeStartOffset = range.getStartOffset(); - int rangeEndOffset = range.getEndOffset(); + private static boolean isError(HighlightInfo info, SeverityRegistrar severityRegistrar) { + return severityRegistrar.compare(HighlightSeverity.ERROR, info.getSeverity()) <= 0; + } - List highlights = new ArrayList(infos.get(range)); - Collections.sort(highlights, new Comparator() { - public int compare(HighlightInfo o1, HighlightInfo o2) { - return o1.startOffset - o2.startOffset; - } - }); + // 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) { + final int infoStartOffset = info.startOffset; + int infoEndOffset = info.endOffset; + if (infoStartOffset < rangeStartOffset || infoEndOffset > rangeEndOffset) return false; - for (HighlightInfo info : highlights) { - if (info.isFileLevelAnnotation && psiFile != null && psiFile.getViewProvider().isPhysical()) { - addFileLevelHighlight(project, group, info, psiFile); - continue; - } + if (infoEndOffset == infoStartOffset && !info.isAfterEndOfLine) { + infoEndOffset++; //show something in case of empty highlightinfo + } + final int docLength = document.getTextLength(); + if (infoEndOffset > docLength) { + infoEndOffset = docLength; + } - int infoStartOffset = info.startOffset; - int infoEndOffset = info.endOffset; - if (infoStartOffset < rangeStartOffset || infoEndOffset > rangeEndOffset) continue; + info.text = document.getCharsSequence().subSequence(infoStartOffset, infoEndOffset).toString(); + info.group = group; - if (infoEndOffset == infoStartOffset && !info.isAfterEndOfLine) { - infoEndOffset++; //show something in case of empty highlightinfo - } - final int docLength = document.getTextLength(); - if (infoEndOffset > docLength) { - infoEndOffset = docLength; - } + int layer = getLayer(info, severityRegistrar); + RangeHighlighterEx highlighter = infosToRemove == null ? null : (RangeHighlighterEx)infosToRemove.pickupHighlighterFromGarbageBin(info.startOffset, info.endOffset, layer); + if (highlighter == null) { + highlighter = createRangeHighlighter(infoStartOffset, infoEndOffset, markup, layer); + } - info.text = document.getCharsSequence().subSequence(infoStartOffset, infoEndOffset).toString(); - info.group = group; + final RangeHighlighterEx finalHighlighter = highlighter; + final int finalInfoEndOffset = infoEndOffset; + highlighter.changeAttributesInBatch(new Runnable() { + public void run() { + finalHighlighter.setTextAttributes(info.getTextAttributes(psiFile)); - - RangeHighlighterEx highlighter = (RangeHighlighterEx)infosToRemove.reuseHighlighterAt(info.startOffset, info.endOffset); - if (highlighter == null) { - highlighter = createRangeHighlighter(project, psiFile, info, infoStartOffset, infoEndOffset, markup); - } - else { - ((RangeHighlighterImpl)highlighter).setTextAttributes(info.getTextAttributes(psiFile)); - } - changed = true; - info.highlighter = highlighter; - highlighter.setAfterEndOfLine(info.isAfterEndOfLine); + info.highlighter = finalHighlighter; + finalHighlighter.setAfterEndOfLine(info.isAfterEndOfLine); Color color = info.getErrorStripeMarkColor(psiFile); - highlighter.setErrorStripeMarkColor(color); - if (!(highlighter.getErrorStripeTooltip() instanceof HighlightInfo) || !info.equalsByActualOffset((HighlightInfo)highlighter.getErrorStripeTooltip())) { - highlighter.setErrorStripeTooltip(info); + finalHighlighter.setErrorStripeMarkColor(color); + if (info != finalHighlighter.getErrorStripeTooltip()) { + finalHighlighter.setErrorStripeTooltip(info); } GutterIconRenderer renderer = info.getGutterIconRenderer(); - highlighter.setGutterIconRenderer(renderer); + finalHighlighter.setGutterIconRenderer(renderer); - ranges2markersCache.put(new TextRange(infoStartOffset, infoEndOffset), info.highlighter); + if (ranges2markersCache != null) ranges2markersCache.put(new TextRange(infoStartOffset, finalInfoEndOffset), info.highlighter); if (info.quickFixActionRanges != null) { - info.quickFixActionMarkers = new ArrayList>(info.quickFixActionRanges.size()); + info.quickFixActionMarkers = ContainerUtil.createEmptyCOWList(); for (Pair pair : info.quickFixActionRanges) { TextRange textRange = pair.second; RangeMarker marker = getOrCreate(document, ranges2markersCache, textRange); @@ -267,68 +389,43 @@ public class UpdateHighlightersUtil { } } info.fixMarker = getOrCreate(document, ranges2markersCache, new TextRange(info.fixStartOffset, info.fixEndOffset)); - - assert Comparing.equal(info.getTextAttributes(psiFile), highlighter.getTextAttributes()) : "Info: " + - info.getTextAttributes(psiFile) + - "; highlighter:" + - highlighter.getTextAttributes(); - - result.add(info); } - } + }); - for (RangeHighlighter highlighter : infosToRemove.forAll()) { - markup.removeHighlighter(highlighter); - changed = true; - } - - if (changed) { - DaemonCodeAnalyzerImpl.setHighlights(markup, project, result, toRemoveInAnotherPass); - clearWhiteSpaceOptimizationFlag(document); - } - assertMarkupConsistent(markup, result, toRemoveInAnotherPass); + assert Comparing.equal(info.getTextAttributes(psiFile), highlighter.getTextAttributes()) : "Info: " + + info.getTextAttributes(psiFile) + + "; highlighter:" + + highlighter.getTextAttributes(); + return true; } - private static boolean isRemoving(int group, List ranges, HighlightInfo info, RangeHighlighter highlighter) { - if (!highlighter.isValid()) return true; - if (info.group != group) return false; - TextRange toFind = new TextRange(highlighter.getStartOffset(), highlighter.getEndOffset()); - int i = Collections.binarySearch(ranges, toFind, BY_START_OFFSET_OR_CONTAINS); - if (i<0) return false; - Document document = highlighter.getDocument(); - if (highlighter.getEndOffset() == document.getTextLength() && ranges.get(ranges.size()-1).getEndOffset() == document.getTextLength()) { - return true; - } - TextRange containing = ranges.get(i); - return containing.contains(toFind.getStartOffset()); + @NotNull + private static RangeHighlighterEx createRangeHighlighter(int infoStartOffset, int infoEndOffset, MarkupModelEx markup, int layer) { + return (RangeHighlighterEx)markup.addRangeHighlighter(infoStartOffset, infoEndOffset, layer, null, HighlighterTargetArea.EXACT_RANGE); } - private static RangeHighlighterEx createRangeHighlighter(Project project, - PsiFile psiFile, - HighlightInfo info, - int infoStartOffset, - int infoEndOffset, - MarkupModel markup) { - HighlightSeverity severity = info.getSeverity(); + private static int getLayer(HighlightInfo info, SeverityRegistrar severityRegistrar) { + final HighlightSeverity severity = info.getSeverity(); int layer; if (severity == HighlightSeverity.WARNING) { layer = HighlighterLayer.WARNING; } - else if (SeverityRegistrar.getInstance(project).compare(severity, HighlightSeverity.ERROR) >= 0) { + else if (severityRegistrar.compare(severity, HighlightSeverity.ERROR) >= 0) { layer = HighlighterLayer.ERROR; } else { layer = HighlighterLayer.ADDITIONAL_SYNTAX; } - - return (RangeHighlighterEx)markup.addRangeHighlighter(infoStartOffset, infoEndOffset, layer, info.getTextAttributes(psiFile), HighlighterTargetArea.EXACT_RANGE); + return layer; } - private static RangeMarker getOrCreate(Document document, Map ranges2markersCache, TextRange textRange) { - RangeMarker marker = ranges2markersCache.get(textRange); + private static RangeMarker getOrCreate(@NotNull Document document, @Nullable Map ranges2markersCache, @NotNull TextRange textRange) { + RangeMarker marker = ranges2markersCache == null ? null : ranges2markersCache.get(textRange); if (marker == null) { marker = document.createRangeMarker(textRange); - ranges2markersCache.put(textRange, marker); + if (ranges2markersCache != null) { + ranges2markersCache.put(textRange, marker); + } } return marker; } @@ -364,7 +461,7 @@ public class UpdateHighlightersUtil { List oldMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(document, project); List array = new ArrayList(oldMarkers == null ? markers.size() : oldMarkers.size()); MarkupModel markupModel = document.getMarkupModel(project); - RangeHighlightersToReuse toReuse = new RangeHighlightersToReuse(); + HighlightersRecycler toReuse = new HighlightersRecycler(); if (oldMarkers != null) { for (LineMarkerInfo info : oldMarkers) { RangeHighlighter highlighter = info.highlighter; @@ -374,7 +471,7 @@ public class UpdateHighlightersUtil { (highlighter.getEndOffset() < endOffset || highlighter.getEndOffset() == document.getTextLength()); if (toRemove) { - toReuse.add(highlighter); + toReuse.recycleHighlighter(highlighter); } else { array.add(info); @@ -382,7 +479,6 @@ public class UpdateHighlightersUtil { } } - final EditorColorsScheme colorsScheme = EditorColorsManager.getInstance().getGlobalScheme(); // TODO: editor color scheme for (LineMarkerInfo info : markers) { PsiElement element = info.getElement(); if (element == null) { @@ -395,7 +491,7 @@ public class UpdateHighlightersUtil { if (startOffset > elementRange.getStartOffset() || elementRange.getEndOffset() > endOffset) { continue; } - RangeHighlighter marker = toReuse.reuseHighlighterAt(info.startOffset, info.endOffset); + RangeHighlighter marker = toReuse.pickupHighlighterFromGarbageBin(info.startOffset, info.endOffset, HighlighterLayer.ADDITIONAL_SYNTAX); if (marker == null) { marker = markupModel.addRangeHighlighter(info.startOffset, info.endOffset, HighlighterLayer.ADDITIONAL_SYNTAX, null, HighlighterTargetArea.EXACT_RANGE); } @@ -414,7 +510,7 @@ public class UpdateHighlightersUtil { array.add(info); } - for (RangeHighlighter highlighter : toReuse.forAll()) { + for (RangeHighlighter highlighter : toReuse.forAllInGarbageBin()) { markupModel.removeHighlighter(highlighter); } @@ -435,12 +531,11 @@ public class UpdateHighlightersUtil { public static void updateHighlightersByTyping(@NotNull Project project, @NotNull DocumentEvent e) { ApplicationManager.getApplication().assertIsDispatchThread(); - Document document = e.getDocument(); + final Document document = e.getDocument(); if (document instanceof DocumentEx && ((DocumentEx)document).isInBulkUpdate()) return; - MarkupModel markup = document.getMarkupModel(project); - List oldHighlights = DaemonCodeAnalyzerImpl.getHighlights(markup); - if (oldHighlights == null || oldHighlights.isEmpty()) return; + final MarkupModel markup = document.getMarkupModel(project); + assertMarkupConsistent(markup, project); int offset = e.getOffset(); Editor[] editors = EditorFactory.getInstance().getEditors(document, project); @@ -448,61 +543,64 @@ public class UpdateHighlightersUtil { Editor editor = editors[0]; // use any editor - just to fetch SelectInEditorManager HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(Math.max(0, offset - 1)); if (iterator.atEnd()) return; - int start = iterator.getStart(); - while (iterator.getEnd() < e.getOffset() + e.getNewLength()) { + final int start = iterator.getStart(); + while (iterator.getEnd() < e.getOffset() + Math.max(e.getOldLength(), e.getNewLength())) { iterator.advance(); if (iterator.atEnd()) return; } - int end = iterator.getEnd(); + final int end = iterator.getEnd(); - List infosToRemove = new ArrayList(DaemonCodeAnalyzerImpl.getHighlightsToRemove(markup)); - boolean highlightersChanged = false; - List result = new ArrayList(oldHighlights.size()); - boolean documentChangedInsideHighlighter = false; - for (HighlightInfo info : oldHighlights) { - RangeHighlighter highlighter = info.highlighter; - boolean toRemove = false; - if (info.needUpdateOnTyping()) { - int highlighterStart = highlighter.getStartOffset(); - int highlighterEnd = highlighter.getEndOffset(); - if (info.isAfterEndOfLine) { - if (highlighterStart < document.getTextLength()) { - highlighterStart += 1; + final boolean[] highlightersChanged = {false}; + final List removed = new ArrayList(); + final boolean[] documentChangedInsideHighlighter = {false}; + DaemonCodeAnalyzerImpl.processHighlights(document, project, null, start, end, new Processor() { + public boolean process(HighlightInfo info) { + RangeHighlighter highlighter = info.highlighter; + boolean toRemove = false; + if (info.needUpdateOnTyping()) { + int highlighterStart = highlighter.getStartOffset(); + int highlighterEnd = highlighter.getEndOffset(); + if (info.isAfterEndOfLine) { + if (highlighterStart < document.getTextLength()) { + highlighterStart += 1; + } + if (highlighterEnd < document.getTextLength()) { + highlighterEnd += 1; + } } - if (highlighterEnd < document.getTextLength()) { - highlighterEnd += 1; + + if (!highlighter.isValid()) { + toRemove = true; + } + else if (start < highlighterEnd && highlighterStart <= end) { + documentChangedInsideHighlighter[0] = true; + toRemove = true; } } - if (!highlighter.isValid()) { - toRemove = true; + if (toRemove) { + //if (info.type.equals(HighlightInfoType.WRONG_REF)) { + /* + markup.removeHighlighter(highlighter); + */ + //} + highlightersChanged[0] = true; + removed.add(info); } - else if (start < highlighterEnd && highlighterStart <= end) { - documentChangedInsideHighlighter = true; - toRemove = true; - } - } - if (toRemove) { - if (info.type.equals(HighlightInfoType.WRONG_REF)) { - markup.removeHighlighter(highlighter); - } - else { - infosToRemove.add(info); - } - highlightersChanged = true; + return true; } - else { - result.add(info); + }); + + for (HighlightInfo info : removed) { + if (!info.highlighter.isValid() || info.type.equals(HighlightInfoType.WRONG_REF)) { + markup.removeHighlighter(info.highlighter); } } + + assertMarkupConsistent(markup, project); - if (highlightersChanged) { - DaemonCodeAnalyzerImpl.setHighlights(markup, project, result, infosToRemove); - } - assertMarkupConsistent(markup, result, infosToRemove); - - if (highlightersChanged || documentChangedInsideHighlighter) { + if (highlightersChanged[0] || documentChangedInsideHighlighter[0]) { disableWhiteSpaceOptimization(document); } } @@ -523,25 +621,31 @@ public class UpdateHighlightersUtil { return result; } - private static void assertMarkupConsistent(MarkupModel markup, List highlightsToSet, List highlightsToRemove) { - if (DEBUG) { // TODO: [cdr] the checks are quadratic to number of highlights, - // PLEASE make them more efficient if there is a need to switch them on, - // there are many problematic tests FAILING for this reason! - if (highlightsToSet != null) { - for (HighlightInfo info : highlightsToSet) { - assert ((MarkupModelEx)markup).containsHighlighter(info.highlighter); - } + private static void assertMarkupConsistent(final MarkupModel markup, Project project) { + if (!RedBlackTree.VERIFY) { + return; + } + Document document = markup.getDocument(); + DaemonCodeAnalyzerImpl.processHighlights(document, project, null, 0, document.getTextLength(), new Processor() { + public boolean process(HighlightInfo info) { + assert ((MarkupModelEx)markup).containsHighlighter(info.highlighter); + return true; } - RangeHighlighter[] allHighlighters = markup.getAllHighlighters(); - for (RangeHighlighter highlighter : allHighlighters) { - Object tooltip = highlighter.getErrorStripeTooltip(); - if (tooltip instanceof HighlightInfo) { - HighlightInfo info = (HighlightInfo)tooltip; - assert highlightsToSet != null && highlightsToSet.contains(info) - || highlightsToRemove != null && highlightsToRemove.contains(info) - ; - } + }); + RangeHighlighter[] allHighlighters = markup.getAllHighlighters(); + for (RangeHighlighter highlighter : allHighlighters) { + if (!highlighter.isValid()) continue; + Object tooltip = highlighter.getErrorStripeTooltip(); + if (!(tooltip instanceof HighlightInfo)) { + continue; } + final HighlightInfo info = (HighlightInfo)tooltip; + boolean contains = !DaemonCodeAnalyzerImpl.processHighlights(document, project, null, info.getActualStartOffset(), info.getActualEndOffset(), new Processor() { + public boolean process(HighlightInfo highlightInfo) { + return UpdateHighlightersUtil.BY_START_OFFSET_NODUPS.compare(highlightInfo, info) != 0; + } + }); + assert contains: info; } } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/VisibleGeneralHighlightingPassFactory.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/VisibleGeneralHighlightingPassFactory.java deleted file mode 100644 index ff52806fea8d..000000000000 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/VisibleGeneralHighlightingPassFactory.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2000-2009 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.intellij.codeInsight.daemon.impl; - -import com.intellij.codeHighlighting.*; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.TextRange; -import com.intellij.psi.PsiFile; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * @author cdr -*/ -public class VisibleGeneralHighlightingPassFactory extends VisibleHighlightingPassFactory implements TextEditorHighlightingPassFactory { - public VisibleGeneralHighlightingPassFactory(Project project, TextEditorHighlightingPassRegistrar highlightingPassRegistrar) { - super(project); - highlightingPassRegistrar.registerTextEditorHighlightingPass(this, new int[]{Pass.UPDATE_FOLDING,}, null, true, Pass.UPDATE_VISIBLE); - } - - @NonNls - @NotNull - public String getComponentName() { - return "VisibleHighlightingPassFactory"; - } - - @Nullable - public TextEditorHighlightingPass createHighlightingPass(@NotNull PsiFile file, @NotNull final Editor editor) { - TextRange textRange = calculateRangeToProcess(editor); - if (textRange == null) return null; - - return new GeneralHighlightingPass(file.getProject(), file, editor.getDocument(), textRange.getStartOffset(), textRange.getEndOffset(), false); - } -} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/VisibleLineMarkersPassFactory.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/VisibleLineMarkersPassFactory.java index da70262698d2..1ebe9bbc6b08 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/VisibleLineMarkersPassFactory.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/VisibleLineMarkersPassFactory.java @@ -34,7 +34,7 @@ import org.jetbrains.annotations.Nullable; public class VisibleLineMarkersPassFactory extends VisibleHighlightingPassFactory implements TextEditorHighlightingPassFactory { public VisibleLineMarkersPassFactory(Project project, TextEditorHighlightingPassRegistrar highlightingPassRegistrar) { super(project); - highlightingPassRegistrar.registerTextEditorHighlightingPass(this, null, new int[]{Pass.UPDATE_VISIBLE}, false, Pass.VISIBLE_LINE_MARKERS); + highlightingPassRegistrar.registerTextEditorHighlightingPass(this, null, null, false, Pass.VISIBLE_LINE_MARKERS); } @NonNls diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightLevelUtil.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightLevelUtil.java index 9c09200608c3..4c426c9e864d 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightLevelUtil.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightLevelUtil.java @@ -23,11 +23,19 @@ import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; import org.jetbrains.annotations.NotNull; public class HighlightLevelUtil { private HighlightLevelUtil() { } + public enum AnalysisLevel { + HIGHLIGHT, HIGHLIGHT_AND_INSPECT + } + public static boolean shouldAnalyse(@NotNull PsiFile root, @NotNull AnalysisLevel analysisLevel) { + return analysisLevel == AnalysisLevel.HIGHLIGHT_AND_INSPECT ? shouldInspect(root) : shouldHighlight(root); + } + public static boolean shouldHighlight(@NotNull PsiElement psiRoot) { final HighlightingSettingsPerFile component = HighlightingSettingsPerFile.getInstance(psiRoot.getProject()); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/QuickFixAction.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/QuickFixAction.java index 3402d1c854ee..a0b76005f07b 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/QuickFixAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/QuickFixAction.java @@ -21,6 +21,7 @@ import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl; import com.intellij.codeInsight.daemon.impl.HighlightInfo; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInspection.HintAction; +import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.project.Project; @@ -28,10 +29,11 @@ import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiFile; +import com.intellij.util.Processor; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; -import java.util.Arrays; import java.util.Iterator; import java.util.List; @@ -52,23 +54,22 @@ public final class QuickFixAction { @Deprecated public static void registerQuickFixAction(HighlightInfo info, IntentionAction action, List options, String displayName) { - if (info == null || action == null) return; - final TextRange fixRange = new TextRange(info.startOffset, info.endOffset); - if (info.quickFixActionRanges == null) { - info.quickFixActionRanges = new ArrayList>(); - } - info.quickFixActionRanges.add(Pair.create(new HighlightInfo.IntentionActionDescriptor(action, options, displayName), fixRange)); - info.fixStartOffset = Math.min (info.fixStartOffset, fixRange.getStartOffset()); - info.fixEndOffset = Math.max (info.fixEndOffset, fixRange.getEndOffset()); + doregister(info, action, options, displayName, null, null); } - public static void registerQuickFixAction(HighlightInfo info, TextRange fixRange, IntentionAction action, final HighlightDisplayKey key) { + private static void doregister(HighlightInfo info, + IntentionAction action, + List options, + String displayName, + TextRange fixRange, + HighlightDisplayKey key) { if (info == null || action == null) return; if (fixRange == null) fixRange = new TextRange(info.startOffset, info.endOffset); if (info.quickFixActionRanges == null) { - info.quickFixActionRanges = new ArrayList>(); + info.quickFixActionRanges = ContainerUtil.createEmptyCOWList(); } - info.quickFixActionRanges.add(Pair.create(new HighlightInfo.IntentionActionDescriptor(action, key), fixRange)); + HighlightInfo.IntentionActionDescriptor desc = new HighlightInfo.IntentionActionDescriptor(action, options, displayName, null, key); + info.quickFixActionRanges.add(Pair.create(desc, fixRange)); info.fixStartOffset = Math.min (info.fixStartOffset, fixRange.getStartOffset()); info.fixEndOffset = Math.max (info.fixEndOffset, fixRange.getEndOffset()); if (action instanceof HintAction) { @@ -76,6 +77,10 @@ public final class QuickFixAction { } } + public static void registerQuickFixAction(HighlightInfo info, TextRange fixRange, IntentionAction action, final HighlightDisplayKey key) { + doregister(info, action, null, HighlightDisplayKey.getDisplayNameByKey(key), fixRange, key); + } + public static void unregisterQuickFixAction(HighlightInfo info, Condition condition) { for (Iterator> it = info.quickFixActionRanges.iterator(); it.hasNext();) { Pair pair = it.next(); @@ -89,24 +94,28 @@ public final class QuickFixAction { * Is invoked inside atomic action. */ @NotNull - public static List getAvailableActions(@NotNull Editor editor, @NotNull PsiFile file, final int passId) { - int offset = editor.getCaretModel().getOffset(); + public static List getAvailableActions(@NotNull final Editor editor, @NotNull final PsiFile file, final int passId) { + final int offset = editor.getCaretModel().getOffset(); final Project project = file.getProject(); - List result = new ArrayList(); - List infos = DaemonCodeAnalyzerImpl.getHighlightsAround(editor.getDocument(), project, offset); - int[] groups = passId == -1 ? null : new int[]{passId}; - for (HighlightInfo info : infos) { - addAvailableActionsForGroups(info, editor, file, result, groups, offset); - } + final List result = new ArrayList(); + DaemonCodeAnalyzerImpl.processHighlightsNearOffset(editor.getDocument(), project, HighlightSeverity.INFORMATION, offset, true, new Processor() { + public boolean process(HighlightInfo info) { + addAvailableActionsForGroups(info, editor, file, result, passId, offset); + return true; + } + }); return result; } - private static void addAvailableActionsForGroups(HighlightInfo info, Editor editor, PsiFile file, List outList, - int[] groups, + private static void addAvailableActionsForGroups(HighlightInfo info, + Editor editor, + PsiFile file, + List outList, + int group, int offset) { if (info == null || info.quickFixActionMarkers == null) return; - if (groups != null && Arrays.binarySearch(groups, info.group) < 0) return; + if (group != -1 && group != info.group) return; for (Pair pair : info.quickFixActionMarkers) { HighlightInfo.IntentionActionDescriptor actionInGroup = pair.first; RangeMarker range = pair.second; diff --git a/platform/lang-impl/src/com/intellij/injected/editor/DocumentWindowImpl.java b/platform/lang-impl/src/com/intellij/injected/editor/DocumentWindowImpl.java index fea09517897f..a0ca53b83f57 100644 --- a/platform/lang-impl/src/com/intellij/injected/editor/DocumentWindowImpl.java +++ b/platform/lang-impl/src/com/intellij/injected/editor/DocumentWindowImpl.java @@ -27,14 +27,12 @@ import com.intellij.openapi.editor.ex.*; import com.intellij.openapi.editor.impl.DocumentImpl; import com.intellij.openapi.editor.markup.MarkupModel; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.ProperTextRange; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.util.UserDataHolderBase; +import com.intellij.openapi.util.*; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiLanguageInjectionHost; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; import com.intellij.psi.impl.source.tree.injected.Place; +import com.intellij.util.Processor; import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -137,8 +135,9 @@ public class DocumentWindowImpl extends UserDataHolderBase implements Disposable return text.toString(); } + @NotNull @Override - public String getText(TextRange range) { + public String getText(@NotNull TextRange range) { return range.substring(getText()); } @@ -487,8 +486,8 @@ public class DocumentWindowImpl extends UserDataHolderBase implements Disposable public void clearLineModificationFlags() { } - public void removeRangeMarker(@NotNull RangeMarkerEx rangeMarker) { - myDelegate.removeRangeMarker(((RangeMarkerWindow)rangeMarker).getDelegate()); + public boolean removeRangeMarker(@NotNull RangeMarkerEx rangeMarker) { + return myDelegate.removeRangeMarker(((RangeMarkerWindow)rangeMarker).getDelegate()); } public void addRangeMarker(@NotNull RangeMarkerEx rangeMarker) { @@ -770,4 +769,17 @@ public class DocumentWindowImpl extends UserDataHolderBase implements Disposable public List getGuardedBlocks() { return Collections.emptyList(); } + + //todo convert injected RMs to host + public boolean processRangeMarkers(@NotNull Processor processor) { + return myDelegate.processRangeMarkers(processor); + } + + public boolean processRangeMarkersOverlappingWith(int start, int end, @NotNull Processor processor) { + return myDelegate.processRangeMarkersOverlappingWith(start, end, processor); + } + + public boolean processRangeMarkersOverlappingWith(int offset, @NotNull Processor processor) { + return myDelegate.processRangeMarkersOverlappingWith(offset, processor); + } } diff --git a/platform/lang-impl/src/com/intellij/injected/editor/FoldingModelWindow.java b/platform/lang-impl/src/com/intellij/injected/editor/FoldingModelWindow.java index cebf385b9f11..3f845c451a15 100644 --- a/platform/lang-impl/src/com/intellij/injected/editor/FoldingModelWindow.java +++ b/platform/lang-impl/src/com/intellij/injected/editor/FoldingModelWindow.java @@ -22,6 +22,7 @@ import com.intellij.openapi.editor.ex.FoldingModelEx; import com.intellij.openapi.editor.impl.FoldRegionImpl; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.TextRange; import org.jetbrains.annotations.NotNull; import java.awt.*; @@ -54,18 +55,6 @@ public class FoldingModelWindow implements FoldingModelEx{ return myDelegate.getFoldingPlaceholderAt(p); } - public FoldRegion[] getAllFoldRegionsIncludingInvalid() { - FoldRegion[] all = myDelegate.getAllFoldRegionsIncludingInvalid(); - List result = new ArrayList(); - for (FoldRegion region : all) { - FoldingRegionWindow window = region.getUserData(FOLD_REGION_WINDOW); - if (window != null && window.getEditor() == myEditorWindow) { - result.add(window); - } - } - return result.toArray(new FoldRegion[result.size()]); - } - public boolean intersectsRegion(int startOffset, int endOffset) { int hostStart = myDocumentWindow.injectedToHost(startOffset); int hostEnd = myDocumentWindow.injectedToHost(endOffset); @@ -74,7 +63,7 @@ public class FoldingModelWindow implements FoldingModelEx{ public FoldRegion addFoldRegion(int startOffset, int endOffset, @NotNull String placeholderText) { FoldRegion region = createFoldRegion(startOffset, endOffset, placeholderText, null); - return addFoldRegion(region) ? region : null; + return region == null || addFoldRegion(region) ? region : null; } public boolean addFoldRegion(@NotNull final FoldRegion region) { @@ -134,7 +123,9 @@ public class FoldingModelWindow implements FoldingModelEx{ private static final Key FOLD_REGION_WINDOW = Key.create("FOLD_REGION_WINDOW"); public FoldRegion createFoldRegion(int startOffset, int endOffset, @NotNull String placeholder, FoldingGroup group) { - FoldRegion hostRegion = myDelegate.createFoldRegion(myDocumentWindow.injectedToHost(startOffset), myDocumentWindow.injectedToHost(endOffset), placeholder, group); + TextRange hostRange = myDocumentWindow.injectedToHost(new TextRange(startOffset, endOffset)); + if (hostRange.getLength() < 2) return null; + FoldRegion hostRegion = myDelegate.createFoldRegion(hostRange.getStartOffset(), hostRange.getEndOffset(), placeholder, group); FoldingRegionWindow window = new FoldingRegionWindow(myDocumentWindow, myEditorWindow, (FoldRegionImpl)hostRegion); hostRegion.putUserData(FOLD_REGION_WINDOW, window); return window; diff --git a/platform/lang-impl/src/com/intellij/injected/editor/MarkupModelWindow.java b/platform/lang-impl/src/com/intellij/injected/editor/MarkupModelWindow.java index 2e97b329d840..5bf87ec22585 100644 --- a/platform/lang-impl/src/com/intellij/injected/editor/MarkupModelWindow.java +++ b/platform/lang-impl/src/com/intellij/injected/editor/MarkupModelWindow.java @@ -18,16 +18,20 @@ package com.intellij.injected.editor; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.ex.MarkupModelEx; -import com.intellij.openapi.editor.impl.HighlighterList; +import com.intellij.openapi.editor.ex.RangeHighlighterEx; import com.intellij.openapi.editor.impl.event.MarkupModelListener; import com.intellij.openapi.editor.markup.HighlighterTargetArea; import com.intellij.openapi.editor.markup.RangeHighlighter; import com.intellij.openapi.editor.markup.TextAttributes; +import com.intellij.openapi.editor.impl.Interval; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.UserDataHolderBase; import com.intellij.openapi.util.ProperTextRange; +import com.intellij.util.Processor; import org.jetbrains.annotations.NotNull; +import java.util.Iterator; + /** * @author cdr */ @@ -78,31 +82,47 @@ class MarkupModelWindow extends UserDataHolderBase implements MarkupModelEx { myHostModel.dispose(); } - public HighlighterList getHighlighterList() { - return myHostModel.getHighlighterList(); - } - public RangeHighlighter addPersistentLineHighlighter(final int line, final int layer, final TextAttributes textAttributes) { int hostLine = myDocument.injectedToHostLine(line); return myHostModel.addPersistentLineHighlighter(hostLine, layer, textAttributes); } - public boolean containsHighlighter(final RangeHighlighter highlighter) { + public boolean containsHighlighter(@NotNull final RangeHighlighter highlighter) { return myHostModel.containsHighlighter(highlighter); } - public void addMarkupModelListener(MarkupModelListener listener) { + public void addMarkupModelListener(@NotNull MarkupModelListener listener) { myHostModel.addMarkupModelListener(listener); } - public void removeMarkupModelListener(MarkupModelListener listener) { + public void removeMarkupModelListener(@NotNull MarkupModelListener listener) { myHostModel.removeMarkupModelListener(listener); } - public void setRangeHighlighterAttributes(final RangeHighlighter highlighter, final TextAttributes textAttributes) { + public void setRangeHighlighterAttributes(@NotNull final RangeHighlighter highlighter, final TextAttributes textAttributes) { myHostModel.setRangeHighlighterAttributes(highlighter, textAttributes); } + public boolean processHighlightsOverlappingWith(int start, int end, @NotNull Processor processor) { + //todo + return false; + } + + public Iterator iterator() { + // todo + return null; + } + + @NotNull + public Iterator iteratorFrom(@NotNull Interval interval) { + // todo convert + return myHostModel.iteratorFrom(interval); + } + public boolean sweep(int start, int end, @NotNull SweepProcessor sweepProcessor) { + // todo convert + return myHostModel.sweep(start, end, sweepProcessor); + } + } \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/injected/editor/RangeMarkerWindow.java b/platform/lang-impl/src/com/intellij/injected/editor/RangeMarkerWindow.java index 134eb1c6b5b1..47d411395509 100644 --- a/platform/lang-impl/src/com/intellij/injected/editor/RangeMarkerWindow.java +++ b/platform/lang-impl/src/com/intellij/injected/editor/RangeMarkerWindow.java @@ -56,6 +56,10 @@ public class RangeMarkerWindow implements RangeMarkerEx { return myHostMarker.isValid() && myDocumentWindow.isValid(); } + public boolean setValid(boolean value) { + return myHostMarker.setValid(value); + } + ////////////////////////////delegates public void setGreedyToLeft(final boolean greedy) { myHostMarker.setGreedyToLeft(greedy); @@ -91,4 +95,21 @@ public class RangeMarkerWindow implements RangeMarkerEx { public boolean isGreedyToLeft() { return myHostMarker.isGreedyToLeft(); } + + public int intervalStart() { + return getStartOffset(); + } + + public int intervalEnd() { + return getEndOffset(); + } + + public int setIntervalStart(int start) { + throw new IllegalStateException(); + } + + public int setIntervalEnd(int end) { + throw new IllegalStateException(); + } + } diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/DiffRangeMarker.java b/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/DiffRangeMarker.java index 6cdc48448eb4..885e2b2d97f0 100644 --- a/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/DiffRangeMarker.java +++ b/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/DiffRangeMarker.java @@ -36,6 +36,7 @@ class DiffRangeMarker extends RangeMarkerImpl { private DiffRangeMarker(DocumentEx document, int start, int end, RangeInvalidListener listener) { super(document, start, end); + registerInDocument(); myListener = listener; if (myListener != null) InvalidRangeDispatcher.addClient(document); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/DocumentEx.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/DocumentEx.java index 44f6c2e050b2..1aa39b8e864b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/DocumentEx.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/DocumentEx.java @@ -18,6 +18,7 @@ package com.intellij.openapi.editor.ex; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.RangeMarker; +import com.intellij.util.Processor; import org.jetbrains.annotations.NotNull; import java.util.List; @@ -48,8 +49,7 @@ public interface DocumentEx extends Document { void clearLineModificationFlags(); - - void removeRangeMarker(@NotNull RangeMarkerEx rangeMarker); + boolean removeRangeMarker(@NotNull RangeMarkerEx rangeMarker); void addRangeMarker(@NotNull RangeMarkerEx rangeMarker); boolean isInBulkUpdate(); @@ -58,6 +58,10 @@ public interface DocumentEx extends Document { @NotNull List getGuardedBlocks(); + + boolean processRangeMarkers(@NotNull Processor processor); + boolean processRangeMarkersOverlappingWith(int start, int end, @NotNull Processor processor); + boolean processRangeMarkersOverlappingWith(int offset, @NotNull Processor processor); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorMarkupModel.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorMarkupModel.java index 3e985189090c..0cdae1ae1a7a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorMarkupModel.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorMarkupModel.java @@ -39,7 +39,5 @@ public interface EditorMarkupModel extends MarkupModel { @NotNull ErrorStripTooltipRendererProvider getErrorStripTooltipRendererProvider(); - int getMinMarkHeight(); - void setMinMarkHeight(int minMarkHeight); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/FoldingModelEx.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/FoldingModelEx.java index 444f36828ab7..50fda1bc7810 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/FoldingModelEx.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/FoldingModelEx.java @@ -20,6 +20,7 @@ import com.intellij.openapi.editor.FoldingGroup; import com.intellij.openapi.editor.FoldingModel; import com.intellij.openapi.editor.markup.TextAttributes; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.awt.*; @@ -31,7 +32,6 @@ public interface FoldingModelEx extends FoldingModel { boolean isFoldingEnabled(); FoldRegion getFoldingPlaceholderAt(Point p); - FoldRegion[] getAllFoldRegionsIncludingInvalid(); boolean intersectsRegion(int startOffset, int endOffset); @@ -42,6 +42,7 @@ public interface FoldingModelEx extends FoldingModel { TextAttributes getPlaceholderAttributes(); FoldRegion[] fetchTopLevel(); - + + @Nullable FoldRegion createFoldRegion(int startOffset, int endOffset, @NotNull String placeholder, FoldingGroup group); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/MarkupModelEx.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/MarkupModelEx.java index 7cf8d2c819b7..556f282978f0 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/MarkupModelEx.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/MarkupModelEx.java @@ -15,28 +15,38 @@ */ package com.intellij.openapi.editor.ex; -import com.intellij.openapi.editor.impl.HighlighterList; +import com.intellij.openapi.editor.impl.Interval; import com.intellij.openapi.editor.impl.event.MarkupModelListener; import com.intellij.openapi.editor.markup.MarkupModel; import com.intellij.openapi.editor.markup.RangeHighlighter; import com.intellij.openapi.editor.markup.TextAttributes; -import org.jetbrains.annotations.Nullable; +import com.intellij.util.Processor; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Iterator; /** * @author max */ -public interface MarkupModelEx extends MarkupModel { +public interface MarkupModelEx extends MarkupModel, Iterable { void dispose(); - @Nullable - HighlighterList getHighlighterList(); - - @Nullable RangeHighlighter addPersistentLineHighlighter(int lineNumber, int layer, TextAttributes textAttributes); - boolean containsHighlighter(RangeHighlighter highlighter); + boolean containsHighlighter(@NotNull RangeHighlighter highlighter); - void addMarkupModelListener(MarkupModelListener listener); - void removeMarkupModelListener(MarkupModelListener listener); + void addMarkupModelListener(@NotNull MarkupModelListener listener); + void removeMarkupModelListener(@NotNull MarkupModelListener listener); - void setRangeHighlighterAttributes(final RangeHighlighter highlighter, TextAttributes textAttributes); + void setRangeHighlighterAttributes(@NotNull RangeHighlighter highlighter, TextAttributes textAttributes); + + boolean processHighlightsOverlappingWith(int start, int end, @NotNull Processor processor); + @NotNull + Iterator iteratorFrom(@NotNull Interval interval); + + interface SweepProcessor { + boolean process(int offset, T interval, boolean atStart, Collection overlappingIntervals); + } + + boolean sweep(int start, int end, @NotNull final SweepProcessor sweepProcessor); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/RangeHighlighterEx.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/RangeHighlighterEx.java index f2c3a261ce45..c4f1629337f8 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/RangeHighlighterEx.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/RangeHighlighterEx.java @@ -25,8 +25,19 @@ package com.intellij.openapi.editor.ex; import com.intellij.openapi.editor.markup.RangeHighlighter; +import com.intellij.openapi.editor.markup.TextAttributes; +import org.jetbrains.annotations.NotNull; -public interface RangeHighlighterEx extends RangeHighlighter { +public interface RangeHighlighterEx extends RangeHighlighter, RangeMarkerEx { boolean isAfterEndOfLine(); void setAfterEndOfLine(boolean val); + + int getAffectedAreaStartOffset(); + + int getAffectedAreaEndOffset(); + long getId(); + + void setTextAttributes(TextAttributes textAttributes); + + void changeAttributesInBatch(@NotNull Runnable change); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/RangeMarkerEx.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/RangeMarkerEx.java index 73a83e0db2bd..7ba51ea1148d 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/RangeMarkerEx.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/RangeMarkerEx.java @@ -26,9 +26,11 @@ package com.intellij.openapi.editor.ex; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.editor.impl.MutableInterval; +import com.intellij.openapi.util.Segment; -public interface RangeMarkerEx extends RangeMarker { +public interface RangeMarkerEx extends RangeMarker, MutableInterval, Segment { void documentChanged(DocumentEvent e); long getId(); -} \ No newline at end of file +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/BorderEffect.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/BorderEffect.java index 1c0ddfa43312..8b59c528c319 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/BorderEffect.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/BorderEffect.java @@ -16,6 +16,7 @@ package com.intellij.openapi.editor.impl; import com.intellij.openapi.editor.LogicalPosition; +import com.intellij.openapi.editor.ex.RangeHighlighterEx; import com.intellij.openapi.editor.highlighter.EditorHighlighter; import com.intellij.openapi.editor.markup.EffectType; import com.intellij.openapi.editor.markup.RangeHighlighter; @@ -66,7 +67,7 @@ public class BorderEffect { public void paintHighlighters(RangeHighlighter[] highlighterList) { for (RangeHighlighter aHighlighterList : highlighterList) { - RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl)aHighlighterList; + RangeHighlighterEx rangeHighlighter = (RangeHighlighterEx)aHighlighterList; if (rangeHighlighter.isValid()) { TextAttributes textAttributes = rangeHighlighter.getTextAttributes(); if (isBorder(textAttributes) && intersectsRange(rangeHighlighter)) paintBorder(rangeHighlighter); @@ -80,7 +81,7 @@ public class BorderEffect { EffectType.BOXED == textAttributes.getEffectType(); } - private void paintBorder(RangeHighlighterImpl rangeHighlighter) { + private void paintBorder(RangeHighlighterEx rangeHighlighter) { paintBorder(rangeHighlighter.getTextAttributes().getEffectColor(), rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset()); @@ -90,7 +91,7 @@ public class BorderEffect { paintBorder(myGraphics, myEditor, startOffset, endOffset, color); } - private boolean intersectsRange(RangeHighlighterImpl rangeHighlighter) { + private boolean intersectsRange(RangeHighlighterEx rangeHighlighter) { return myRange.contains(rangeHighlighter.getAffectedAreaStartOffset()) || myRange.contains(rangeHighlighter.getAffectedAreaEndOffset()); } @@ -102,7 +103,7 @@ public class BorderEffect { highlighter.createIterator(startOffset), BOX_FILTER); iterator.init(myRange); - for (; !iterator.atEnd();) { + while (!iterator.atEnd()) { iterator.advance(); paintBorder(myGraphics, myEditor, iterator.getStart(), iterator.getEnd(), iterator.getTextAttributes().getEffectColor()); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java index 644be0d7c09c..e8cfe81c2a81 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java @@ -35,8 +35,8 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.*; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.ArrayUtil; -import com.intellij.util.ConcurrencyUtil; import com.intellij.util.LocalTimeCounter; +import com.intellij.util.Processor; import com.intellij.util.containers.ConcurrentHashMap; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.text.CharArrayUtil; @@ -47,14 +47,16 @@ import org.jetbrains.annotations.TestOnly; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.lang.ref.WeakReference; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.concurrent.ConcurrentMap; public class DocumentImpl extends UserDataHolderBase implements DocumentEx { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.DocumentImpl"); private final List myDocumentListeners = new ArrayList(); - private final WeakHashMap myRangeMarkers = new WeakHashMap(); + private final RangeMarkerTree myRangeMarkers = new RangeMarkerTree(this); private final List myGuardedBlocks = new ArrayList(); private ReadonlyFragmentModificationHandler myReadonlyFragmentModificationHandler; @@ -207,23 +209,26 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { return !myIsReadOnly; } - public void removeRangeMarker(@NotNull RangeMarkerEx rangeMarker) { + public boolean removeRangeMarker(@NotNull RangeMarkerEx rangeMarker) { ApplicationManagerEx.getApplicationEx().assertReadAccessToDocumentsAllowed(); synchronized(myRangeMarkers) { - myRangeMarkers.remove(rangeMarker); + return myRangeMarkers.remove(rangeMarker); } } public void addRangeMarker(@NotNull RangeMarkerEx rangeMarker) { ApplicationManagerEx.getApplicationEx().assertReadAccessToDocumentsAllowed(); synchronized(myRangeMarkers) { - myRangeMarkers.put(rangeMarker, null); + RangeMarkerImpl marker = (RangeMarkerImpl)rangeMarker; + marker.setValid(true); + marker.myNode = myRangeMarkers.add(rangeMarker); + myRangeMarkers.checkMax(true); } } @TestOnly - public Collection getRangeMarkers() { - return myRangeMarkers.keySet(); + public int getRangeMarkersSize() { + return myRangeMarkers.size(); } @NotNull @@ -291,8 +296,7 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { @NotNull public RangeMarker createRangeMarker(int startOffset, int endOffset) { - ApplicationManagerEx.getApplicationEx().assertReadAccessToDocumentsAllowed(); - return new RangeMarkerImpl(this, startOffset, endOffset); + return createRangeMarker(startOffset, endOffset, false); } @NotNull @@ -301,9 +305,11 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { if (!(0 <= startOffset && startOffset <= endOffset && endOffset <= getTextLength())) { LOG.error("Incorrect offsets startOffset=" + startOffset + ", endOffset=" + endOffset + ", text length=" + getTextLength()); } - return surviveOnExternalChange - ? new PersistentRangeMarker(this, startOffset, endOffset) - : new RangeMarkerImpl(this, startOffset, endOffset); + RangeMarkerImpl rangeMarker = surviveOnExternalChange + ? new PersistentRangeMarker(this, startOffset, endOffset) + : new RangeMarkerImpl(this, startOffset, endOffset); + rangeMarker.registerInDocument(); + return rangeMarker; } public long getModificationStamp() { @@ -477,7 +483,7 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { if (ShutDownTracker.isShutdownHookRunning()) { return; // suppress events in shutdown hook } - try{ + try { if (LOG.isDebugEnabled()) LOG.debug(event.toString()); myLineSet.changedUpdate(event); @@ -501,31 +507,37 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { } private void updateRangeMarkers(final DocumentEvent event) { - synchronized(myRangeMarkers) { - for(Iterator rangeMarkerIterator = myRangeMarkers.keySet().iterator(); rangeMarkerIterator.hasNext();) { - try { - final RangeMarkerEx rangeMarker = rangeMarkerIterator.next(); + /* + synchronized (myRangeMarkers) { + myRangeMarkers.updateMarkersOnChange(event); + */ - if (rangeMarker != null && rangeMarker.isValid()) { - if (event.getOffset() <= rangeMarker.getEndOffset()) { - rangeMarker.documentChanged(event); - if (!rangeMarker.isValid()) { - rangeMarkerIterator.remove(); - if (myGuardedBlocks.remove(rangeMarker)) { - LOG.error("Guarded blocks should stay valid: "+rangeMarker); - } - } - } - } - else { - rangeMarkerIterator.remove(); - } - } - catch (Exception e) { - LOG.error(e); - } - } + //for (Iterator rangeMarkerIterator = myRangeMarkers.keySet().iterator(); rangeMarkerIterator.hasNext();) { + // try { + // final RangeMarkerEx rangeMarker = rangeMarkerIterator.next(); + // + // if (rangeMarker != null && rangeMarker.isValid()) { + // if (event.getOffset() <= rangeMarker.getEndOffset()) { + // rangeMarker.documentChanged(event); + // if (!rangeMarker.isValid()) { + // rangeMarkerIterator.remove(); + // if (myGuardedBlocks.remove(rangeMarker)) { + // LOG.error("Guarded blocks should stay valid: " + rangeMarker); + // } + // } + // } + // } + // else { + // rangeMarkerIterator.remove(); + // } + // } + // catch (Exception e) { + // LOG.error(e); + // } + //} + /* } + */ } public String getText() { @@ -704,8 +716,14 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { MarkupModel model = myProjectToMarkupModelMap.get(project); if (create && model == null) { - model = ConcurrencyUtil.cacheOrGet(myProjectToMarkupModelMap, project, new MarkupModelImpl(this)); - documentMarkupModelManager.registerDocument(this); + synchronized (lock) { + model = myProjectToMarkupModelMap.get(project); + if (model == null) { + model = new MarkupModelImpl(this); + myProjectToMarkupModelMap.put(project, model); + documentMarkupModelManager.registerDocument(this); + } + } } return model; @@ -774,5 +792,17 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { private static DocumentBulkUpdateListener getPublisher() { return DocumentBulkUpdateListenerHolder.ourBulkChangePublisher; } + + public boolean processRangeMarkers(@NotNull Processor processor) { + return myRangeMarkers.process(processor); + } + + public boolean processRangeMarkersOverlappingWith(int start, int end, @NotNull Processor processor) { + return myRangeMarkers.processOverlappingWith(start, end, processor); + } + + public boolean processRangeMarkersOverlappingWith(int offset, @NotNull Processor processor) { + return myRangeMarkers.processOverlappingWith(offset, processor); + } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java index c10ba86ddc2a..9408af1f42ba 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java @@ -103,7 +103,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse public Dimension getPreferredSize() { int w = getLineNumberAreaWidth() + getLineMarkerAreaWidth() + getFoldingAreaWidth() + getAnnotationsAreaWidth(); - return new Dimension(w, myEditor.getPreferredSize().height); + return new Dimension(w, myEditor.getPreferredHeight()); } protected void setUI(ComponentUI newUI) { @@ -339,17 +339,15 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse void process(RangeHighlighter highlighter); } - private void processRangeHighlighters(RangeHighlighterProcessor p, int startOffset, int endOffset) { + private void processRangeHighlighters(RangeHighlighterProcessor processor, int startOffset, int endOffset) { final MarkupModelEx docMarkup = (MarkupModelEx)myEditor.getDocument().getMarkupModel(myEditor.getProject()); - final HighlighterList docList = docMarkup.getHighlighterList(); - Iterator docHighlighters = docList != null ? docList.getHighlighterIterator() : null; + Iterator docHighlighters = docMarkup.iterator(); final MarkupModelEx editorMarkup = (MarkupModelEx)myEditor.getMarkupModel(); - final HighlighterList editorList = editorMarkup.getHighlighterList(); - Iterator editorHighlighters = editorList != null ? editorList.getHighlighterIterator() : null; + Iterator editorHighlighters = editorMarkup.iterator(); - RangeHighlighterImpl lastDocHighlighter = null; - RangeHighlighterImpl lastEditorHighlighter = null; + RangeHighlighterEx lastDocHighlighter = null; + RangeHighlighterEx lastEditorHighlighter = null; while (true) { if (lastDocHighlighter == null && docHighlighters != null && docHighlighters.hasNext()) { @@ -380,7 +378,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse if (lastDocHighlighter == null && lastEditorHighlighter == null) return; - final RangeHighlighterImpl lowerHighlighter; + final RangeHighlighterEx lowerHighlighter; if (less(lastDocHighlighter, lastEditorHighlighter)) { lowerHighlighter = lastDocHighlighter; @@ -401,7 +399,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse if (endLineIndex < 0 || endLineIndex >= myEditor.getDocument().getLineCount()) continue; if (lowerHighlighter.getEditorFilter().avaliableIn(myEditor)) { - p.process(lowerHighlighter); + processor.process(lowerHighlighter); } } } @@ -452,7 +450,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse processRangeHighlighters(new RangeHighlighterProcessor() { public void process(RangeHighlighter highlighter) { GutterIconRenderer renderer = highlighter.getGutterIconRenderer(); - if (renderer == null || !highlighter.getEditorFilter().avaliableIn(myEditor)) return; + if (renderer == null) return; int startOffset = highlighter.getStartOffset(); int line = myEditor.getDocument().getLineNumber(startOffset); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java index b9a1fd83d757..72c000b05425 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java @@ -47,7 +47,6 @@ import com.intellij.openapi.editor.ex.util.EditorUtil; import com.intellij.openapi.editor.ex.util.EmptyEditorHighlighter; import com.intellij.openapi.editor.highlighter.EditorHighlighter; import com.intellij.openapi.editor.highlighter.HighlighterClient; -import com.intellij.openapi.editor.impl.event.MarkupModelEvent; import com.intellij.openapi.editor.impl.event.MarkupModelListener; import com.intellij.openapi.editor.impl.softwrap.SoftWrapDrawingType; import com.intellij.openapi.editor.impl.softwrap.SoftWrapHelper; @@ -266,28 +265,29 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi myMouseMotionListeners = ContainerUtil.createEmptyCOWList(); myMarkupModelListener = new MarkupModelListener() { - public void rangeHighlighterChanged(MarkupModelEvent event) { - assertIsDispatchThread(); - RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl)event.getHighlighter(); - if (rangeHighlighter.isValid()) { - int start = rangeHighlighter.getAffectedAreaStartOffset(); - int end = rangeHighlighter.getAffectedAreaEndOffset(); - int startLine = myDocument.getLineNumber(start); - int endLine = myDocument.getLineNumber(end); - repaintLines(Math.max(0, startLine - 1), Math.min(endLine + 1, getDocument().getLineCount())); - } - else { - repaint(0, getDocument().getTextLength()); - } - ((EditorMarkupModelImpl)getMarkupModel()).repaint(); + public void afterAdded(@NotNull RangeHighlighterEx highlighter) { + int start = highlighter.getAffectedAreaStartOffset(); + int end = highlighter.getAffectedAreaEndOffset(); + int startLine = myDocument.getLineNumber(start); + int endLine = myDocument.getLineNumber(end); + repaintLines(Math.max(0, startLine - 1), Math.min(endLine + 1, getDocument().getLineCount())); ((EditorMarkupModelImpl)getMarkupModel()).markDirtied(); - GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer(); + ((EditorMarkupModelImpl)getMarkupModel()).repaint(start, end); + GutterIconRenderer renderer = highlighter.getGutterIconRenderer(); if (renderer != null) { updateGutterSize(); } updateCaretCursor(); } + + public void beforeRemoved(@NotNull RangeHighlighterEx highlighter) { + afterAdded(highlighter); + } + + public void attributesChanged(@NotNull RangeHighlighterEx highlighter) { + afterAdded(highlighter); + } }; ((MarkupModelEx)myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener); @@ -675,7 +675,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi myPanel.addComponentListener(new ComponentAdapter() { public void componentResized(ComponentEvent e) { - myMarkupModel.repaint(); + myMarkupModel.repaint(0, myDocument.getTextLength()); } }); } @@ -768,7 +768,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } public EditorHighlighter getHighlighter() { - assertIsDispatchThread(); + assertReadAccess(); return myHighlighter; } @@ -1351,7 +1351,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi myEditorComponent.setSize(dim); myEditorComponent.fireResized(); - myMarkupModel.repaint(); + myMarkupModel.repaint(0, myDocument.getTextLength()); } } @@ -2901,7 +2901,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi int lineIndex = myDocument.getLineNumber(offset); LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount()); - if (softWrapAware) { + if (softWrapAware && getSoftWrapModel().isSoftWrappingEnabled()) { int column = calcColumnNumber(offset, lineIndex, false); return mySoftWrapModel.adjustLogicalPosition(new LogicalPosition(lineIndex, column), offset).line; } @@ -3024,7 +3024,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } // if (myMousePressedInsideSelection) getSelectionModel().removeSelection(); - final FoldRegion region = ((FoldingModelEx)getFoldingModel()).getFoldingPlaceholderAt(e.getPoint()); + final FoldRegion region = getFoldingModel().getFoldingPlaceholderAt(e.getPoint()); if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) { getFoldingModel().runBatchFoldingOperation(new Runnable() { public void run() { @@ -4831,14 +4831,6 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi myOldEndLine = offsetToLogicalPosition(e.getOffset() + e.getOldLength()).line; } - private int getVisualPositionLine(int offset) { - // Do round up of offset to the nearest line start (valid since we need only line) - // This is needed for preventing access to lexer editor highlighter regions [that are reset] during bulk mode operation - - int line = calcLogicalLineNumber(offset); - return logicalToVisualLine(line); - } - public synchronized void update(int startLine, int newEndLine, int oldEndLine) { final int lineWidthSize = myLineWidths.size(); if (lineWidthSize == 0 || myDocument.getTextLength() <= 0) { diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java index 80e7fa65a501..ee4b317c9be7 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java @@ -38,18 +38,15 @@ import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.actionSystem.DocCommandGroupId; import com.intellij.openapi.editor.ex.*; import com.intellij.openapi.editor.markup.ErrorStripeRenderer; -import com.intellij.openapi.editor.markup.MarkupModel; import com.intellij.openapi.editor.markup.RangeHighlighter; import com.intellij.openapi.util.IconLoader; import com.intellij.ui.ColorUtil; import com.intellij.ui.PopupHandler; -import com.intellij.util.SmartList; -import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.Processor; import com.intellij.util.ui.ButtonlessScrollBarUI; import com.intellij.util.ui.UIUtil; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; @@ -65,19 +62,17 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark private static final TooltipGroup ERROR_STRIPE_TOOLTIP_GROUP = new TooltipGroup("ERROR_STRIPE_TOOLTIP_GROUP", 0); private static final Icon ERRORS_FOUND_ICON = IconLoader.getIcon("/general/errorsFound.png"); - + private static final int ERROR_ICON_WIDTH = ERRORS_FOUND_ICON.getIconWidth(); + private static final int ERROR_ICON_HEIGHT = ERRORS_FOUND_ICON.getIconHeight(); + private static final int PREFERRED_WIDTH = ERRORS_FOUND_ICON.getIconWidth() + 4; private final EditorImpl myEditor; private ErrorStripeRenderer myErrorStripeRenderer = null; private final List myErrorMarkerListeners = new ArrayList(); private ErrorStripeListener[] myCachedErrorMarkerListeners = null; - private List myCachedSortedHighlighters = null; - private final MarkSpots myMarkSpots = new MarkSpots(); - private int myScrollBarHeight; - private static final Comparator LAYER_COMPARATOR = new Comparator() { - public int compare(final RangeHighlighter o1, final RangeHighlighter o2) { - return o1.getLayer() - o2.getLayer(); - } - }; + + private int myEditorScrollbarTop = -1; + private int myEditorTargetHeight = -1; + private int myEditorSourceHeight = -1; @NotNull private ErrorStripTooltipRendererProvider myTooltipRendererProvider = new BasicTooltipRendererProvider(); @@ -96,397 +91,104 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark private int offsetToLine(int offset) { final Document document = myEditor.getDocument(); if (offset > document.getTextLength()) { - offset = document.getTextLength(); + return document.getLineCount(); } - final int lineNumber = document.getLineNumber(offset); - return myEditor.logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line; + return myEditor.offsetToVisualLine(offset); } - private static class MarkSpot { - - private final int yStart; - - private int yEnd; - - // sorted by layers from bottom to top - private RangeHighlighter[] highlighters = RangeHighlighter.EMPTY_ARRAY; - - private MarkSpot(final int yStart, final int yEnd) { - this.yStart = yStart; - this.yEnd = yEnd; - } - - private boolean near(MouseEvent e, double width) { - final int x = e.getX(); - final int y = e.getY(); - return 0 <= x && x < width && yStart - getMinHeight() <= y && y < yEnd + getMinHeight(); - } - - } - - public static int getMinHeight() { + private static int getMinHeight() { return myMinMarkHeight; } - private class MarkSpots { + private void recalcEditorDimensions() { + EditorImpl.MyScrollBar scrollBar = myEditor.getVerticalScrollBar(); + int scrollBarHeight = scrollBar.getSize().height; - private List myWideSpots; - private List myNarrowSpots; + myEditorScrollbarTop = scrollBar.getDecScrollButtonHeight()/* + 1*/; + int editorScrollbarBottom = scrollBar.getIncScrollButtonHeight(); + myEditorTargetHeight = scrollBarHeight - myEditorScrollbarTop - editorScrollbarBottom; + myEditorSourceHeight = myEditor.getPreferredHeight(); + } - private int myEditorScrollbarTop = -1; + private static class PositionedStripe { + private final Color color; + private final int yStart; + private int yEnd; + private final boolean thin; + private final int layer; - private int myEditorTargetHeight = -1; - - private int myEditorSourceHeight = -1; - - private void recalcEditorDimensions() { - EditorImpl.MyScrollBar scrollBar = myEditor.getVerticalScrollBar(); - myEditorScrollbarTop = scrollBar.getDecScrollButtonHeight() + 1; - int bottom = scrollBar.getIncScrollButtonHeight(); - myEditorTargetHeight = myScrollBarHeight - myEditorScrollbarTop - bottom; - myEditorSourceHeight = myEditor.getPreferredSize().height; + private PositionedStripe(Color color, int yStart, int yEnd, boolean thin, int layer) { + this.color = color; + this.yStart = yStart; + this.yEnd = yEnd; + this.thin = thin; + this.layer = layer; } + } - private void clear() { - myWideSpots = null; - myNarrowSpots = null; - myEditorScrollbarTop = -1; - myEditorSourceHeight = -1; - myEditorTargetHeight = -1; + public boolean showToolTipByMouseMove(final MouseEvent e, final double width) { + Set highlighters = new THashSet(); + + getNearestHighlighters(this, e, width, highlighters); + getNearestHighlighters((MarkupModelEx)myEditor.getDocument().getMarkupModel(getEditor().getProject()), e, width, highlighters); + if (highlighters.isEmpty()) return false; + TooltipRenderer bigRenderer = myTooltipRendererProvider.calcTooltipRenderer(highlighters); + if (bigRenderer != null) { + showTooltip(e, bigRenderer); + return true; } + return false; + } - public boolean showToolTipByMouseMove(final MouseEvent e, final double width) { - recalcMarkSpots(); - final List nearestMarkSpots = getNearestMarkSpots(e, width); - if (nearestMarkSpots.isEmpty()) return false; - Set highlighters = new THashSet(nearestMarkSpots.size() + 4); - for (MarkSpot markSpot : nearestMarkSpots) { - ContainerUtil.addAll(highlighters, markSpot.highlighters); + private RangeHighlighter getNearestRangeHighlighter(final MouseEvent e, final int width) { + List highlighters = new ArrayList(); + getNearestHighlighters(this, e, width, highlighters); + getNearestHighlighters((MarkupModelEx)myEditor.getDocument().getMarkupModel(myEditor.getProject()), e, width, highlighters); + RangeHighlighter nearestMarker = null; + int yPos = 0; + for (RangeHighlighter highlighter : highlighters) { + final int newYPos = offsetToYPosition(highlighter.getStartOffset()); + + if (nearestMarker == null || Math.abs(yPos - e.getY()) > Math.abs(newYPos - e.getY())) { + nearestMarker = highlighter; + yPos = newYPos; } - TooltipRenderer bigRenderer = myTooltipRendererProvider.calcTooltipRenderer(highlighters); - if (bigRenderer != null) { - showTooltip(e, bigRenderer); + } + return nearestMarker; + } + + private void getNearestHighlighters(MarkupModelEx markupModel, MouseEvent e, final double width, final Collection nearest) { + if (0 > e.getX() || e.getX() >= width) return; + int startOffset = yPositionToOffset(e.getY()-getMinHeight(), true); + int endOffset = yPositionToOffset(e.getY()+getMinHeight(), false); + markupModel.processHighlightsOverlappingWith(startOffset, endOffset, new Processor() { + public boolean process(RangeHighlighterEx highlighter) { + if (highlighter.getErrorStripeMarkColor() != null) nearest.add(highlighter); return true; } - return false; - } - - private class PositionedRangeHighlighter { - - private final RangeHighlighter highlighter; - - private final int yStart; - - private final int yEnd; - - private PositionedRangeHighlighter(final RangeHighlighter highlighter, final int yStart, final int yEnd) { - this.highlighter = highlighter; - this.yStart = yStart; - this.yEnd = yEnd; - } - - @SuppressWarnings({"HardCodedStringLiteral"}) - public String toString() { - return "PR[" + yStart + "-" + yEnd + ")"; - } - - } - - private PositionedRangeHighlighter getPositionedRangeHighlighter(RangeHighlighter mark) { - int visStartLine = offsetToLine(mark.getStartOffset()); - int visEndLine = offsetToLine(mark.getEndOffset()); - int yStartPosition = visibleLineToYPosition(visStartLine); - int yEndPosition = visibleLineToYPosition(visEndLine); - if (yEndPosition - yStartPosition < getMinMarkHeight()) { - yEndPosition = yStartPosition + getMinMarkHeight(); - } - return new PositionedRangeHighlighter(mark, yStartPosition, yEndPosition); - } - - private int visibleLineToYPosition(int lineNumber) { - if (myEditorScrollbarTop == -1) { - recalcEditorDimensions(); - } - if (myEditorSourceHeight < myEditorTargetHeight) { - return myEditorScrollbarTop + lineNumber * myEditor.getLineHeight(); - } - else { - final int lineCount = myEditorSourceHeight / myEditor.getLineHeight(); - return myEditorScrollbarTop + (int)((float)lineNumber / lineCount * myEditorTargetHeight); - } - } - - private void recalcMarkSpots() { - if (myWideSpots != null) return; - myWideSpots = new ArrayList(); - myNarrowSpots = new ArrayList(); - - buildSpots(false, myWideSpots); - buildSpots(true, myNarrowSpots); - } - - private void buildSpots(boolean narrow, List spots) { - final List sortedHighlighters = getSortedHighlighters(); - - if (sortedHighlighters.isEmpty()) return; - Queue startQueue = - new PriorityQueue(5, new Comparator() { - public int compare(final PositionedRangeHighlighter o1, final PositionedRangeHighlighter o2) { - return o1.yStart - o2.yStart; - } - }); - Queue endQueue = - new PriorityQueue(5, new Comparator() { - public int compare(final PositionedRangeHighlighter o1, final PositionedRangeHighlighter o2) { - return o1.yEnd - o2.yEnd; - } - }); - int index = 0; - MarkSpot currentSpot = null; - while (!startQueue.isEmpty() || !endQueue.isEmpty() || index != sortedHighlighters.size()) { - LOG.assertTrue(startQueue.size() == endQueue.size()); - - final PositionedRangeHighlighter positionedMark; - boolean addingNew; - if (index != sortedHighlighters.size()) { - RangeHighlighter mark = sortedHighlighters.get(index); - if (!mark.isValid() || mark.getErrorStripeMarkColor() == null) { - sortedHighlighters.remove(index); - continue; - } - if (mark.isThinErrorStripeMark() != narrow) { - index++; - continue; - } - - PositionedRangeHighlighter positioned = getPositionedRangeHighlighter(mark); - if (!endQueue.isEmpty() && endQueue.peek().yEnd <= positioned.yStart) { - positionedMark = endQueue.peek(); - addingNew = false; - } - else { - positionedMark = positioned; - addingNew = true; - } - } - else if (!endQueue.isEmpty()) { - positionedMark = endQueue.peek(); - addingNew = false; - } - else { - LOG.error("cant be"); - return; - } - - if (addingNew) { - if (currentSpot == null) { - currentSpot = new MarkSpot(positionedMark.yStart, -1); - } - else { - currentSpot.yEnd = positionedMark.yStart; - if (currentSpot.yEnd != currentSpot.yStart) { - spitOutMarkSpot(currentSpot, startQueue, spots); - } - currentSpot = new MarkSpot(positionedMark.yStart, -1); - } - while (index != sortedHighlighters.size()) { - RangeHighlighter mark = sortedHighlighters.get(index); - if (mark.isThinErrorStripeMark() == narrow) { - PositionedRangeHighlighter positioned = getPositionedRangeHighlighter(mark); - if (positioned.yStart != positionedMark.yStart) break; - startQueue.add(positioned); - endQueue.add(positioned); - } - - index++; - } - } - else { - currentSpot.yEnd = positionedMark.yEnd; - spitOutMarkSpot(currentSpot, startQueue, spots); - currentSpot = new MarkSpot(positionedMark.yEnd, -1); - while (!endQueue.isEmpty() && endQueue.peek().yEnd == positionedMark.yEnd) { - final PositionedRangeHighlighter highlighter = endQueue.remove(); - for (Iterator iterator = startQueue.iterator(); iterator.hasNext();) { - PositionedRangeHighlighter positioned = iterator.next(); - if (positioned == highlighter) { - iterator.remove(); - break; - } - } - } - if (startQueue.isEmpty()) { - currentSpot = null; - } - } - } - } - - private void spitOutMarkSpot(final MarkSpot currentSpot, - final Queue startQueue, - List spots) { - spots.add(currentSpot); - currentSpot.highlighters = new RangeHighlighter[startQueue.size()]; - int i =0; - for (PositionedRangeHighlighter positioned : startQueue) { - currentSpot.highlighters[i++] = positioned.highlighter; - } - Arrays.sort(currentSpot.highlighters, LAYER_COMPARATOR); - } - - private void repaint(Graphics g, final int width) { - recalcMarkSpots(); - - int offset = 3; - paintSpots(g, offset, width, myWideSpots); - - int narrow = width / 2 + 1; - paintSpots(g, offset + narrow + 1, narrow, myNarrowSpots); - } - - private void paintSpots(Graphics g, int x, int width, List spots) { - for (int i = 0; i < spots.size(); i++) { - MarkSpot markSpot = spots.get(i); - - int yStart = markSpot.yStart; - RangeHighlighter mark = markSpot.highlighters[markSpot.highlighters.length - 1]; - - int yEnd = markSpot.yEnd; - - final Color color = mark.getErrorStripeMarkColor(); - - - if (color == null) return; - g.setColor(color); - g.fillRect(x + 1, yStart, width - 2, yEnd - yStart); - - - if (true) {// mark.isThinErrorStripeMark()) { - paintPullDecorations(g, i, markSpot, yStart, yEnd, color, x, width, spots); - } - else { - paintPushDecorations(g, i, markSpot, yStart, yEnd, color, x, width, spots); - } - - } - } - - private void paintPullDecorations(Graphics g, - int i, - MarkSpot markSpot, - int yStart, - int yEnd, - Color color, - int x, - int paintWidth, List spots) { - Color brighter = color.brighter(); - Color darker = ColorUtil.shift(color, 0.75); - - g.setColor(brighter); - //left - UIUtil.drawLine(g, x, yStart, x, yEnd - 1); - if (i == 0 || !isAdjacent(spots.get(i - 1), markSpot) || wider(markSpot, spots.get(i - 1))) { - //top decoration - UIUtil.drawLine(g, x + 1, yStart, x + paintWidth - 2, yStart); - } - g.setColor(darker); - if (i == spots.size() - 1 || !isAdjacent(markSpot, spots.get(i + 1)) || wider(markSpot, spots.get(i + 1))) { - // bottom decoration - UIUtil.drawLine(g, x + 1, yEnd - 1, x + paintWidth - 2, yEnd - 1); - } - //right - UIUtil.drawLine(g, x + paintWidth - 2, yStart, x + paintWidth - 2, yEnd - 1); - } - - private void paintPushDecorations(Graphics g, - int i, - MarkSpot markSpot, - int yStart, - int yEnd, - Color color, - int x, - int paintWidth, List spots) { - Color brighter = ColorUtil.shift(color, 0.75); - - g.setColor(brighter); - //left - UIUtil.drawLine(g, x, yStart, x, yEnd - 1); - if (i == 0 || !isAdjacent(spots.get(i - 1), markSpot) || wider(markSpot, spots.get(i - 1))) { - //top decoration - UIUtil.drawLine(g, x + 1, yStart, x + paintWidth - 2, yStart); - } - } - - - private boolean isAdjacent(MarkSpot markTop, MarkSpot markBottom) { - return markTop.yEnd >= markBottom.yStart; - } - - private boolean wider(MarkSpot markTop, MarkSpot markBottom) { - final RangeHighlighter highlighterTop = markTop.highlighters[markTop.highlighters.length - 1]; - final RangeHighlighter highlighterBottom = markBottom.highlighters[markBottom.highlighters.length - 1]; - return !highlighterTop.isThinErrorStripeMark() && highlighterBottom.isThinErrorStripeMark(); - } - - public void doClick(final MouseEvent e, final int width) { - recalcMarkSpots(); - RangeHighlighter marker = getNearestRangeHighlighter(e, width); - if (marker == null) return; - int offset = marker.getStartOffset(); - - final Document doc = myEditor.getDocument(); - if (doc.getLineCount() > 0) { - // Necessary to expand folded block even if navigating just before one - // Very useful when navigating to first unused import statement. - int lineEnd = doc.getLineEndOffset(doc.getLineNumber(offset)); - myEditor.getCaretModel().moveToOffset(lineEnd); - } - - myEditor.getCaretModel().moveToOffset(offset); - myEditor.getSelectionModel().removeSelection(); - ScrollingModel scrollingModel = myEditor.getScrollingModel(); - scrollingModel.disableAnimation(); - scrollingModel.scrollToCaret(ScrollType.CENTER); - scrollingModel.enableAnimation(); - fireErrorMarkerClicked(marker, e); - } - - @Nullable - private RangeHighlighter getNearestRangeHighlighter(final MouseEvent e, final int width) { - List nearestSpots = getNearestMarkSpots(e, width); - RangeHighlighter nearestMarker = null; - int yPos = 0; - for (MarkSpot markSpot : nearestSpots) { - for (RangeHighlighter highlighter : markSpot.highlighters) { - final int newYPos = visibleLineToYPosition(offsetToLine(highlighter.getStartOffset())); - - if (nearestMarker == null || Math.abs(yPos - e.getY()) > Math.abs(newYPos - e.getY())) { - nearestMarker = highlighter; - yPos = newYPos; - } - } - } - return nearestMarker; - } - - private List getNearestMarkSpots(final MouseEvent e, final double width) { - List nearestSpot = findSpots(e, width, null, myWideSpots); - nearestSpot = findSpots(e, width, nearestSpot, myNarrowSpots); - return nearestSpot == null ? Collections.emptyList() : nearestSpot; - } - - private List findSpots(MouseEvent e, double width, List nearestSpot, List spots) { - for (MarkSpot markSpot : spots) { - if (markSpot.near(e, width)) { - if (nearestSpot == null) { - nearestSpot = new SmartList(); - } - nearestSpot.add(markSpot); - } - } - return nearestSpot; + }); + } + + public void doClick(final MouseEvent e, final int width) { + RangeHighlighter marker = getNearestRangeHighlighter(e, width); + if (marker == null) return; + int offset = marker.getStartOffset(); + + final Document doc = myEditor.getDocument(); + if (doc.getLineCount() > 0) { + // Necessary to expand folded block even if navigating just before one + // Very useful when navigating to first unused import statement. + int lineEnd = doc.getLineEndOffset(doc.getLineNumber(offset)); + myEditor.getCaretModel().moveToOffset(lineEnd); } + myEditor.getCaretModel().moveToOffset(offset); + myEditor.getSelectionModel().removeSelection(); + ScrollingModel scrollingModel = myEditor.getScrollingModel(); + scrollingModel.disableAnimation(); + scrollingModel.scrollToCaret(ScrollType.CENTER); + scrollingModel.enableAnimation(); + fireErrorMarkerClicked(marker, e); } public void setErrorStripeVisible(boolean val) { @@ -523,6 +225,7 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark myErrorStripeRenderer = renderer; //try to not cancel tooltips here, since it is being called after every writeAction, even to the console //HintManager.getInstance().getTooltipController().cancelTooltips(); + myEditor.getVerticalScrollBar().repaint(); } @@ -539,41 +242,16 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark super.dispose(); } - public void repaint() { + void repaint(int startOffset, int endOffset) { markDirtied(); - EditorImpl.MyScrollBar scrollBar = myEditor.getVerticalScrollBar(); - myScrollBarHeight = scrollBar.getSize().height; - scrollBar.repaint(); - } - private List getSortedHighlighters() { - if (myCachedSortedHighlighters == null) { - myCachedSortedHighlighters = new ArrayList(); + int startY = offsetToYPosition(startOffset); + int endY = offsetToYPosition(endOffset) + getMinHeight(); - for (RangeHighlighter highlighter : getAllHighlighters()) { - if (highlighter.getErrorStripeMarkColor() != null && highlighter.isValid()) { - myCachedSortedHighlighters.add(highlighter); - } - } - - final MarkupModel docMarkup = getDocument().getMarkupModel(myEditor.getProject()); - for (RangeHighlighter highlighter : docMarkup.getAllHighlighters()) { - if (highlighter.getErrorStripeMarkColor() != null && highlighter.isValid()) { - myCachedSortedHighlighters.add(highlighter); - } - } - - if (!myCachedSortedHighlighters.isEmpty()) { - ContainerUtil.quickSort(myCachedSortedHighlighters, new Comparator() { - public int compare(final RangeHighlighter h1, final RangeHighlighter h2) { - return h1.getStartOffset() - h2.getStartOffset(); - } - }); - } - } - return myCachedSortedHighlighters; + myEditor.getVerticalScrollBar().repaint(0, startY, PREFERRED_WIDTH, endY - startY); } + private static final Dimension STRIPE_BUTTON_PREFERRED_SIZE = new Dimension(PREFERRED_WIDTH, ERROR_ICON_HEIGHT + 4); private class ErrorStripeButton extends JButton { private ErrorStripeButton() { } @@ -595,7 +273,7 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark try { if (myErrorStripeRenderer != null) { - myErrorStripeRenderer.paint(this, g, new Rectangle(5, 2, ERRORS_FOUND_ICON.getIconWidth(), ERRORS_FOUND_ICON.getIconHeight())); + myErrorStripeRenderer.paint(this, g, new Rectangle(5, 2, ERROR_ICON_WIDTH, ERROR_ICON_HEIGHT)); } } finally { @@ -605,7 +283,7 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark @Override public Dimension getPreferredSize() { - return new Dimension(ERRORS_FOUND_ICON.getIconWidth() + 4, ERRORS_FOUND_ICON.getIconHeight() + 4); + return STRIPE_BUTTON_PREFERRED_SIZE; } } @@ -677,7 +355,8 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark ((ApplicationImpl)ApplicationManager.getApplication()).editorPaintStart(); try { - myMarkSpots.repaint(g, ERRORS_FOUND_ICON.getIconWidth() - 2); + Rectangle clipBounds = g.getClipBounds(); + repaint(g, ERROR_ICON_WIDTH - 2, clipBounds); } finally { g.translate(-shift, 0); @@ -690,6 +369,151 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark return ColorUtil.withAlpha(super.adjustColor(c), 0.85); } + private void repaint(final Graphics g, final int width, Rectangle clipBounds) { + Document document = myEditor.getDocument(); + int startOffset = yPositionToOffset(clipBounds.y, true); + int endOffset = yPositionToOffset(clipBounds.y + clipBounds.height, false); + + drawMarkup(g, width, startOffset, endOffset, EditorMarkupModelImpl.this); + drawMarkup(g, width, startOffset, endOffset, (MarkupModelEx)document.getMarkupModel(myEditor.getProject())); + } + + private void drawMarkup(final Graphics g, final int width, int startOffset, int endOffset, MarkupModelEx markup) { + final Queue thinEnds = new PriorityQueue(5, new Comparator() { + public int compare(PositionedStripe o1, PositionedStripe o2) { + return o1.yEnd - o2.yEnd; + } + }); + final Queue wideEnds = new PriorityQueue(5, new Comparator() { + public int compare(PositionedStripe o1, PositionedStripe o2) { + return o1.yEnd - o2.yEnd; + } + }); + // sorted by layer + final List thinStripes = new ArrayList(); + final List wideStripes = new ArrayList(); + final int[] thinYStart = new int[1]; // in range 0..yStart all spots are drawn + final int[] wideYStart = new int[1]; // in range 0..yStart all spots are drawn + + markup.processHighlightsOverlappingWith(startOffset, endOffset, new Processor() { + public boolean process(RangeHighlighterEx highlighter) { + Color color = highlighter.getErrorStripeMarkColor(); + if (color == null) return true; + boolean isThin = highlighter.isThinErrorStripeMark(); + int[] yStart = isThin ? thinYStart : wideYStart; + List stripes = isThin ? thinStripes : wideStripes; + Queue ends = isThin ? thinEnds : wideEnds; + + final int ys = offsetToYPosition(highlighter.getStartOffset()); + int ye = offsetToYPosition(highlighter.getEndOffset()); + if (ye - ys < getMinHeight()) ye = ys + getMinHeight(); + + yStart[0] = drawStripesEndingBefore(ys, ends, stripes, g, width, yStart[0]); + + final int layer = highlighter.getLayer(); + + PositionedStripe stripe = null; + int i; + for (i = 0; i < stripes.size(); i++) { + PositionedStripe s = stripes.get(i); + if (s.layer == layer) { + stripe = s; + break; + } + if (s.layer < layer) { + break; + } + } + if (stripe == null) { + // started new stripe, draw previous above + if (yStart[0] != ys) { + if (!stripes.isEmpty()) { + PositionedStripe top = stripes.get(0); + drawSpot(g, width, top.thin, yStart[0], ys, top.color, true, true); + } + yStart[0] = ys; + } + stripe = new PositionedStripe(color, ys, ye, isThin, layer); + stripes.add(i, stripe); + ends.offer(stripe); + } + else { + // key changed, reinsert into queue + if (stripe.yEnd != ye) { + ends.remove(stripe); + stripe.yEnd = ye; + ends.offer(stripe); + } + } + + return true; + } + + }); + + drawStripesEndingBefore(Integer.MAX_VALUE, thinEnds, thinStripes, g, width, thinYStart[0]); + drawStripesEndingBefore(Integer.MAX_VALUE, wideEnds, wideStripes, g, width, wideYStart[0]); + } + + private int drawStripesEndingBefore(int ys, + Queue ends, + List stripes, + Graphics g, int width, int yStart) { + while (!ends.isEmpty()) { + PositionedStripe endingStripe = ends.peek(); + if (endingStripe.yEnd > ys) break; + ends.remove(); + + // check whether endingStripe got obscured in the range yStart..endingStripe.yEnd + int i = stripes.indexOf(endingStripe); + stripes.remove(i); + if (i == 0) { + // visible + drawSpot(g, width, endingStripe.thin, yStart, endingStripe.yEnd, endingStripe.color, true, true); + yStart = endingStripe.yEnd; + } + } + return yStart; + } + + private void drawSpot(Graphics g, + int width, + boolean thinErrorStripeMark, + int yStart, + int yEnd, + Color color, + boolean drawTopDecoration, + boolean drawBottomDecoration) { + int x = 3; + int paintWidth = width; + if (thinErrorStripeMark) { + paintWidth /= 2; + paintWidth += 1; + x += paintWidth + 1; + } + if (color == null) return; + g.setColor(color); + g.fillRect(x + 1, yStart, paintWidth - 2, yEnd - yStart+1); + + Color brighter = color.brighter(); + g.setColor(brighter); + //left decoration + UIUtil.drawLine(g, x, yStart, x, yEnd/* - 1*/); + if (drawTopDecoration) { + //top decoration + UIUtil.drawLine(g, x + 1, yStart, x + paintWidth - 2, yStart); + } + Color darker = ColorUtil.shift(color, 0.75); + + g.setColor(darker); + if (drawBottomDecoration) { + // bottom decoration + UIUtil.drawLine(g, x + 1, yEnd/* - 1*/, x + paintWidth - 2, yEnd/* - 1*/); // large bottom to let overwrite by hl below + } + //right decoration + UIUtil.drawLine(g, x + paintWidth - 2, yStart, x + paintWidth - 2, yEnd/* - 1*/); + } + // mouse events public void mouseClicked(final MouseEvent e) { CommandProcessor.getInstance().executeCommand(myEditor.getProject(), new Runnable() { @@ -707,7 +531,7 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark public void mouseReleased(MouseEvent e) { } - public int getWidth() { + private int getWidth() { return scrollbar.getWidth(); } @@ -717,7 +541,7 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark if (lineCount == 0) { return; } - myMarkSpots.doClick(e, getWidth()); + doClick(e, getWidth()); } public void mouseMoved(MouseEvent e) { @@ -736,7 +560,7 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark return; } - if (myMarkSpots.showToolTipByMouseMove(e,getWidth())) { + if (showToolTipByMouseMove(e,getWidth())) { scrollbar.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); return; } @@ -813,12 +637,9 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark } public void markDirtied() { - myCachedSortedHighlighters = null; - myMarkSpots.clear(); - } - - public int getMinMarkHeight() { - return myMinMarkHeight; + myEditorScrollbarTop = -1; + myEditorSourceHeight = -1; + myEditorTargetHeight = -1; } public void setMinMarkHeight(final int minMarkHeight) { @@ -860,4 +681,39 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark return new LineTooltipRenderer(text, width); } } + + private int offsetToYPosition(int offset) { + int lineNumber = offsetToLine(offset); + if (myEditorScrollbarTop == -1 || myEditorTargetHeight == -1) { + recalcEditorDimensions(); + } + if (myEditorSourceHeight < myEditorTargetHeight) { + return myEditorScrollbarTop + lineNumber * myEditor.getLineHeight(); + } + else { + final int lineCount = myEditorSourceHeight / myEditor.getLineHeight(); + return myEditorScrollbarTop + (int)((float)lineNumber / lineCount * myEditorTargetHeight); + } + } + + private int yPositionToOffset(int y, boolean beginLine) { + if (myEditorScrollbarTop == -1 || myEditorTargetHeight == -1) { + recalcEditorDimensions(); + } + VisualPosition visual; + if (myEditorSourceHeight < myEditorTargetHeight) { + visual = myEditor.xyToVisualPosition(new Point(0, y - myEditorScrollbarTop)); + } + else { + float fraction = Math.max(0, Math.min(1, (y - myEditorScrollbarTop) / (float)myEditorTargetHeight)); + final int lineCount = myEditorSourceHeight / myEditor.getLineHeight(); + visual = new VisualPosition((int)(fraction * lineCount),0); + } + int line = myEditor.visualToLogicalPosition(visual).line; + Document document = myEditor.getDocument(); + if (line < 0) return 0; + if (line >= document.getLineCount()) return document.getTextLength(); + + return beginLine ? document.getLineStartOffset(line) : document.getLineEndOffset(line); + } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EmptyMarkupModel.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EmptyMarkupModel.java index eb86a65e1c29..82e861e025ec 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EmptyMarkupModel.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EmptyMarkupModel.java @@ -5,14 +5,20 @@ package com.intellij.openapi.editor.impl; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.ex.MarkupModelEx; +import com.intellij.openapi.editor.ex.RangeHighlighterEx; import com.intellij.openapi.editor.impl.event.MarkupModelListener; import com.intellij.openapi.editor.markup.HighlighterTargetArea; import com.intellij.openapi.editor.markup.RangeHighlighter; import com.intellij.openapi.editor.markup.TextAttributes; +import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.util.Key; +import com.intellij.util.Processor; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Iterator; + /** * This is mock implementation to be used in null-object pattern where necessary. * @author max @@ -35,12 +41,12 @@ public class EmptyMarkupModel implements MarkupModelEx { int layer, @Nullable TextAttributes textAttributes, @NotNull HighlighterTargetArea targetArea) { - return new RangeHighlighterImpl(this, startOffset, endOffset, layer, targetArea, textAttributes, false); + throw new ProcessCanceledException(); } @NotNull public RangeHighlighter addLineHighlighter(int line, int layer, @Nullable TextAttributes textAttributes) { - return new RangeHighlighterImpl(this, 0, 0, layer, HighlighterTargetArea.LINES_IN_RANGE, textAttributes, false); + throw new ProcessCanceledException(); } public void removeHighlighter(RangeHighlighter rangeHighlighter) { @@ -64,25 +70,37 @@ public class EmptyMarkupModel implements MarkupModelEx { public void dispose() { } - public HighlighterList getHighlighterList() { - return null; - } - public RangeHighlighter addPersistentLineHighlighter(int lineNumber, int layer, TextAttributes textAttributes) { return null; } - public boolean containsHighlighter(RangeHighlighter highlighter) { + public boolean containsHighlighter(@NotNull RangeHighlighter highlighter) { return false; } - public void addMarkupModelListener(MarkupModelListener listener) { + public void addMarkupModelListener(@NotNull MarkupModelListener listener) { } - public void removeMarkupModelListener(MarkupModelListener listener) { + public void removeMarkupModelListener(@NotNull MarkupModelListener listener) { } - public void setRangeHighlighterAttributes(final RangeHighlighter highlighter, final TextAttributes textAttributes) { + public void setRangeHighlighterAttributes(@NotNull final RangeHighlighter highlighter, final TextAttributes textAttributes) { } + + public boolean processHighlightsOverlappingWith(int start, int end, @NotNull Processor processor) { + return false; + } + + public Iterator iterator() { + return ContainerUtil.emptyIterator(); + } + + @NotNull + public Iterator iteratorFrom(@NotNull Interval interval) { + return iterator(); + } + public boolean sweep(int start, int end, @NotNull SweepProcessor sweepProcessor) { + return false; + } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldRegionsTree.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldRegionsTree.java new file mode 100644 index 000000000000..aaf2445db6ad --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldRegionsTree.java @@ -0,0 +1,295 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.editor.impl; + +import com.intellij.openapi.editor.FoldRegion; +import com.intellij.openapi.editor.RangeMarker; +import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.CollectionFactory; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; + +/** +* User: cdr +*/ +abstract class FoldRegionsTree { + private FoldRegion[] myCachedVisible; + private FoldRegion[] myCachedTopLevelRegions; + private int[] myCachedEndOffsets; + private int[] myCachedStartOffsets; + private int[] myCachedFoldedLines; + int myCachedLastIndex = -1; + private ArrayList myRegions = CollectionFactory.arrayList(); //sorted in tree left-to-right topdown traversal order + private static final Comparator BY_END_OFFSET = new Comparator() { + public int compare(FoldRegion r1, FoldRegion r2) { + int end1 = r1.getEndOffset(); + int end2 = r2.getEndOffset(); + if (end1 < end2) return -1; + if (end1 > end2) return 1; + return 0; + } + }; + private static final Comparator BY_END_OFFSET_REVERSE = Collections.reverseOrder(BY_END_OFFSET); + + void clear() { + myCachedVisible = null; + myCachedTopLevelRegions = null; + myCachedEndOffsets = null; + myCachedStartOffsets = null; + myCachedFoldedLines = null; + myRegions = new ArrayList(); + } + + private boolean isFoldingEnabledAndUpToDate() { + return isFoldingEnabled() && myCachedVisible != null; + } + + protected abstract boolean isFoldingEnabled(); + + protected abstract boolean isBatchFoldingProcessing(); + + void rebuild() { + ArrayList topLevels = new ArrayList(myRegions.size() / 2); + ArrayList visible = new ArrayList(myRegions.size()); + FoldRegion[] regions = myRegions.toArray(new FoldRegion[myRegions.size()]); + FoldRegion currentToplevel = null; + for (FoldRegion region : regions) { + if (region.isValid()) { + visible.add(region); + if (!region.isExpanded()) { + if (currentToplevel == null || currentToplevel.getEndOffset() < region.getStartOffset()) { + currentToplevel = region; + topLevels.add(region); + } + } + } + } + + myCachedTopLevelRegions = topLevels.isEmpty() ? FoldRegion.EMPTY_ARRAY : topLevels.toArray(new FoldRegion[topLevels.size()]); + + Arrays.sort(myCachedTopLevelRegions, BY_END_OFFSET); + + FoldRegion[] visibleArrayed = visible.toArray(new FoldRegion[visible.size()]); + for (FoldRegion visibleRegion : visibleArrayed) { + for (FoldRegion topLevelRegion : myCachedTopLevelRegions) { + if (contains(topLevelRegion, visibleRegion)) { + visible.remove(visibleRegion); + break; + } + } + } + + myCachedVisible = visible.toArray(new FoldRegion[visible.size()]); + + Arrays.sort(myCachedVisible, BY_END_OFFSET_REVERSE); + + updateCachedOffsets(); + } + + void updateCachedOffsets() { + if (!isFoldingEnabled()) { + return; + } + if (myCachedVisible == null) { + rebuild(); + return; + } + + for (FoldRegion foldRegion : myCachedVisible) { + if (!foldRegion.isValid()) { + rebuild(); + return; + } + } + + int length = myCachedTopLevelRegions.length; + if (myCachedEndOffsets == null || myCachedEndOffsets.length != length) { + if (length != 0) { + myCachedEndOffsets = new int[length]; + myCachedStartOffsets = new int[length]; + myCachedFoldedLines = new int[length]; + } + else { + myCachedEndOffsets = ArrayUtil.EMPTY_INT_ARRAY; + myCachedStartOffsets = ArrayUtil.EMPTY_INT_ARRAY; + myCachedFoldedLines = ArrayUtil.EMPTY_INT_ARRAY; + } + } + + int sum = 0; + for (int i = 0; i < length; i++) { + FoldRegion region = myCachedTopLevelRegions[i]; + myCachedStartOffsets[i] = region.getStartOffset(); + myCachedEndOffsets[i] = region.getEndOffset() - 1; + sum += region.getDocument().getLineNumber(region.getEndOffset()) - region.getDocument().getLineNumber(region.getStartOffset()); + myCachedFoldedLines[i] = sum; + } + } + + boolean addRegion(FoldRegion range) { + // During batchProcessing elements are inserted in ascending order, + // binary search find acceptable insertion place first time + int fastIndex = myCachedLastIndex != -1 && isBatchFoldingProcessing() + ? myCachedLastIndex + 1 : Collections.binarySearch(myRegions, range, RangeMarker.BY_START_OFFSET); + if (fastIndex < 0) fastIndex = -fastIndex - 1; + + for (int i = fastIndex - 1; i >=0; --i) { + final FoldRegion region = myRegions.get(i); + if (region.getEndOffset() < range.getStartOffset()) break; + if (region.isValid() && intersects(region, range)) { + return false; + } + } + + for (int i = fastIndex; i < myRegions.size(); i++) { + final FoldRegion region = myRegions.get(i); + + if (range.getStartOffset() < region.getStartOffset() || + range.getStartOffset() == region.getStartOffset() && range.getEndOffset() > region.getEndOffset()) { + for (int j = i + 1; j < myRegions.size(); j++) { + final FoldRegion next = myRegions.get(j); + if (next.getEndOffset() >= range.getEndOffset() && next.isValid()) { + if (next.getStartOffset() < range.getStartOffset()) { + return false; + } + else { + break; + } + } + } + + myRegions.add(myCachedLastIndex = i, range); + return true; + } + } + myRegions.add(myCachedLastIndex = myRegions.size(),range); + return true; + } + + FoldRegion fetchOutermost(int offset) { + if (!isFoldingEnabledAndUpToDate()) return null; + + final int[] starts = myCachedStartOffsets; + final int[] ends = myCachedEndOffsets; + + int start = 0; + int end = ends.length - 1; + + while (start <= end) { + int i = (start + end) / 2; + if (offset < starts[i]) { + end = i - 1; + } else if (offset > ends[i]) { + start = i + 1; + } + else { + return myCachedTopLevelRegions[i]; + } + } + + return null; + } + + FoldRegion[] fetchVisible() { + if (!isFoldingEnabledAndUpToDate()) return FoldRegion.EMPTY_ARRAY; + return myCachedVisible; + } + + FoldRegion[] fetchTopLevel() { + if (!isFoldingEnabledAndUpToDate()) return null; + return myCachedTopLevelRegions; + } + + private static boolean contains(FoldRegion outer, FoldRegion inner) { + return outer.getStartOffset() < inner.getStartOffset() && outer.getEndOffset() > inner.getStartOffset(); + } + + private static boolean intersects(FoldRegion r1, FoldRegion r2) { + final int s1 = r1.getStartOffset(); + final int s2 = r2.getStartOffset(); + final int e1 = r1.getEndOffset(); + final int e2 = r2.getEndOffset(); + return s1 == s2 && e1 == e2 || s1 < s2 && s2 < e1 && e1 < e2 || s2 < s1 && s1 < e2 && e2 < e1; + } + + static boolean contains(FoldRegion region, int offset) { + return region.getStartOffset() < offset && region.getEndOffset() > offset; + } + + public FoldRegion[] fetchCollapsedAt(int offset) { + if (!isFoldingEnabledAndUpToDate()) return FoldRegion.EMPTY_ARRAY; + ArrayList allCollapsed = new ArrayList(); + for (FoldRegion region : myRegions) { + if (!region.isExpanded() && contains(region, offset)) { + allCollapsed.add(region); + } + } + + return allCollapsed.toArray(new FoldRegion[allCollapsed.size()]); + } + + boolean intersectsRegion(int startOffset, int endOffset) { + if (!isFoldingEnabled()) return true; + for (FoldRegion region : myRegions) { + boolean contains1 = contains(region, startOffset); + boolean contains2 = contains(region, endOffset); + if (contains1 != contains2) { + return true; + } + } + return false; + } + + FoldRegion[] fetchAllRegions() { + if (!isFoldingEnabledAndUpToDate()) return FoldRegion.EMPTY_ARRAY; + + return myRegions.toArray(new FoldRegion[myRegions.size()]); + } + + void removeRegion(FoldRegion range) { + myRegions.remove(range); + } + + int getFoldedLinesCountBefore(int offset) { + int idx = getLastTopLevelIndexBefore(offset); + if (idx == -1) return 0; + return myCachedFoldedLines[idx]; + } + + public int getLastTopLevelIndexBefore(int offset) { + if (!isFoldingEnabledAndUpToDate()) return -1; + + int start = 0; + int end = myCachedEndOffsets.length - 1; + + while (start <= end) { + int i = (start + end) / 2; + if (offset < myCachedEndOffsets[i]) { + end = i - 1; + } else if (offset > myCachedEndOffsets[i]) { + start = i + 1; + } + else { + return i; + } + } + + return end; + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldingModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldingModelImpl.java index b5939ef09106..e00ecad42f96 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldingModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldingModelImpl.java @@ -26,20 +26,21 @@ package com.intellij.openapi.editor.impl; import com.intellij.openapi.application.ex.ApplicationManagerEx; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.*; +import com.intellij.openapi.editor.FoldRegion; +import com.intellij.openapi.editor.FoldingGroup; +import com.intellij.openapi.editor.LogicalPosition; +import com.intellij.openapi.editor.VisualPosition; import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.ex.DocumentEx; import com.intellij.openapi.editor.ex.FoldingModelEx; import com.intellij.openapi.editor.ex.PrioritizedDocumentListener; import com.intellij.openapi.editor.markup.TextAttributes; -import com.intellij.util.ArrayUtil; -import com.intellij.util.containers.CollectionFactory; import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NotNull; import java.awt.*; -import java.util.*; +import java.util.Arrays; import java.util.List; public class FoldingModelImpl implements FoldingModelEx, PrioritizedDocumentListener { @@ -57,23 +58,21 @@ public class FoldingModelImpl implements FoldingModelEx, PrioritizedDocumentList private int mySavedCaretShift; private boolean myCaretPositionSaved; private final MultiMap myGroups = new MultiMap(); - private static final Comparator BY_END_OFFSET = new Comparator() { - public int compare(FoldRegion r1, FoldRegion r2) { - int end1 = r1.getEndOffset(); - int end2 = r2.getEndOffset(); - if (end1 < end2) return -1; - if (end1 > end2) return 1; - return 0; - } - }; - private static final Comparator BY_END_OFFSET_REVERSE = Collections.reverseOrder(BY_END_OFFSET); public FoldingModelImpl(EditorImpl editor) { myEditor = editor; myIsFoldingEnabled = true; myIsBatchFoldingProcessing = false; myDoNotCollapseCaret = false; - myFoldTree = new FoldRegionsTree(); + myFoldTree = new FoldRegionsTree() { + protected boolean isFoldingEnabled() { + return FoldingModelImpl.this.isFoldingEnabled(); + } + + protected boolean isBatchFoldingProcessing() { + return myIsBatchFoldingProcessing; + } + }; myFoldRegionsProcessed = false; refreshSettings(); } @@ -88,8 +87,7 @@ public class FoldingModelImpl implements FoldingModelEx, PrioritizedDocumentList final List regions = getGroupedRegions(group); if (regions.isEmpty()) { final boolean inAll = Arrays.asList(getAllFoldRegions()).contains(child); - final boolean inAllPlusInvalid = Arrays.asList(getAllFoldRegionsIncludingInvalid()).contains(child); - throw new AssertionError("Folding group without children; the known child is in all: " + inAll + "; in all+invalid: " + inAllPlusInvalid); + throw new AssertionError("Folding group without children; the known child is in all: " + inAll); } FoldRegion main = regions.get(0); @@ -140,25 +138,26 @@ public class FoldingModelImpl implements FoldingModelEx, PrioritizedDocumentList public FoldRegion addFoldRegion(int startOffset, int endOffset, @NotNull String placeholderText) { FoldRegion region = createFoldRegion(startOffset, endOffset, placeholderText, null); - return addFoldRegion(region) ? region : null; + return region == null || addFoldRegion(region) ? region : null; } public boolean addFoldRegion(@NotNull final FoldRegion region) { assertIsDispatchThread(); - if (isFoldingEnabled()) { - if (!myIsBatchFoldingProcessing) { - LOG.error("Fold regions must be added or removed inside batchFoldProcessing() only."); - return false; - } + if (!isFoldingEnabled()) { + return false; + } + if (!myIsBatchFoldingProcessing) { + LOG.error("Fold regions must be added or removed inside batchFoldProcessing() only."); + return false; + } - myFoldRegionsProcessed = true; - if (myFoldTree.addRegion(region)) { - final FoldingGroup group = region.getGroup(); - if (group != null) { - myGroups.putValue(group, region); - } - return true; + myFoldRegionsProcessed = true; + if (myFoldTree.addRegion(region)) { + final FoldingGroup group = region.getGroup(); + if (group != null) { + myGroups.putValue(group, region); } + return true; } return false; @@ -229,11 +228,6 @@ public class FoldingModelImpl implements FoldingModelEx, PrioritizedDocumentList return myFoldTree.fetchOutermost(offset); } - public FoldRegion[] getAllFoldRegionsIncludingInvalid() { - assertReadAccess(); - return myFoldTree.fetchAllRegionsIncludingInvalid(); - } - public void removeFoldRegion(@NotNull final FoldRegion region) { assertIsDispatchThread(); @@ -284,7 +278,7 @@ public class FoldingModelImpl implements FoldingModelEx, PrioritizedDocumentList int caretOffset = myEditor.logicalPositionToOffset(caretPosition); - if (myFoldTree.contains(region, caretOffset)) { + if (FoldRegionsTree.contains(region, caretOffset)) { if (myDoNotCollapseCaret) return; if (!myCaretPositionSaved) { @@ -297,7 +291,7 @@ public class FoldingModelImpl implements FoldingModelEx, PrioritizedDocumentList int selectionStart = myEditor.getSelectionModel().getSelectionStart(); int selectionEnd = myEditor.getSelectionModel().getSelectionEnd(); - if (myFoldTree.contains(region, selectionStart-1) || myFoldTree.contains(region, selectionEnd)) myEditor.getSelectionModel().removeSelection(); + if (FoldRegionsTree.contains(region, selectionStart-1) || FoldRegionsTree.contains(region, selectionEnd)) myEditor.getSelectionModel().removeSelection(); myFoldRegionsProcessed = true; ((FoldRegionImpl) region).setExpandedInternal(false); @@ -341,7 +335,8 @@ public class FoldingModelImpl implements FoldingModelEx, PrioritizedDocumentList VisualPosition vis = myEditor.logicalToVisualPosition(log); VisualPosition pos = new VisualPosition(vis.line, column); myEditor.getCaretModel().moveToVisualPosition(pos); - } else { + } + else { myEditor.getCaretModel().moveToLogicalPosition(caretPosition); } @@ -403,271 +398,6 @@ public class FoldingModelImpl implements FoldingModelEx, PrioritizedDocumentList myCaretPositionSaved = false; } - class FoldRegionsTree { - private FoldRegion[] myCachedVisible; - private FoldRegion[] myCachedTopLevelRegions; - private int[] myCachedEndOffsets; - private int[] myCachedStartOffsets; - private int[] myCachedFoldedLines; - private int myCachedLastIndex = -1; - private ArrayList myRegions = CollectionFactory.arrayList(); //sorted in tree left-to-right topdown traversal order - - private void clear() { - myCachedVisible = null; - myCachedTopLevelRegions = null; - myCachedEndOffsets = null; - myCachedStartOffsets = null; - myCachedFoldedLines = null; - myRegions = new ArrayList(); - } - - private boolean isFoldingEnabled() { - return FoldingModelImpl.this.isFoldingEnabled() && myCachedVisible != null; - } - - void rebuild() { - ArrayList topLevels = new ArrayList(myRegions.size() / 2); - ArrayList visible = new ArrayList(myRegions.size()); - FoldRegion[] regions = myRegions.toArray(new FoldRegion[myRegions.size()]); - FoldRegion currentToplevel = null; - for (FoldRegion region : regions) { - if (region.isValid()) { - visible.add(region); - if (!region.isExpanded()) { - if (currentToplevel == null || currentToplevel.getEndOffset() < region.getStartOffset()) { - currentToplevel = region; - topLevels.add(region); - } - } - } - } - - myCachedTopLevelRegions = topLevels.isEmpty() ? FoldRegion.EMPTY_ARRAY : topLevels.toArray(new FoldRegion[topLevels.size()]); - - Arrays.sort(myCachedTopLevelRegions, BY_END_OFFSET); - - FoldRegion[] visibleArrayed = visible.toArray(new FoldRegion[visible.size()]); - for (FoldRegion visibleRegion : visibleArrayed) { - for (FoldRegion topLevelRegion : myCachedTopLevelRegions) { - if (contains(topLevelRegion, visibleRegion)) { - visible.remove(visibleRegion); - break; - } - } - } - - myCachedVisible = visible.isEmpty() ? FoldRegion.EMPTY_ARRAY : visible.toArray(new FoldRegion[visible.size()]); - - Arrays.sort(myCachedVisible, BY_END_OFFSET_REVERSE); - - updateCachedOffsets(); - } - - void updateCachedOffsets() { - if (FoldingModelImpl.this.isFoldingEnabled()) { - if (myCachedVisible == null) { - rebuild(); - return; - } - - for (FoldRegion foldRegion : myCachedVisible) { - if (!foldRegion.isValid()) { - rebuild(); - return; - } - } - - int length = myCachedTopLevelRegions.length; - if (myCachedEndOffsets == null || myCachedEndOffsets.length != length) { - if (length != 0) { - myCachedEndOffsets = new int[length]; - myCachedStartOffsets = new int[length]; - myCachedFoldedLines = new int[length]; - } - else { - myCachedEndOffsets = ArrayUtil.EMPTY_INT_ARRAY; - myCachedStartOffsets = ArrayUtil.EMPTY_INT_ARRAY; - myCachedFoldedLines = ArrayUtil.EMPTY_INT_ARRAY; - } - } - - int sum = 0; - for (int i = 0; i < length; i++) { - FoldRegion region = myCachedTopLevelRegions[i]; - myCachedStartOffsets[i] = region.getStartOffset(); - myCachedEndOffsets[i] = region.getEndOffset() - 1; - sum += region.getDocument().getLineNumber(region.getEndOffset()) - region.getDocument().getLineNumber(region.getStartOffset()); - myCachedFoldedLines[i] = sum; - } - } - } - - boolean addRegion(FoldRegion range) { - // During batchProcessing elements are inserted in ascending order, - // binary search find acceptable insertion place first time - int fastIndex = myCachedLastIndex != -1 && myIsBatchFoldingProcessing? myCachedLastIndex + 1:Collections.binarySearch(myRegions, range, RangeMarker.BY_START_OFFSET); - if (fastIndex < 0) fastIndex = -fastIndex - 1; - - for (int i = fastIndex - 1; i >=0; --i) { - final FoldRegion region = myRegions.get(i); - if (region.getEndOffset() < range.getStartOffset()) break; - if (region.isValid() && intersects(region, range)) { - return false; - } - } - - for (int i = fastIndex; i < myRegions.size(); i++) { - final FoldRegion region = myRegions.get(i); - - if (range.getStartOffset() < region.getStartOffset() || - range.getStartOffset() == region.getStartOffset() && range.getEndOffset() > region.getEndOffset()) { - for (int j = i + 1; j < myRegions.size(); j++) { - final FoldRegion next = myRegions.get(j); - if (next.getEndOffset() >= range.getEndOffset() && next.isValid()) { - if (next.getStartOffset() < range.getStartOffset()) { - return false; - } - else { - break; - } - } - } - - myRegions.add(myCachedLastIndex = i, range); - return true; - } - } - myRegions.add(myCachedLastIndex = myRegions.size(),range); - return true; - } - - FoldRegion fetchOutermost(int offset) { - if (!isFoldingEnabled()) return null; - - final int[] starts = myCachedStartOffsets; - final int[] ends = myCachedEndOffsets; - - int start = 0; - int end = ends.length - 1; - - while (start <= end) { - int i = (start + end) / 2; - if (offset < starts[i]) { - end = i - 1; - } else if (offset > ends[i]) { - start = i + 1; - } - else { - return myCachedTopLevelRegions[i]; - } - } - - return null; - } - - FoldRegion[] fetchVisible() { - if (!isFoldingEnabled()) return FoldRegion.EMPTY_ARRAY; - return myCachedVisible; - } - - FoldRegion[] fetchTopLevel() { - if (!isFoldingEnabled()) return null; - return myCachedTopLevelRegions; - } - - private boolean contains(FoldRegion outer, FoldRegion inner) { - return outer.getStartOffset() < inner.getStartOffset() && outer.getEndOffset() > inner.getStartOffset(); - } - - private boolean intersects(FoldRegion r1, FoldRegion r2) { - final int s1 = r1.getStartOffset(); - final int s2 = r2.getStartOffset(); - final int e1 = r1.getEndOffset(); - final int e2 = r2.getEndOffset(); - return s1 == s2 && e1 == e2 || s1 < s2 && s2 < e1 && e1 < e2 || s2 < s1 && s1 < e2 && e2 < e1; - } - - private boolean contains(FoldRegion region, int offset) { - return region.getStartOffset() < offset && region.getEndOffset() > offset; - } - - public FoldRegion[] fetchCollapsedAt(int offset) { - if (!isFoldingEnabled()) return FoldRegion.EMPTY_ARRAY; - ArrayList allCollapsed = new ArrayList(); - for (FoldRegion region : myRegions) { - if (!region.isExpanded() && contains(region, offset)) { - allCollapsed.add(region); - } - } - - return allCollapsed.toArray(new FoldRegion[allCollapsed.size()]); - } - - boolean intersectsRegion(int startOffset, int endOffset) { - if (!FoldingModelImpl.this.isFoldingEnabled()) return true; - for (FoldRegion region : myRegions) { - boolean contains1 = contains(region, startOffset); - boolean contains2 = contains(region, endOffset); - if (contains1 != contains2) { - return true; - } - } - return false; - } - - FoldRegion[] fetchAllRegions() { - if (!isFoldingEnabled()) return FoldRegion.EMPTY_ARRAY; - - return myRegions.toArray(new FoldRegion[myRegions.size()]); - } - - void removeRegion(FoldRegion range) { - myRegions.remove(range); - } - - int getFoldedLinesCountBefore(int offset) { - int idx = getLastTopLevelIndexBefore(offset); - if (idx == -1) return 0; - return myCachedFoldedLines[idx]; - } - - public int getLastTopLevelIndexBefore(int offset) { - if (!isFoldingEnabled()) return -1; - - int start = 0; - int end = myCachedEndOffsets.length - 1; - - while (start <= end) { - int i = (start + end) / 2; - if (offset < myCachedEndOffsets[i]) { - end = i - 1; - } else if (offset > myCachedEndOffsets[i]) { - start = i + 1; - } - else { - return i; - } - } - - return end; - -// for (int i = 0; i < myCachedEndOffsets.length; i++) { -// if (!myCachedTopLevelRegions[i].isValid()) continue; -// int endOffset = myCachedEndOffsets[i]; -// if (endOffset > offset) break; -// lastIndex = i; -// } -// -// return lastIndex; - } - - public FoldRegion[] fetchAllRegionsIncludingInvalid() { - if (!FoldingModelImpl.this.isFoldingEnabled()) return FoldRegion.EMPTY_ARRAY; - - return myRegions.toArray(new FoldRegion[myRegions.size()]); - } - } - public void beforeDocumentChange(DocumentEvent event) { } @@ -684,6 +414,9 @@ public class FoldingModelImpl implements FoldingModelEx, PrioritizedDocumentList } public FoldRegion createFoldRegion(int startOffset, int endOffset, @NotNull String placeholder, FoldingGroup group) { - return new FoldRegionImpl(myEditor, startOffset, endOffset, placeholder, group); + FoldRegionImpl region = new FoldRegionImpl(myEditor, startOffset, endOffset, placeholder, group); + region.registerInDocument(); + LOG.assertTrue(region.isValid()); + return region; } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/HighlighterList.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/HighlighterList.java deleted file mode 100644 index ad18de77a9c8..000000000000 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/HighlighterList.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2000-2009 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.openapi.editor.impl; - -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.event.DocumentEvent; -import com.intellij.openapi.editor.event.DocumentListener; -import com.intellij.openapi.editor.ex.PrioritizedDocumentListener; -import com.intellij.openapi.editor.markup.RangeHighlighter; -import com.intellij.util.containers.SortedList; - -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; - -public abstract class HighlighterList { - private final SortedList mySegmentHighlighters = new SortedList(MY_RANGE_COMPARATOR) { - @Override - protected void sort(List delegate) { - Iterator it = delegate.iterator(); - boolean needSort = false; - RangeHighlighterImpl lastHighlighter = null; - - while (it.hasNext()) { - RangeHighlighterImpl highlighter = it.next(); - if (!highlighter.isValid()) { - needSort = true; - it.remove(); - } - - if (lastHighlighter != null) { - if (!needSort) needSort = MY_RANGE_COMPARATOR.compare(lastHighlighter, highlighter) > 0; - } - - lastHighlighter = highlighter; - } - - if (needSort) { - super.sort(delegate); - } - } - - @Override - public boolean remove(Object o) { - if (o instanceof RangeHighlighterImpl) { - if (!((RangeHighlighterImpl)o).isValid()) return false; - } - return super.remove(o); - } - }; - - private boolean myIsDirtied = false; - private final DocumentListener myDocumentListener; - private final Document myDoc; - private int myLongestHighlighterLength = 0; - - private static final Comparator MY_RANGE_COMPARATOR = new Comparator() { - public int compare(RangeHighlighterImpl r1, RangeHighlighterImpl r2) { - int o = r1.getAffectedAreaStartOffset() - r2.getAffectedAreaStartOffset(); - if (o != 0) { - return o; - } - - if (r1.getLayer() != r2.getLayer()) { - return r2.getLayer() - r1.getLayer(); - } - - return (int) (r2.getId() - r1.getId()); - } - }; - - public HighlighterList(Document doc) { - myDocumentListener = new PrioritizedDocumentListener() { - public int getPriority() { - return 0; // Need to make sure we invalidate all the stuff before someone (like LineStatusTracker) starts to modify highlights. - } - - public void beforeDocumentChange(DocumentEvent event) {} - - public void documentChanged(DocumentEvent e) { - myIsDirtied = true; - mySegmentHighlighters.markDirty(); - } - }; - myDoc = doc; - myDoc.addDocumentListener(myDocumentListener); - } - - public void dispose() { - myDoc.removeDocumentListener(myDocumentListener); - } - - public int getLongestHighlighterLength() { - return myLongestHighlighterLength; - } - - private void sortMarkers() { - assertDispatchThread(); - myLongestHighlighterLength = 0; - - Iterator iterator = mySegmentHighlighters.iterator(); - while (iterator.hasNext()) { - RangeHighlighter rangeHighlighter = iterator.next(); - if (rangeHighlighter.isValid()) { - myLongestHighlighterLength = Math.max(myLongestHighlighterLength, rangeHighlighter.getEndOffset() - rangeHighlighter.getStartOffset()); - } - else { - iterator.remove(); - } - } - - myIsDirtied = false; - } - - protected abstract void assertDispatchThread(); - - Iterator getHighlighterIterator() { - if (myIsDirtied) sortMarkers(); - return mySegmentHighlighters.iterator(); - } - - List getSortedHighlighters() { - if (myIsDirtied) sortMarkers(); - return mySegmentHighlighters; - } - - void addSegmentHighlighter(RangeHighlighter segmentHighlighter) { - assertDispatchThread(); - myIsDirtied = true; - mySegmentHighlighters.add((RangeHighlighterImpl)segmentHighlighter); - } - - void removeSegmentHighlighter(RangeHighlighter segmentHighlighter) { - assertDispatchThread(); - myIsDirtied = true; - - //noinspection SuspiciousMethodCalls - mySegmentHighlighters.remove(segmentHighlighter); - } -} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/Interval.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/Interval.java new file mode 100644 index 000000000000..18589a0a0e8e --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/Interval.java @@ -0,0 +1,24 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.editor.impl; + +/** + * User: cdr + */ +public interface Interval { + int intervalStart(); + int intervalEnd(); +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/IntervalTree.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/IntervalTree.java new file mode 100644 index 000000000000..649af003ad6e --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/IntervalTree.java @@ -0,0 +1,32 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.editor.impl; + +import com.intellij.util.Processor; +import org.jetbrains.annotations.NotNull; + +/** + * User: cdr + */ +public interface IntervalTree extends Iterable { + boolean process(@NotNull Processor processor); + boolean processOverlappingWith(int start, int end, @NotNull Processor processor); + boolean processOverlappingWith(int offset, @NotNull Processor processor); + + Object add(@NotNull T interval); + boolean remove(@NotNull T interval); + +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/IntervalTreeImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/IntervalTreeImpl.java new file mode 100644 index 000000000000..95ac17cbb75c --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/IntervalTreeImpl.java @@ -0,0 +1,612 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.editor.impl; + +import com.intellij.openapi.editor.ex.RangeMarkerEx; +import com.intellij.openapi.util.Trinity; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.Processor; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * User: cdr + */ +public abstract class IntervalTreeImpl extends RedBlackTree implements IntervalTree { + protected final ReadWriteLock l = new ReentrantReadWriteLock(); + private final EqualStartIntervalComparator comparator; + + public class MyNode extends DeltaNode { + public MyNode(T key) { + super(key); + } + @Override + public MyNode getLeft() { + return (MyNode)super.getLeft(); + } + + @Override + public MyNode getRight() { + return (MyNode)super.getRight(); + } + + @Override + public MyNode getParent() { + return (MyNode)super.getParent(); + } + } + + private void pushDeltaFromRoot(MyNode node) { + if (node != null) { + pushDeltaFromRoot(node.getParent()); + pushDelta(node); + } + } + + protected static class DeltaNode extends RedBlackTree.Node { + protected int maxEnd; // max of all intervalEnd()s among all children. + protected int delta; // delta of startOffset. getStartOffset() = myStartOffset + Sum of deltas up to root + public DeltaNode(T key) { + // maxEnd == 0 so to not dusrupt existing maxes + super(key); + } + } + + + @Override + protected MyNode createNewNode(T key) { + return new MyNode(key); + } + + @Override + protected Node lookupNode(T key, Node root) { + int delta = 0; + int delta1 = computeDeltaUpToRoot(((RangeMarkerImpl)key).myNode); + while (root != null) { + delta += ((MyNode)root).delta; + int compResult = compare(key, delta1, root.key, delta); + if (compResult < 0) { + root = root.getLeft(); + } + else if (compResult > 0) { + root = root.getRight(); + } + else { + return root; + } + } + return root; + } + + private int compare(T i1, int delta1, T i2, int delta2) { + int start1 = i1.intervalStart() + delta1; + int start2 = i2.intervalStart() + delta2; + if (start1 != start2) return start1 - start2; + int equalStartCompare = comparator.compare(i1, i2); + return equalStartCompare; + } + + protected interface EqualStartIntervalComparator { + int compare(T i1, T i2); + } + + public IntervalTreeImpl(EqualStartIntervalComparator comparator) { + super(null); + this.comparator = comparator; + } + + protected MyNode getRoot() { + return (MyNode)root; + } + + public boolean process(@NotNull Processor processor) { + try { + l.readLock().lock(); + checkMax(true); + return process(getRoot(), processor, modCount); + } + finally { + l.readLock().unlock(); + } + } + + protected boolean process(Node root, Processor processor, int modCountBefore) { + if (root == null) return true; + + if (!process(root.getLeft(), processor, modCountBefore)) return false; + if (!processor.process(root.key)) return false; + assert modCount == modCountBefore; + + return process(root.getRight(), processor, modCountBefore); + } + + public boolean processOverlappingWith(int start, int end, @NotNull Processor processor) { + try { + l.readLock().lock(); + checkMax(true); + return processOverlappingWith(getRoot(), start, end, processor, modCount, 0); + } + finally { + l.readLock().unlock(); + } + } + + private boolean processOverlappingWith(MyNode root, int start, int end, Processor processor, int modCountBefore, int deltaUpToRootExclusive) { + if (root == null) { + return true; + } + assert root.key.isValid(); + + int delta = deltaUpToRootExclusive + root.delta; + if (start > maxEndOf(root, deltaUpToRootExclusive)) { + return true; // right of the rightmost interval in the subtree + } + + if (!processOverlappingWith(root.getLeft(), start, end, processor, modCountBefore, delta)) return false; + int myStartOffset = root.key.intervalStart() + delta; + int myEndOffset = root.key.intervalEnd() + delta; + boolean overlaps = Math.max(myStartOffset, start) <= Math.min(myEndOffset, end); + if (overlaps && !processor.process(root.key)) return false; + assert modCount == modCountBefore; + + if (end < myStartOffset) { + return true; // left of the root, cant be in the right subtree + } + + return processOverlappingWith(root.getRight(), start, end, processor, modCountBefore, delta); + } + + public boolean processOverlappingWith(int offset, @NotNull Processor processor) { + try { + l.readLock().lock(); + checkMax(true); + return processOverlapping(getRoot(), offset, processor, modCount, 0); + } + finally { + l.readLock().unlock(); + } + } + + protected boolean processOverlapping(MyNode root, int offset, Processor processor, int modCountBefore, int deltaUpToRootExclusive) { + if (root == null) { + return true; + } + assert root.key.isValid(); + int delta = deltaUpToRootExclusive + root.delta; + if (offset > maxEndOf(root, deltaUpToRootExclusive)) { + return true; // right of the rightmost interval in the subtree + } + + if (!processOverlapping(root.getLeft(), offset, processor, modCountBefore, delta)) return false; + int myStartOffset = root.key.intervalStart() + delta; + int myEndOffset = root.key.intervalEnd() + delta; + boolean overlaps = myStartOffset <= offset && offset < myEndOffset; + + if (overlaps && !processor.process(root.key)) return false; + assert modCount == modCountBefore; + + if (offset < myStartOffset) { + return true; // left of the root, cant be in the right subtree + } + + return processOverlapping(root.getRight(), offset, processor, modCountBefore, delta); + } + + private void insert(MyNode newNode) { + T key = newNode.key; + if (root == null) { + root = newNode; + } + else { + MyNode n = (MyNode)root; + int delta = 0; + loop: + while (true) { + assert n.key.isValid(); + delta += n.delta; + int compResult = compare(key, 0, n.key, delta); + if (compResult < 0) { + if (n.getLeft() == null) { + n.setLeft(newNode); + break loop; + } + n = n.getLeft(); + } + else if (compResult > 0) { + if (n.getRight() == null) { + n.setRight(newNode); + break loop; + } + n = n.getRight(); + } + else { + T i1 = key; + T i2 = n.key; + int delta1 = 0; + int delta2 = delta; + assert false : "already inserted: " + i1 + ":" + delta1 + "; "+ ((RangeMarkerEx)i1).getId() +" <-> " + i2 + ":" + delta2 + "; "+ ((RangeMarkerEx)i2).getId()+ " iden=" + (i1 == i2); + return; + } + } + newNode.delta = -delta; + newNode.setParent(n); + } + size++; + correctMaxUp(newNode); + insertCase1(newNode); + verifyProperties(); + } + + public MyNode add(@NotNull T interval) { + try { + l.writeLock().lock(); + checkMax(true); + + modCount++; + MyNode newNode = createNewNode(interval); + insert(newNode); + + checkMax(false); // myNode still not assigned + return newNode; + } + finally { + l.writeLock().unlock(); + } + } + + public void checkMax(boolean assertInvalid) { + if (!VERIFY) return; + checkMax(getRoot(), 0, assertInvalid); + } + + // returns real (minStart, maxStart, maxEnd) + protected Trinity checkMax(MyNode root, int deltaUpToRootExclusive, boolean assertInvalid) { + if (root == null) return Trinity.create(Integer.MAX_VALUE,Integer.MIN_VALUE,Integer.MIN_VALUE); + int delta = deltaUpToRootExclusive + (root.key.isValid() ? root.delta : 0); + Trinity l = checkMax(root.getLeft(), delta, assertInvalid); + int minLeftStart = l.first; + int maxLeftStart = l.second; + int maxLeftEnd = l.third; + Trinity r = checkMax(root.getRight(), delta, assertInvalid); + int maxRightEnd = r.third; + int minRightStart = r.first; + int maxRightStart = r.second; + if (!root.key.isValid()) { + if (assertInvalid) assert false : root.key; + return Trinity.create(Math.min(minLeftStart, minRightStart), Math.max(maxLeftStart, maxRightStart), Math.max(maxRightEnd, maxLeftEnd)); + } + MyNode parent = root.getParent(); + if (parent != null && assertInvalid) { + int c = compare(root.key, delta, parent.key, delta - root.delta); + assert c != 0; + assert c < 0 && parent.getLeft() == root || c > 0 && parent.getRight() == root; + } + assert delta + root.maxEnd == Math.max(maxLeftEnd, Math.max(maxRightEnd, delta + root.key.intervalEnd())); + int myStartOffset = delta + root.key.intervalStart(); + assert maxLeftStart <= myStartOffset; + assert minRightStart >= myStartOffset; + assert myStartOffset >= 0; + assert minLeftStart == Integer.MAX_VALUE || minLeftStart <= myStartOffset; + assert maxRightStart == Integer.MIN_VALUE || maxRightStart >= myStartOffset; + int minStart = Math.min(minLeftStart, myStartOffset); + int maxStart = Math.max(myStartOffset, Math.max(maxLeftStart, maxRightStart)); + assert minStart <= maxStart; + return Trinity.create(minStart, maxStart, root.maxEnd + delta); + } + + @Override + protected Node maximumNode(Node n) { + MyNode root = (MyNode)n; + pushDelta(root.getParent()); + pushDelta(root); + while (root.getRight() != null) { + root = root.getRight(); + pushDelta(root); + } + return root; + } + + public boolean remove(@NotNull T interval) { + try { + l.writeLock().lock(); + checkMax(true); + + MyNode node = (MyNode)delete(interval); + if (node == null) return false; + MyNode parent = node.getParent(); + correctMaxUp(parent); + checkMax(true); + return true; + } + finally { + l.writeLock().unlock(); + } + } + + @Override + protected void deleteNode(Node n) { + pushDeltaFromRoot((MyNode)n); + super.deleteNode(n); + } + + protected void pushDelta(MyNode root) { + if (root == null || !root.key.isValid()) return; + int delta = root.delta; + if (delta != 0) { + root.key.setIntervalStart(root.key.intervalStart() + delta); + root.key.setIntervalEnd(root.key.intervalEnd() + delta); + root.maxEnd += delta; + root.delta = 0; + incDelta(root.getLeft(), delta); + incDelta(root.getRight(), delta); + } + } + private void incDelta(MyNode root, int delta) { + if (root == null) return; + if (root.key.isValid()) { + root.delta += delta; + } + else { + incDelta(root.getLeft(), delta); + incDelta(root.getRight(), delta); + } + } + + @Override + protected MyNode swapWithMaxPred(Node root, Node maxPred) { + checkMax(false); + MyNode a = (MyNode)root; + MyNode d = (MyNode)maxPred; + Color acolor = a.color; + Color dcolor = d.color; + assert !a.key.isValid() || a.delta == 0 : a.delta; + for (MyNode n = a.getLeft(); n != null; n = n.getRight()) { + assert !n.key.isValid() || n.delta == 0 : n.delta; + } + swapNodes(a, d); + + // set range of the key to be deleted so it wont disrupt maxes + a.key.setValid(false); + //a.key.setIntervalStart(d.key.intervalStart()); + //a.key.setIntervalEnd(d.key.intervalEnd()); + + //correctMaxUp(a); + a.color = dcolor; + d.color = acolor; + correctMaxUp(a); + + checkMax(false); + return a; + } + + private void swapNodes(MyNode n1, MyNode n2) { + MyNode l1 = n1.getLeft(); + MyNode r1 = n1.getRight(); + MyNode p1 = n1.getParent(); + MyNode l2 = n2.getLeft(); + MyNode r2 = n2.getRight(); + MyNode p2 = n2.getParent(); + + if (p1 != null) { + if (p1.getLeft() == n1) p1.setLeft(n2); else p1.setRight(n2); + } + else { + root = n2; + } + if (p2 != null) { + if (p2.getLeft() == n2) p2.setLeft(p2 == n1 ? l2 : n1); else p2.setRight(p2 == n1 ? r2 : n1); + } + else { + root = n1; + } + n1.setParent(p2 == n1 ? n2 : p2); + n2.setParent(p1); + + n1.setLeft(l2); + n2.setLeft(l1 == n2 ? n1 : l1); + if (l1 != null) l1.setParent(n2 == l1 ? p1 : n2); + if (r1 != null) r1.setParent(n2); + n1.setRight(r2); + n2.setRight(r1); + if (l2 != null) l2.setParent(n1); + if (r2 != null) r2.setParent(n1); + } + + protected int maxEndOf(MyNode node, int deltaUpToRootExclusive) { + if (node == null) { + return 0; + } + if (node.key.isValid()) { + return node.maxEnd + node.delta + deltaUpToRootExclusive; + } + return Math.max(maxEndOf(node.getLeft(), deltaUpToRootExclusive), maxEndOf(node.getRight(), deltaUpToRootExclusive)); + } + + // max of n.left's maxend, n.right's maxend and its own interval endOffset + protected void correctMax(@NotNull MyNode node, int deltaUpToRoot) { + if (!node.key.isValid()) return; + int realMax = Math.max(Math.max(maxEndOf(node.getLeft(), deltaUpToRoot), maxEndOf(node.getRight(), deltaUpToRoot)), + deltaUpToRoot + node.key.intervalEnd()); + node.maxEnd = realMax - deltaUpToRoot; + } + + protected void correctMaxUp(MyNode node) { + int delta = computeDeltaUpToRoot(node); + while (node != null) { + if (node.key.isValid()) { + int d = node.delta; + correctMax(node, delta); + delta -= d; + } + node = node.getParent(); + } + assert delta == 0 : delta; + } + + protected static int computeDeltaUpToRoot(DeltaNode node) { + int delta = 0; + while (node != null) { + if (node.key.isValid()) { + delta += node.delta; + } + node = (DeltaNode)node.getParent(); + } + return delta; + } + + @Override + protected void rotateRight(Node n) { + checkMax(false); + MyNode node1 = (MyNode)n; + MyNode node2 = node1.getLeft(); + MyNode node3 = node1.getRight(); + + int deltaUp = computeDeltaUpToRoot(node1.getParent()); + pushDelta(node1); + pushDelta(node2); + pushDelta(node3); + + super.rotateRight(node1); + + if (node3 != null) { + correctMax(node3, deltaUp); + } + correctMax(node1, deltaUp); + correctMax(node2, deltaUp); + checkMax(false); + } + + @Override + protected void rotateLeft(Node n) { + checkMax(false); + MyNode node1 = (MyNode)n; + MyNode node2 = node1.getLeft(); + MyNode node3 = node1.getRight(); + + int deltaUp = computeDeltaUpToRoot(node1.getParent()); + pushDelta(node1); + pushDelta(node2); + pushDelta(node3); + checkMax(false); + super.rotateLeft(node1); + + if (node2 != null) { + correctMax(node2, deltaUp); + } + correctMax(node1, deltaUp); + correctMax(node3, deltaUp); + checkMax(false); + } + + @Override + protected void replaceNode(@NotNull Node node, Node child) { + super.replaceNode(node, child); + if (child != null && node.key.isValid()) { + ((MyNode)child).delta += ((MyNode)node).delta; + //todo correct max up to root?? + } + } + + public Iterator iterator() { + MyNode firstNode = getRoot(); + while (firstNode != null && firstNode.getLeft() != null) { + firstNode = firstNode.getLeft(); + } + return createIteratorFrom(firstNode); + } + + private MyNode findMinOverlappingWith(MyNode root, Interval interval, int modCountBefore, int deltaUpToRootExclusive) { + if (root == null) { + return null; + } + assert root.key.isValid(); + + int delta = deltaUpToRootExclusive + root.delta; + if (interval.intervalStart() > maxEndOf(root, deltaUpToRootExclusive)) { + return null; // right of the rightmost interval in the subtree + } + + MyNode inLeft = findMinOverlappingWith(root.getLeft(), interval, modCountBefore, delta); + if (inLeft != null) return inLeft; + int myStartOffset = root.key.intervalStart() + delta; + int myEndOffset = root.key.intervalEnd() + delta; + boolean overlaps = Math.max(myStartOffset, interval.intervalStart()) <= Math.min(myEndOffset, interval.intervalEnd()); + if (overlaps) return root; + assert modCount == modCountBefore; + + if (interval.intervalEnd() < myStartOffset) { + return null; // left of the root, cant be in the right subtree + } + + return findMinOverlappingWith(root.getRight(), interval, modCountBefore, delta); + } + + @NotNull + public Iterator iteratorFrom(@NotNull Interval interval) { + MyNode firstOverlap = findMinOverlappingWith(getRoot(), interval, modCount, 0); + if (firstOverlap == null) { + return ContainerUtil.emptyIterator(); + } + return createIteratorFrom(firstOverlap); + } + + private Iterator createIteratorFrom(final MyNode firstNode) { + checkMax(true); + + final int modCountBefore = modCount; + return new Iterator() { + Node element = firstNode; + + public boolean hasNext() { + return element != null; + } + + public T next() { + assert modCount == modCountBefore; + if (element == null) throw new NoSuchElementException(); + Node prev = element; + element = getNext(element); + return prev.key; + } + + private Node getNext(Node element) { + Node n = element.getRight(); + if (n != null) { + while (n.getLeft()!= null) { + n = n.getLeft(); + } + return n; + } + Node parent = element.getParent(); + while (parent != null) { + if (parent.getLeft() == element) return parent; + element = parent; + parent = parent.getParent(); + } + return parent; + } + + public void remove() { + throw new IncorrectOperationException(); + } + }; + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/IterationState.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/IterationState.java index 31f6217e893a..86cde1b81c7a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/IterationState.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/IterationState.java @@ -21,46 +21,41 @@ import com.intellij.openapi.editor.CaretModel; import com.intellij.openapi.editor.FoldRegion; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.editor.colors.EditorColors; -import com.intellij.openapi.editor.ex.DocumentEx; -import com.intellij.openapi.editor.ex.EditorEx; -import com.intellij.openapi.editor.ex.FoldingModelEx; -import com.intellij.openapi.editor.ex.MarkupModelEx; +import com.intellij.openapi.editor.ex.*; import com.intellij.openapi.editor.highlighter.HighlighterIterator; import com.intellij.openapi.editor.markup.EffectType; import com.intellij.openapi.editor.markup.HighlighterLayer; import com.intellij.openapi.editor.markup.HighlighterTargetArea; import com.intellij.openapi.editor.markup.TextAttributes; +import com.intellij.openapi.util.TextRange; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.Nullable; import java.awt.*; import java.util.ArrayList; import java.util.Comparator; +import java.util.Iterator; import java.util.List; -@SuppressWarnings({"ForLoopReplaceableByForEach"}) // Way too many garbage in AbstractList.iterator() produced otherwise. +@SuppressWarnings({"ForLoopReplaceableByForEach"}) // Way too many garbage in AbrstractList.iterator() produced otherwise. public final class IterationState { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.IterationState"); private final TextAttributes myMergedAttributes = new TextAttributes(); private final HighlighterIterator myHighlighterIterator; - private final List myViewHighlighters; - private int myCurrentViewHighlighterIdx; + private final HighlighterSweep myView = new HighlighterSweep(); - private final List myDocumentHighlighters; - private int myCurrentDocHighlighterIdx; + private final HighlighterSweep myDoc = new HighlighterSweep(); private int myStartOffset; + private int myEndOffset; private final int myEnd; private final int mySelectionStart; + private final int mySelectionEnd; - - private ArrayList myCurrentHighlighters; - - private RangeHighlighterImpl myNextViewHighlighter = null; - private RangeHighlighterImpl myNextDocumentHighlighter = null; + private final List myCurrentHighlighters; private final FoldingModelEx myFoldingModel; @@ -73,7 +68,7 @@ public final class IterationState { private final Color myDefaultForeground; private final int myCaretRowStart; private final int myCaretRowEnd; - private final ArrayList myCachedAttributesList; + private final List myCachedAttributesList; private final DocumentEx myDocument; private final EditorEx myEditor; private final Color myReadOnlyColor; @@ -88,27 +83,11 @@ public final class IterationState { LOG.assertTrue(myStartOffset <= myEnd); myHighlighterIterator = editor.getHighlighter().createIterator(start); - HighlighterList editorList = ((MarkupModelEx)editor.getMarkupModel()).getHighlighterList(); - - int longestViewHighlighterLength = editorList == null ? 0 : editorList.getLongestHighlighterLength(); - myViewHighlighters = editorList == null ? null : editorList.getSortedHighlighters(); - - final MarkupModelEx docMarkup = (MarkupModelEx)editor.getDocument().getMarkupModel(editor.getProject()); - - final HighlighterList docList = docMarkup.getHighlighterList(); - myDocumentHighlighters = docList != null - ? docList.getSortedHighlighters() - : new ArrayList(); - - int longestDocHighlighterLength = docList != null - ? docList.getLongestHighlighterLength() - : 0; - hasSelection = useCaretAndSelection && editor.getSelectionModel().hasSelection(); mySelectionStart = hasSelection ? editor.getSelectionModel().getSelectionStart() : -1; mySelectionEnd = hasSelection ? editor.getSelectionModel().getSelectionEnd() : -1; - myFoldingModel = (FoldingModelEx)editor.getFoldingModel(); + myFoldingModel = editor.getFoldingModel(); myFoldTextAttributes = myFoldingModel.getPlaceholderAttributes(); mySelectionAttributes = editor.getSelectionModel().getTextAttributes(); @@ -119,78 +98,67 @@ public final class IterationState { myDefaultBackground = editor.getColorsScheme().getDefaultBackground(); myDefaultForeground = editor.getColorsScheme().getDefaultForeground(); - myCurrentHighlighters = new ArrayList(); - - myCurrentViewHighlighterIdx = initHighlighterIterator(start, myViewHighlighters, longestViewHighlighterLength); - while (myCurrentViewHighlighterIdx < myViewHighlighters.size()) { - myNextViewHighlighter = myViewHighlighters.get(myCurrentViewHighlighterIdx); - if (!skipHighlighter(myNextViewHighlighter)) break; - myCurrentViewHighlighterIdx++; - } - if (myCurrentViewHighlighterIdx == myViewHighlighters.size()) myNextViewHighlighter = null; - - myCurrentDocHighlighterIdx = initHighlighterIterator(start, myDocumentHighlighters, longestDocHighlighterLength); - myNextDocumentHighlighter = null; - while (myCurrentDocHighlighterIdx < myDocumentHighlighters.size()) { - myNextDocumentHighlighter = myDocumentHighlighters.get(myCurrentDocHighlighterIdx); - if (!skipHighlighter(myNextDocumentHighlighter)) break; - myCurrentDocHighlighterIdx++; - } - if (myCurrentDocHighlighterIdx == myDocumentHighlighters.size()) myNextDocumentHighlighter = null; - - advanceSegmentHighlighters(); - myCaretRowStart = caretModel.getVisualLineStart(); myCaretRowEnd = caretModel.getVisualLineEnd(); - - myEndOffset = Math.min(getHighlighterEnd(myStartOffset), getSelectionEnd(myStartOffset)); - myEndOffset = Math.min(myEndOffset, getSegmentHighlightersEnd()); - myEndOffset = Math.min(myEndOffset, getFoldRangesEnd(myStartOffset)); - myEndOffset = Math.min(myEndOffset, getCaretEnd(myStartOffset)); - myEndOffset = Math.min(myEndOffset, getGuardedBlockEnd(myStartOffset)); - - myCurrentFold = myFoldingModel.getCollapsedRegionAtOffset(myStartOffset); - if (myCurrentFold != null) { - myEndOffset = myCurrentFold.getEndOffset(); - } - myCachedAttributesList = new ArrayList(5); - reinit(); + myCurrentHighlighters = new ArrayList(); + + MarkupModelEx editorMarkup = (MarkupModelEx)editor.getMarkupModel(); + myView.init(editorMarkup, start); + + final MarkupModelEx docMarkup = (MarkupModelEx)editor.getDocument().getMarkupModel(editor.getProject()); + myDoc.init(docMarkup, start); + + myEndOffset = myStartOffset; + + advance(); } - private int initHighlighterIterator(int start, List sortedHighlighters, int longestHighlighterLength) { - int low = 0; - int high = sortedHighlighters.size(); - int search = myDocument.getLineStartOffset(myDocument.getLineNumber(start)) - - longestHighlighterLength - 1; + private class HighlighterSweep { + private RangeHighlighterEx myNextHighlighter = null; + private Iterator myIterator; - if (search > 0) { - while (low < high) { - int mid = (low + high) / 2; - while (mid > 0 && !sortedHighlighters.get(mid).isValid()) mid--; - if (mid < low + 1) break; - RangeHighlighterImpl midHighlighter = sortedHighlighters.get(mid); - if (midHighlighter.getStartOffset() < search) { - low = mid + 1; + private void init(MarkupModelEx markupModel, int start) { + myIterator = markupModel.iteratorFrom(new TextRangeInterval(start, myDocument.getTextLength())); + + while (myIterator.hasNext()) { + myNextHighlighter = myIterator.next(); + if (!skipHighlighter(myNextHighlighter)) break; + else myNextHighlighter = null; + } + } + + private void advance() { + if (myNextHighlighter != null) { + if (myNextHighlighter.getAffectedAreaStartOffset() <= myStartOffset) { + myCurrentHighlighters.add(myNextHighlighter); + myNextHighlighter = null; } - else { - high = mid - 1; + } + + while (myNextHighlighter == null && myIterator.hasNext()) { + RangeHighlighterEx highlighter = myIterator.next(); + if (!skipHighlighter(highlighter)) { + if (highlighter.getAffectedAreaStartOffset() > myStartOffset) { + myNextHighlighter = highlighter; + } + else { + myCurrentHighlighters.add(highlighter); + } } } } - for (int i = low == high ? low : 0; i < sortedHighlighters.size(); i++) { - RangeHighlighterImpl rangeHighlighter = sortedHighlighters.get(i); - if (!skipHighlighter(rangeHighlighter) && - rangeHighlighter.getAffectedAreaEndOffset() >= start) { - return i; + private int getMinSegmentHighlighterEnd() { + if (myNextHighlighter != null) { + return myNextHighlighter.getAffectedAreaStartOffset(); } + return Integer.MAX_VALUE; } - return sortedHighlighters.size(); } - private boolean skipHighlighter(RangeHighlighterImpl highlighter) { + private boolean skipHighlighter(RangeHighlighterEx highlighter) { if (!highlighter.isValid() || highlighter.isAfterEndOfLine() || highlighter.getTextAttributes() == null) return true; final FoldRegion region = myFoldingModel.getCollapsedRegionAtOffset(highlighter.getAffectedAreaStartOffset()); if (region != null && region == myFoldingModel.getCollapsedRegionAtOffset(highlighter.getAffectedAreaEndOffset())) return true; @@ -207,7 +175,7 @@ public final class IterationState { } else { myEndOffset = Math.min(getHighlighterEnd(myStartOffset), getSelectionEnd(myStartOffset)); - myEndOffset = Math.min(myEndOffset, getSegmentHighlightersEnd()); + myEndOffset = Math.min(myEndOffset, getMinSegmentHighlightersEnd()); myEndOffset = Math.min(myEndOffset, getFoldRangesEnd(myStartOffset)); myEndOffset = Math.min(myEndOffset, getCaretEnd(myStartOffset)); myEndOffset = Math.min(myEndOffset, getGuardedBlockEnd(myStartOffset)); @@ -268,67 +236,16 @@ public final class IterationState { } private void advanceSegmentHighlighters() { - if (myNextDocumentHighlighter != null) { - if (myNextDocumentHighlighter.getAffectedAreaStartOffset() <= myStartOffset) { - myCurrentHighlighters.add(myNextDocumentHighlighter); - myNextDocumentHighlighter = null; - } - } + myDoc.advance(); - if (myNextViewHighlighter != null) { - if (myNextViewHighlighter.getAffectedAreaStartOffset() <= myStartOffset) { - myCurrentHighlighters.add(myNextViewHighlighter); - myNextViewHighlighter = null; - } - } + myView.advance(); - - RangeHighlighterImpl highlighter; - - final int docHighlightersSize = myDocumentHighlighters.size(); - while (myNextDocumentHighlighter == null && myCurrentDocHighlighterIdx < docHighlightersSize) { - highlighter = myDocumentHighlighters.get(myCurrentDocHighlighterIdx++); - if (!skipHighlighter(highlighter)) { - if (highlighter.getAffectedAreaStartOffset() > myStartOffset) { - myNextDocumentHighlighter = highlighter; - break; - } - else { - myCurrentHighlighters.add(highlighter); - } + for (int i = myCurrentHighlighters.size() - 1; i >= 0; i--) { + RangeHighlighterEx highlighter = myCurrentHighlighters.get(i); + if (highlighter.getAffectedAreaEndOffset() <= myStartOffset) { + myCurrentHighlighters.remove(i); } } - - final int viewHighlightersSize = myViewHighlighters.size(); - while (myNextViewHighlighter == null && myCurrentViewHighlighterIdx < viewHighlightersSize) { - highlighter = myViewHighlighters.get(myCurrentViewHighlighterIdx++); - if (!skipHighlighter(highlighter)) { - if (highlighter.getAffectedAreaStartOffset() > myStartOffset) { - myNextViewHighlighter = highlighter; - break; - } - else { - myCurrentHighlighters.add(highlighter); - } - } - } - - if (myCurrentHighlighters.size() == 1) { - //Optimization - if (myCurrentHighlighters.get(0).getAffectedAreaEndOffset() <= myStartOffset) { - myCurrentHighlighters = new ArrayList(); - } - } - else if (!myCurrentHighlighters.isEmpty()) { - ArrayList copy = new ArrayList(myCurrentHighlighters.size()); - for (int i = 0; i < myCurrentHighlighters.size(); i++) { - highlighter = myCurrentHighlighters.get(i); - if (highlighter.getAffectedAreaEndOffset() > myStartOffset) { - copy.add(highlighter); - } - } - myCurrentHighlighters = copy; - } } private int getFoldRangesEnd(int startOffset) { @@ -356,22 +273,18 @@ public final class IterationState { return end; } - private int getSegmentHighlightersEnd() { + private int getMinSegmentHighlightersEnd() { int end = myEnd; - for (RangeHighlighterImpl highlighter : myCurrentHighlighters) { + for (int i = 0; i < myCurrentHighlighters.size(); i++) { + RangeHighlighterEx highlighter = myCurrentHighlighters.get(i); if (highlighter.getAffectedAreaEndOffset() < end) { end = highlighter.getAffectedAreaEndOffset(); } } - if (myNextDocumentHighlighter != null && myNextDocumentHighlighter.getAffectedAreaStartOffset() < end) { - end = myNextDocumentHighlighter.getAffectedAreaStartOffset(); - } - - if (myNextViewHighlighter != null && myNextViewHighlighter.getAffectedAreaStartOffset() < end) { - end = myNextViewHighlighter.getAffectedAreaStartOffset(); - } + end = Math.min(end, myDoc.getMinSegmentHighlighterEnd()); + end = Math.min(end, myView.getMinSegmentHighlighterEnd()); return end; } @@ -400,52 +313,53 @@ public final class IterationState { } for (int i = 0; i < size; i++) { - RangeHighlighterImpl highlighter = myCurrentHighlighters.get(i); + RangeHighlighterEx highlighter = myCurrentHighlighters.get(i); if (highlighter.getTextAttributes() == TextAttributes.ERASE_MARKER) { syntax = null; } } - myCachedAttributesList.clear(); + List cachedAttributes = myCachedAttributesList; + cachedAttributes.clear(); for (int i = 0; i < size; i++) { - RangeHighlighterImpl highlighter = myCurrentHighlighters.get(i); + RangeHighlighterEx highlighter = myCurrentHighlighters.get(i); if (selection != null && highlighter.getLayer() < HighlighterLayer.SELECTION) { - myCachedAttributesList.add(selection); + cachedAttributes.add(selection); selection = null; } if (syntax != null && highlighter.getLayer() < HighlighterLayer.SYNTAX) { if (fold != null) { - myCachedAttributesList.add(fold); + cachedAttributes.add(fold); fold = null; } - myCachedAttributesList.add(syntax); + cachedAttributes.add(syntax); syntax = null; } if (guard != null && highlighter.getLayer() < HighlighterLayer.GUARDED_BLOCKS) { - myCachedAttributesList.add(guard); + cachedAttributes.add(guard); guard = null; } if (caret != null && highlighter.getLayer() < HighlighterLayer.CARET_ROW) { - myCachedAttributesList.add(caret); + cachedAttributes.add(caret); caret = null; } TextAttributes textAttributes = highlighter.getTextAttributes(); if (textAttributes != null) { - myCachedAttributesList.add(textAttributes); + cachedAttributes.add(textAttributes); } } - if (selection != null) myCachedAttributesList.add(selection); - if (fold != null) myCachedAttributesList.add(fold); - if (guard != null) myCachedAttributesList.add(guard); - if (syntax != null) myCachedAttributesList.add(syntax); - if (caret != null) myCachedAttributesList.add(caret); + if (selection != null) cachedAttributes.add(selection); + if (fold != null) cachedAttributes.add(fold); + if (guard != null) cachedAttributes.add(guard); + if (syntax != null) cachedAttributes.add(syntax); + if (caret != null) cachedAttributes.add(caret); Color fore = null; Color back = isInGuardedBlock ? myReadOnlyColor : null; @@ -453,8 +367,8 @@ public final class IterationState { EffectType effectType = null; int fontType = 0; - for (int i = 0; i < myCachedAttributesList.size(); i++) { - TextAttributes attrs = myCachedAttributesList.get(i); + for (int i = 0; i < cachedAttributes.size(); i++) { + TextAttributes attrs = cachedAttributes.get(i); if (fore == null) { fore = ifDiffers(attrs.getForegroundColor(), myDefaultForeground); @@ -517,10 +431,10 @@ public final class IterationState { Color caret = isInCaretRow && myCaretRowAttributes != null ? myCaretRowAttributes.getBackgroundColor() : null; - ContainerUtil.quickSort(myCurrentHighlighters, LayerComparator.INSTANCE); + ContainerUtil.quickSort(myCurrentHighlighters, LayerComparator.INSTANCE); for (int i = 0; i < myCurrentHighlighters.size(); i++) { - RangeHighlighterImpl highlighter = myCurrentHighlighters.get(i); + RangeHighlighterEx highlighter = myCurrentHighlighters.get(i); if (caret != null && highlighter.getLayer() < HighlighterLayer.CARET_ROW) { return caret; } @@ -540,9 +454,9 @@ public final class IterationState { return caret; } - private static class LayerComparator implements Comparator { + private static class LayerComparator implements Comparator { private static final LayerComparator INSTANCE = new LayerComparator(); - public int compare(RangeHighlighterImpl o1, RangeHighlighterImpl o2) { + public int compare(RangeHighlighterEx o1, RangeHighlighterEx o2) { int layerDiff = o2.getLayer() - o1.getLayer(); if (layerDiff != 0) { return layerDiff; diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/MarkupModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/MarkupModelImpl.java index 6530bad009ba..ac7c4d293274 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/MarkupModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/MarkupModelImpl.java @@ -24,40 +24,37 @@ */ package com.intellij.openapi.editor.impl; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.ex.MarkupModelEx; -import com.intellij.openapi.editor.impl.event.MarkupModelEvent; +import com.intellij.openapi.editor.ex.RangeHighlighterEx; +import com.intellij.openapi.editor.ex.RangeMarkerEx; import com.intellij.openapi.editor.impl.event.MarkupModelListener; import com.intellij.openapi.editor.markup.HighlighterTargetArea; import com.intellij.openapi.editor.markup.RangeHighlighter; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.util.UserDataHolderBase; -import com.intellij.openapi.application.ApplicationManager; +import com.intellij.util.CommonProcessors; +import com.intellij.util.Processor; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; -import java.util.Collection; +import java.util.Iterator; import java.util.List; public class MarkupModelImpl extends UserDataHolderBase implements MarkupModelEx { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.MarkupModelImpl"); private final DocumentImpl myDocument; - private final HighlighterList myHighlighterList; - private final Collection myHighlighters = new ArrayList(); private RangeHighlighter[] myCachedHighlighters; - private final List myListeners = new ArrayList(); - private MarkupModelListener[] myCachedListeners; + private final List myListeners = ContainerUtil.createEmptyCOWList(); + private final RangeHighlighterTree myHighlighterTree; MarkupModelImpl(DocumentImpl document) { myDocument = document; - myHighlighterList = new HighlighterList(document) { - @Override - protected void assertDispatchThread() { - MarkupModelImpl.this.assertDispatchThread(); - } - }; + myHighlighterTree = new RangeHighlighterTree(myDocument); } protected void assertDispatchThread() { @@ -65,13 +62,12 @@ public class MarkupModelImpl extends UserDataHolderBase implements MarkupModelEx } public void dispose() { - myHighlighterList.dispose(); } @NotNull public RangeHighlighter addLineHighlighter(int lineNumber, int layer, TextAttributes textAttributes) { if (lineNumber >= getDocument().getLineCount() || lineNumber < 0) { - throw new IndexOutOfBoundsException("lineNumber:" + lineNumber + ". Shold be in [0, " + (getDocument().getLineCount() - 1) + "]"); + throw new IndexOutOfBoundsException("lineNumber:" + lineNumber + ". Must be in [0, " + (getDocument().getLineCount() - 1) + "]"); } int offset = getFirstNonspaceCharOffset(getDocument(), lineNumber); @@ -105,7 +101,9 @@ public class MarkupModelImpl extends UserDataHolderBase implements MarkupModelEx @NotNull public RangeHighlighter[] getAllHighlighters() { if (myCachedHighlighters == null) { - myCachedHighlighters = myHighlighters.toArray(new RangeHighlighter[myHighlighters.size()]); + ArrayList list = new ArrayList(); + myHighlighterTree.process(new CommonProcessors.CollectProcessor(list)); + myCachedHighlighters = list.toArray(new RangeHighlighter[list.size()]); } return myCachedHighlighters; } @@ -116,14 +114,22 @@ public class MarkupModelImpl extends UserDataHolderBase implements MarkupModelEx TextAttributes textAttributes, HighlighterTargetArea targetArea, boolean isPersistent) { - RangeHighlighterImpl segmentHighlighter = new RangeHighlighterImpl(this, startOffset, endOffset, layer, targetArea, - textAttributes, isPersistent); - myHighlighters.add(segmentHighlighter); - myCachedHighlighters = null; - myHighlighterList.addSegmentHighlighter(segmentHighlighter); - fireSegmentHighlighterChanged(segmentHighlighter); - return segmentHighlighter; + RangeHighlighterEx segmentHighlighter = isPersistent + ? new PersistentRangeHighlighterImpl(this, startOffset, layer, targetArea, textAttributes) + : new RangeHighlighterImpl(this, startOffset, endOffset, layer, targetArea, textAttributes); + RangeMarkerImpl marker = (RangeMarkerImpl)segmentHighlighter; + marker.registerInDocument(); + + myCachedHighlighters = null; + fireAfterAdded(segmentHighlighter); + return segmentHighlighter; + } + + void addRangeHighlighter(RangeHighlighterEx marker) { + marker.setValid(true); + ((RangeMarkerImpl)marker).myNode = (IntervalTreeImpl.MyNode)myHighlighterTree.add(marker); + myHighlighterTree.checkMax(true); } @NotNull @@ -136,20 +142,24 @@ public class MarkupModelImpl extends UserDataHolderBase implements MarkupModelEx } public void removeHighlighter(RangeHighlighter segmentHighlighter) { - boolean removed = myHighlighters.remove(segmentHighlighter); - LOG.assertTrue(removed); myCachedHighlighters = null; - myHighlighterList.removeSegmentHighlighter(segmentHighlighter); - fireSegmentHighlighterChanged(segmentHighlighter); + if (!segmentHighlighter.isValid()) return; + + fireBeforeRemoved((RangeHighlighterEx)segmentHighlighter); + + boolean removed = myHighlighterTree.remove((RangeHighlighterEx)segmentHighlighter); + LOG.assertTrue(removed); } public void removeAllHighlighters() { - for (RangeHighlighter highlighter : myHighlighters) { - fireSegmentHighlighterChanged(highlighter); - myHighlighterList.removeSegmentHighlighter(highlighter); - } - myHighlighters.clear(); + myHighlighterTree.process(new Processor() { + public boolean process(RangeMarkerEx rangeMarkerEx) { + fireBeforeRemoved((RangeHighlighterEx)rangeMarkerEx); + return true; + } + }); myCachedHighlighters = null; + myHighlighterTree.clear(); } @NotNull @@ -157,41 +167,58 @@ public class MarkupModelImpl extends UserDataHolderBase implements MarkupModelEx return myDocument; } - public void addMarkupModelListener(MarkupModelListener listener) { + public void addMarkupModelListener(@NotNull MarkupModelListener listener) { myListeners.add(listener); - myCachedListeners = null; } - public void removeMarkupModelListener(MarkupModelListener listener) { + public void removeMarkupModelListener(@NotNull MarkupModelListener listener) { boolean success = myListeners.remove(listener); LOG.assertTrue(success); - myCachedListeners = null; } - public void setRangeHighlighterAttributes(final RangeHighlighter highlighter, final TextAttributes textAttributes) { + public void setRangeHighlighterAttributes(@NotNull final RangeHighlighter highlighter, final TextAttributes textAttributes) { ((RangeHighlighterImpl)highlighter).setTextAttributes(textAttributes); } - private MarkupModelListener[] getCachedListeners() { - if (myCachedListeners == null) { - myCachedListeners = myListeners.isEmpty() ? MarkupModelListener.EMPTY_ARRAY : myListeners.toArray(new MarkupModelListener[myListeners.size()]); + protected void fireAttributesChanged(RangeHighlighterEx segmentHighlighter) { + for (MarkupModelListener listener : myListeners) { + listener.attributesChanged(segmentHighlighter); } - return myCachedListeners; } - - protected void fireSegmentHighlighterChanged(RangeHighlighter segmentHighlighter) { - MarkupModelEvent event = new MarkupModelEvent(this, segmentHighlighter); - MarkupModelListener[] listeners = getCachedListeners(); - for (MarkupModelListener listener : listeners) { - listener.rangeHighlighterChanged(event); + private void fireAfterAdded(RangeHighlighterEx segmentHighlighter) { + for (MarkupModelListener listener : myListeners) { + listener.afterAdded(segmentHighlighter); + } + } + private void fireBeforeRemoved(RangeHighlighterEx segmentHighlighter) { + for (MarkupModelListener listener : myListeners) { + listener.beforeRemoved(segmentHighlighter); } } - public HighlighterList getHighlighterList() { - return myHighlighterList; + public boolean containsHighlighter(@NotNull final RangeHighlighter highlighter) { + return !myHighlighterTree.processOverlappingWith(highlighter.getStartOffset(), highlighter.getEndOffset(), new Processor() { + public boolean process(RangeHighlighterEx h) { + return h.getId() != ((RangeHighlighterEx)highlighter).getId(); + } + }); } - public boolean containsHighlighter(RangeHighlighter highlighter) { - return myHighlighters.contains(highlighter); + public boolean processHighlightsOverlappingWith(int start, int end, @NotNull Processor processor) { + return myHighlighterTree.processOverlappingWith(start, end, processor); } + + public Iterator iterator() { + return myHighlighterTree.iterator(); + } + + @NotNull + public Iterator iteratorFrom(@NotNull Interval interval) { + return myHighlighterTree.iteratorFrom(interval); + } + + public boolean sweep(int start, int end, @NotNull SweepProcessor sweepProcessor) { + return myHighlighterTree.sweep(start, end, sweepProcessor); + } + } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/MutableInterval.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/MutableInterval.java new file mode 100644 index 000000000000..fc6121274496 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/MutableInterval.java @@ -0,0 +1,26 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.editor.impl; + +/** + * User: cdr + */ +public interface MutableInterval extends Interval { + int setIntervalStart(int start); + int setIntervalEnd(int end); + boolean isValid(); + boolean setValid(boolean value); +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/PersistentLineMarker.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/PersistentLineMarker.java index 1f2453339928..0d3dc51fbf4c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/PersistentLineMarker.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/PersistentLineMarker.java @@ -25,7 +25,7 @@ import com.intellij.openapi.editor.impl.event.DocumentEventImpl; public class PersistentLineMarker extends RangeMarkerImpl { private int myLine; public PersistentLineMarker(DocumentEx document, int offset) { - super(document, offset, offset); + super(document, document.getLineStartOffset(document.getLineNumber(offset)), document.getLineEndOffset(document.getLineNumber(offset))); myLine = document.getLineNumber(offset); } @@ -38,8 +38,9 @@ public class PersistentLineMarker extends RangeMarkerImpl { invalidate(); } else { - myStart = MarkupModelImpl.getFirstNonspaceCharOffset(getDocument(), myLine); - myEnd = myStart; + DocumentEx document = getDocument(); + myStart = document.getLineStartOffset(myLine); + myEnd = document.getLineEndOffset(myLine); } } else { diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/PersistentRangeHighlighterImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/PersistentRangeHighlighterImpl.java new file mode 100644 index 000000000000..fe8dbf6d4df7 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/PersistentRangeHighlighterImpl.java @@ -0,0 +1,166 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.editor.impl; + +import com.intellij.openapi.editor.ex.DocumentEx; +import com.intellij.openapi.editor.ex.RangeHighlighterEx; +import com.intellij.openapi.editor.markup.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; + +/** + * Implementation of the markup element for the editor and document. + * @author max + */ +public class PersistentRangeHighlighterImpl extends PersistentLineMarker implements RangeHighlighterEx { + private final RangeHighlighterData data; + PersistentRangeHighlighterImpl(@NotNull MarkupModel model, + int start, + int layer, + @NotNull HighlighterTargetArea target, + TextAttributes textAttributes + ) { + super((DocumentEx)model.getDocument(), start); + data = new RangeHighlighterData(model, layer, target, textAttributes, this); + } + + @Override + protected void registerInDocument() { + // we store highlighters in MarkupModel + data.registerMe(); + } + + @Override + protected boolean unregisterInDocument() { + // we store highlighters in MarkupModel + data.unregisterMe(); + myNode = null; + return true; + } + + // delegates + + public TextAttributes getTextAttributes() { + return data.getTextAttributes(); + } + + public void setTextAttributes(TextAttributes textAttributes) { + data.setTextAttributes(textAttributes); + } + + public void changeAttributesInBatch(@NotNull Runnable change) { + data.changeAttributesInBatch(change); + } + + public int getLayer() { + return data.getLayer(); + } + + public HighlighterTargetArea getTargetArea() { + return data.getTargetArea(); + } + + public LineMarkerRenderer getLineMarkerRenderer() { + return data.getLineMarkerRenderer(); + } + + public void setLineMarkerRenderer(LineMarkerRenderer renderer) { + data.setLineMarkerRenderer(renderer); + } + + public CustomHighlighterRenderer getCustomRenderer() { + return data.getCustomRenderer(); + } + + public void setCustomRenderer(CustomHighlighterRenderer renderer) { + data.setCustomRenderer(renderer); + } + + public GutterIconRenderer getGutterIconRenderer() { + return data.getGutterIconRenderer(); + } + + public void setGutterIconRenderer(GutterIconRenderer renderer) { + data.setGutterIconRenderer(renderer); + } + + public Color getErrorStripeMarkColor() { + return data.getErrorStripeMarkColor(); + } + + public void setErrorStripeMarkColor(Color color) { + data.setErrorStripeMarkColor(color); + } + + public Object getErrorStripeTooltip() { + return data.getErrorStripeTooltip(); + } + + public void setErrorStripeTooltip(Object tooltipObject) { + data.setErrorStripeTooltip(tooltipObject); + } + + public boolean isThinErrorStripeMark() { + return data.isThinErrorStripeMark(); + } + + public void setThinErrorStripeMark(boolean value) { + data.setThinErrorStripeMark(value); + } + + public Color getLineSeparatorColor() { + return data.getLineSeparatorColor(); + } + + public void setLineSeparatorColor(Color color) { + data.setLineSeparatorColor(color); + } + + public SeparatorPlacement getLineSeparatorPlacement() { + return data.getLineSeparatorPlacement(); + } + + public void setLineSeparatorPlacement(@Nullable SeparatorPlacement placement) { + data.setLineSeparatorPlacement(placement); + } + + public void setEditorFilter(@NotNull MarkupEditorFilter filter) { + data.setEditorFilter(filter); + } + + @NotNull + public MarkupEditorFilter getEditorFilter() { + return data.getEditorFilter(); + } + + public boolean isAfterEndOfLine() { + return data.isAfterEndOfLine(); + } + + public void setAfterEndOfLine(boolean afterEndOfLine) { + data.setAfterEndOfLine(afterEndOfLine); + } + + public int getAffectedAreaStartOffset() { + return data.getAffectedAreaStartOffset(); + } + + public int getAffectedAreaEndOffset() { + return data.getAffectedAreaEndOffset(); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterData.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterData.java new file mode 100644 index 000000000000..e60ef481991c --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterData.java @@ -0,0 +1,241 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.editor.impl; + +import com.intellij.openapi.editor.ex.RangeHighlighterEx; +import com.intellij.openapi.editor.markup.*; +import com.intellij.openapi.util.Comparing; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; + +/** + * User: cdr + */ +class RangeHighlighterData { + private final MarkupModel myModel; + private final int myLayer; + private final HighlighterTargetArea myTargetArea; + private TextAttributes myTextAttributes; + private LineMarkerRenderer myLineMarkerRenderer; + private Color myErrorStripeColor; + private Color myLineSeparatorColor; + private SeparatorPlacement mySeparatorPlacement; + private boolean isAfterEndOfLine; + private GutterIconRenderer myGutterIconRenderer; + private boolean myErrorStripeMarkIsThin; + private Object myErrorStripeTooltip; + private MarkupEditorFilter myFilter = MarkupEditorFilter.EMPTY; + private CustomHighlighterRenderer myCustomRenderer; + private final RangeHighlighterEx rangeHighlighter; + + RangeHighlighterData(@NotNull MarkupModel model, + int layer, + @NotNull HighlighterTargetArea target, + TextAttributes textAttributes, + @NotNull RangeHighlighterEx rangeHighlighter) { + this.rangeHighlighter = rangeHighlighter; + myTextAttributes = textAttributes; + myTargetArea = target; + myLayer = layer; + myModel = model; + if (textAttributes != null) { + myErrorStripeColor = textAttributes.getErrorStripeColor(); + } + } + + public TextAttributes getTextAttributes() { + return myTextAttributes; + } + + public void setTextAttributes(TextAttributes textAttributes) { + TextAttributes old = myTextAttributes; + myTextAttributes = textAttributes; + if (!Comparing.equal(old, textAttributes)) { + fireChanged(); + } + } + + public int getLayer() { + return myLayer; + } + + public HighlighterTargetArea getTargetArea() { + return myTargetArea; + } + + public LineMarkerRenderer getLineMarkerRenderer() { + return myLineMarkerRenderer; + } + + public void setLineMarkerRenderer(LineMarkerRenderer renderer) { + myLineMarkerRenderer = renderer; + fireChanged(); + } + + public CustomHighlighterRenderer getCustomRenderer() { + return myCustomRenderer; + } + + public void setCustomRenderer(CustomHighlighterRenderer renderer) { + myCustomRenderer = renderer; + } + + public GutterIconRenderer getGutterIconRenderer() { + return myGutterIconRenderer; + } + + public void setGutterIconRenderer(GutterIconRenderer renderer) { + GutterIconRenderer old = myGutterIconRenderer; + myGutterIconRenderer = renderer; + if (!Comparing.equal(old, renderer)) { + fireChanged(); + } + } + + public Color getErrorStripeMarkColor() { + return myErrorStripeColor; + } + + public void setErrorStripeMarkColor(Color color) { + Color old = myErrorStripeColor; + myErrorStripeColor = color; + if (!Comparing.equal(old, color)) { + fireChanged(); + } + } + + public Object getErrorStripeTooltip() { + return myErrorStripeTooltip; + } + + public void setErrorStripeTooltip(Object tooltipObject) { + Object old = myErrorStripeTooltip; + myErrorStripeTooltip = tooltipObject; + if (!Comparing.equal(old, tooltipObject)) { + fireChanged(); + } + } + + public boolean isThinErrorStripeMark() { + return myErrorStripeMarkIsThin; + } + + public void setThinErrorStripeMark(boolean value) { + boolean old = myErrorStripeMarkIsThin; + myErrorStripeMarkIsThin = value; + if (old != value) { + fireChanged(); + } + } + + public Color getLineSeparatorColor() { + return myLineSeparatorColor; + } + + public void setLineSeparatorColor(Color color) { + Color old = myLineSeparatorColor; + myLineSeparatorColor = color; + if (!Comparing.equal(old, color)) { + fireChanged(); + } + } + + public SeparatorPlacement getLineSeparatorPlacement() { + return mySeparatorPlacement; + } + + public void setLineSeparatorPlacement(@Nullable SeparatorPlacement placement) { + SeparatorPlacement old = mySeparatorPlacement; + mySeparatorPlacement = placement; + if (!Comparing.equal(old, placement)) { + fireChanged(); + } + } + + public void setEditorFilter(@NotNull MarkupEditorFilter filter) { + myFilter = filter; + fireChanged(); + } + + @NotNull + public MarkupEditorFilter getEditorFilter() { + return myFilter; + } + + public boolean isAfterEndOfLine() { + return isAfterEndOfLine; + } + + public void setAfterEndOfLine(boolean afterEndOfLine) { + boolean old = isAfterEndOfLine; + isAfterEndOfLine = afterEndOfLine; + if (old != afterEndOfLine) { + fireChanged(); + } + } + + private boolean inBatchChange = false; + private boolean changed = false; + private void fireChanged() { + if (myModel instanceof MarkupModelImpl) { + if (inBatchChange) { + changed = true; + } + else { + ((MarkupModelImpl)myModel).fireAttributesChanged(rangeHighlighter); + } + } + } + + public int getAffectedAreaStartOffset() { + int startOffset = rangeHighlighter.getStartOffset(); + if (getTargetArea() == HighlighterTargetArea.EXACT_RANGE) return startOffset; + if (startOffset == myModel.getDocument().getTextLength()) return startOffset; + return myModel.getDocument().getLineStartOffset(myModel.getDocument().getLineNumber(startOffset)); + } + + public int getAffectedAreaEndOffset() { + int endOffset = rangeHighlighter.getEndOffset(); + if (getTargetArea() == HighlighterTargetArea.EXACT_RANGE) return endOffset; + int textLength = myModel.getDocument().getTextLength(); + if (endOffset == textLength) return endOffset; + return Math.min(textLength, myModel.getDocument().getLineEndOffset(myModel.getDocument().getLineNumber(endOffset)) + 1); + } + + public void registerMe() { + ((MarkupModelImpl)myModel).addRangeHighlighter(rangeHighlighter); + } + + public void unregisterMe() { + myModel.removeHighlighter(rangeHighlighter); + } + + public void changeAttributesInBatch(@NotNull Runnable change) { + inBatchChange = true; + try { + change.run(); + } + finally { + inBatchChange = false; + if (changed) { + changed= false; + ((MarkupModelImpl)myModel).fireAttributesChanged(rangeHighlighter); + } + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterImpl.java index d577c9de4134..ec7ed5fe8853 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterImpl.java @@ -15,13 +15,9 @@ */ package com.intellij.openapi.editor.impl; -import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.ex.DocumentEx; import com.intellij.openapi.editor.ex.RangeHighlighterEx; -import com.intellij.openapi.editor.ex.RangeMarkerEx; import com.intellij.openapi.editor.markup.*; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Key; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -31,240 +27,140 @@ import java.awt.*; * Implementation of the markup element for the editor and document. * @author max */ -public class RangeHighlighterImpl implements RangeHighlighterEx { - private final MarkupModel myModel; - private final int myLayer; - private final HighlighterTargetArea myTargetArea; - private TextAttributes myTextAttributes; - private LineMarkerRenderer myLineMarkerRenderer; - private Color myErrorStripeColor; - private Color myLineSeparatorColor; - private SeparatorPlacement mySeparatorPlacement; - private boolean isAfterEndOfLine; - private final RangeMarkerEx myRangeMarker; - private GutterIconRenderer myGutterIconRenderer; - private boolean myErrorStripeMarkIsThin; - private Object myErrorStripeTooltip; - private MarkupEditorFilter myFilter = MarkupEditorFilter.EMPTY; - private CustomHighlighterRenderer myCustomRenderer; - +class RangeHighlighterImpl extends RangeMarkerImpl implements RangeHighlighterEx { + private final RangeHighlighterData data; RangeHighlighterImpl(@NotNull MarkupModel model, int start, int end, int layer, @NotNull HighlighterTargetArea target, - TextAttributes textAttributes, - boolean persistent) { - myRangeMarker = persistent - ? new PersistentLineMarker((DocumentEx)model.getDocument(), start) - : (RangeMarkerEx)model.getDocument().createRangeMarker(start, end); - myTextAttributes = textAttributes; - myTargetArea = target; - myLayer = layer; - myModel = model; - if (textAttributes != null) { - myErrorStripeColor = textAttributes.getErrorStripeColor(); - } + TextAttributes textAttributes) { + super((DocumentEx)model.getDocument(), start, end); + data = new RangeHighlighterData(model, layer, target, textAttributes, this); } + @Override + protected void registerInDocument() { + // we store highlighters in MarkupModel + data.registerMe(); + } + + @Override + protected boolean unregisterInDocument() { + // we store highlighters in MarkupModel + data.unregisterMe(); + myNode = null; + return true; + } + + // delegates + public TextAttributes getTextAttributes() { - return myTextAttributes; + return data.getTextAttributes(); } public void setTextAttributes(TextAttributes textAttributes) { - TextAttributes old = myTextAttributes; - myTextAttributes = textAttributes; - if (!Comparing.equal(old, textAttributes)) { - fireChanged(); - } + data.setTextAttributes(textAttributes); + } + + public void changeAttributesInBatch(@NotNull Runnable change) { + data.changeAttributesInBatch(change); } public int getLayer() { - return myLayer; + return data.getLayer(); } public HighlighterTargetArea getTargetArea() { - return myTargetArea; - } - - public int getAffectedAreaStartOffset() { - int startOffset = getStartOffset(); - if (myTargetArea == HighlighterTargetArea.EXACT_RANGE) return startOffset; - if (startOffset == getDocument().getTextLength()) return startOffset; - return getDocument().getLineStartOffset(getDocument().getLineNumber(startOffset)); - } - - public int getAffectedAreaEndOffset() { - int endOffset = getEndOffset(); - if (myTargetArea == HighlighterTargetArea.EXACT_RANGE) return endOffset; - int textLength = getDocument().getTextLength(); - if (endOffset == textLength) return endOffset; - return Math.min(textLength, getDocument().getLineEndOffset(getDocument().getLineNumber(endOffset)) + 1); + return data.getTargetArea(); } public LineMarkerRenderer getLineMarkerRenderer() { - return myLineMarkerRenderer; + return data.getLineMarkerRenderer(); } public void setLineMarkerRenderer(LineMarkerRenderer renderer) { - myLineMarkerRenderer = renderer; - fireChanged(); + data.setLineMarkerRenderer(renderer); } public CustomHighlighterRenderer getCustomRenderer() { - return myCustomRenderer; + return data.getCustomRenderer(); } public void setCustomRenderer(CustomHighlighterRenderer renderer) { - myCustomRenderer = renderer; + data.setCustomRenderer(renderer); } public GutterIconRenderer getGutterIconRenderer() { - return myGutterIconRenderer; + return data.getGutterIconRenderer(); } public void setGutterIconRenderer(GutterIconRenderer renderer) { - GutterIconRenderer old = myGutterIconRenderer; - myGutterIconRenderer = renderer; - if (!Comparing.equal(old, renderer)) { - fireChanged(); - } + data.setGutterIconRenderer(renderer); } public Color getErrorStripeMarkColor() { - return myErrorStripeColor; + return data.getErrorStripeMarkColor(); } public void setErrorStripeMarkColor(Color color) { - Color old = myErrorStripeColor; - myErrorStripeColor = color; - if (!Comparing.equal(old, color)) { - fireChanged(); - } + data.setErrorStripeMarkColor(color); } public Object getErrorStripeTooltip() { - return myErrorStripeTooltip; + return data.getErrorStripeTooltip(); } public void setErrorStripeTooltip(Object tooltipObject) { - Object old = myErrorStripeTooltip; - myErrorStripeTooltip = tooltipObject; - if (!Comparing.equal(old, tooltipObject)) { - fireChanged(); - } + data.setErrorStripeTooltip(tooltipObject); } public boolean isThinErrorStripeMark() { - return myErrorStripeMarkIsThin; + return data.isThinErrorStripeMark(); } public void setThinErrorStripeMark(boolean value) { - boolean old = myErrorStripeMarkIsThin; - myErrorStripeMarkIsThin = value; - if (old != value) { - fireChanged(); - } + data.setThinErrorStripeMark(value); } public Color getLineSeparatorColor() { - return myLineSeparatorColor; + return data.getLineSeparatorColor(); } public void setLineSeparatorColor(Color color) { - Color old = myLineSeparatorColor; - myLineSeparatorColor = color; - if (!Comparing.equal(old, color)) { - fireChanged(); - } + data.setLineSeparatorColor(color); } public SeparatorPlacement getLineSeparatorPlacement() { - return mySeparatorPlacement; + return data.getLineSeparatorPlacement(); } public void setLineSeparatorPlacement(@Nullable SeparatorPlacement placement) { - SeparatorPlacement old = mySeparatorPlacement; - mySeparatorPlacement = placement; - if (!Comparing.equal(old, placement)) { - fireChanged(); - } + data.setLineSeparatorPlacement(placement); } public void setEditorFilter(@NotNull MarkupEditorFilter filter) { - myFilter = filter; - fireChanged(); + data.setEditorFilter(filter); } @NotNull public MarkupEditorFilter getEditorFilter() { - return myFilter; + return data.getEditorFilter(); } public boolean isAfterEndOfLine() { - return isAfterEndOfLine; + return data.isAfterEndOfLine(); } public void setAfterEndOfLine(boolean afterEndOfLine) { - boolean old = isAfterEndOfLine; - isAfterEndOfLine = afterEndOfLine; - if (old != afterEndOfLine) { - fireChanged(); - } + data.setAfterEndOfLine(afterEndOfLine); } - private void fireChanged() { - if (myModel instanceof MarkupModelImpl) { - ((MarkupModelImpl)myModel).fireSegmentHighlighterChanged(this); - } + public int getAffectedAreaStartOffset() { + return data.getAffectedAreaStartOffset(); } - public int getStartOffset() { - return myRangeMarker.getStartOffset(); - } - - public long getId() { - return myRangeMarker.getId(); - } - - public int getEndOffset() { - return myRangeMarker.getEndOffset(); - } - - public boolean isValid() { - return myRangeMarker.isValid(); - } - - @NotNull - public Document getDocument() { - return myRangeMarker.getDocument(); - } - - public void setGreedyToLeft(boolean greedy) { - myRangeMarker.setGreedyToLeft(greedy); - } - - public void setGreedyToRight(boolean greedy) { - myRangeMarker.setGreedyToRight(greedy); - } - - public boolean isGreedyToRight() { - return myRangeMarker.isGreedyToRight(); - } - - public boolean isGreedyToLeft() { - return myRangeMarker.isGreedyToLeft(); - } - - public T getUserData(@NotNull Key key) { - return myRangeMarker.getUserData(key); - } - - public void putUserData(@NotNull Key key, T value) { - myRangeMarker.putUserData(key, value); - } - - public String toString() { - return myRangeMarker.toString(); + public int getAffectedAreaEndOffset() { + return data.getAffectedAreaEndOffset(); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterTree.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterTree.java new file mode 100644 index 000000000000..61b1cc70483a --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeHighlighterTree.java @@ -0,0 +1,51 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.editor.impl; + +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.ex.RangeHighlighterEx; + +/** + * User: cdr + */ +public class RangeHighlighterTree extends RangeMarkerTree { + public RangeHighlighterTree(Document document) { + super(document, COMPARATOR); + } + + private static final EqualStartIntervalComparator COMPARATOR = new EqualStartIntervalComparator() { + public int compare(RangeHighlighterEx o1, RangeHighlighterEx o2) { + if (o1.getLayer() != o2.getLayer()) { + return o2.getLayer() - o1.getLayer(); + } + boolean greedyL1 = o1.isGreedyToLeft(); + boolean greedyL2 = o2.isGreedyToLeft(); + if (greedyL1 != greedyL2) return greedyL1 ? -1 : 1; + + int d = o1.intervalEnd() - o1.intervalStart() - (o2.intervalEnd() - o2.intervalStart()); + if (d != 0) return d; + + boolean greedyR1 = o1.isGreedyToRight(); + boolean greedyR2 = o2.isGreedyToRight(); + if (greedyR1 != greedyR2) return greedyR1 ? -1 : 1; + + // for now we tolerate equal range range markers (till lazy creation impl) + d = (int)(o1.getId() - o2.getId()); + return d; + } + }; + +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeMarkerImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeMarkerImpl.java index 5985768100c2..faa58b44aa82 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeMarkerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeMarkerImpl.java @@ -36,8 +36,10 @@ public class RangeMarkerImpl extends UserDataHolderBase implements RangeMarkerEx private boolean isExpandToRight = false; private static final AtomicLong counter = new AtomicLong(); + //private static long counter; private final long myId; private volatile int modCount; + IntervalTreeImpl.MyNode myNode; protected RangeMarkerImpl(@NotNull DocumentEx document, int start, int end) { if (start < 0) { @@ -54,11 +56,18 @@ public class RangeMarkerImpl extends UserDataHolderBase implements RangeMarkerEx myStart = start; myEnd = end; myId = counter.getAndIncrement(); - registerInDocument(); + //myId = counter++; } protected void registerInDocument() { + myNode = null; myDocument.addRangeMarker(this); + assert myNode != null; + } + protected boolean unregisterInDocument() { + boolean b = myDocument.removeRangeMarker(this); + myNode = null; + return b; } public long getId() { @@ -66,11 +75,11 @@ public class RangeMarkerImpl extends UserDataHolderBase implements RangeMarkerEx } public int getStartOffset() { - return myStart; + return myStart + IntervalTreeImpl.computeDeltaUpToRoot(myNode); } public int getEndOffset() { - return myEnd; + return myEnd + IntervalTreeImpl.computeDeltaUpToRoot(myNode); } public boolean isValid() { @@ -87,11 +96,19 @@ public class RangeMarkerImpl extends UserDataHolderBase implements RangeMarkerEx } public void setGreedyToLeft(boolean greedy) { + if (!isValid()) return; + boolean b = unregisterInDocument(); + assert b; isExpandToLeft = greedy; + registerInDocument(); } public void setGreedyToRight(boolean greedy) { + if (!isValid()) return; + boolean b = unregisterInDocument(); + assert b; isExpandToRight = greedy; + registerInDocument(); } public boolean isGreedyToLeft() { @@ -203,4 +220,26 @@ public class RangeMarkerImpl extends UserDataHolderBase implements RangeMarkerEx return "RangeMarker" + (isGreedyToLeft() ? "[" : "(") + (isValid ? "valid" : "invalid") + "," + getStartOffset() + "," + getEndOffset() + ( isGreedyToRight() ? "]" : ")"); } + + public int intervalStart() { + return myStart; + } + + public int intervalEnd() { + return myEnd; + } + + public int setIntervalStart(int start) { + return myStart = start; + } + + public int setIntervalEnd(int end) { + return myEnd = end; + } + + public boolean setValid(boolean value) { + isValid = value; + return value; + } + } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeMarkerTree.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeMarkerTree.java new file mode 100644 index 000000000000..8a64ac045816 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RangeMarkerTree.java @@ -0,0 +1,222 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.editor.impl; + +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.editor.ex.MarkupModelEx; +import com.intellij.openapi.editor.ex.PrioritizedDocumentListener; +import com.intellij.openapi.editor.ex.RangeMarkerEx; +import com.intellij.openapi.util.Segment; +import com.intellij.openapi.util.Trinity; +import com.intellij.util.Processor; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +/** + * User: cdr + */ +public class RangeMarkerTree extends IntervalTreeImpl { + private static final EqualStartIntervalComparator RANGEMARKER_COMPARATOR = new EqualStartIntervalComparator() { + public int compare(RangeMarkerEx o1, RangeMarkerEx o2) { + boolean greedyL1 = o1.isGreedyToLeft(); + boolean greedyL2 = o2.isGreedyToLeft(); + if (greedyL1 != greedyL2) return greedyL1 ? -1 : 1; + + int d = o1.intervalEnd() - o1.intervalStart() - (o2.intervalEnd() - o2.intervalStart()); + if (d != 0) return d; + + boolean greedyR1 = o1.isGreedyToRight(); + boolean greedyR2 = o2.isGreedyToRight(); + if (greedyR1 != greedyR2) return greedyR1 ? -1 : 1; + + // for now we tolerate equal range range markers (till lazy creation impl) + d = (int)(o1.getId() - o2.getId()); + return d; + } + }; + + protected RangeMarkerTree(Document document, EqualStartIntervalComparator comparator) { + super(comparator); + + document.addDocumentListener(new PrioritizedDocumentListener() { + public int getPriority() { + return 0; // Need to make sure we invalidate all the stuff before someone (like LineStatusTracker) starts to modify highlights. + } + + public void beforeDocumentChange(DocumentEvent event) {} + + public void documentChanged(DocumentEvent e) { + updateMarkersOnChange(e); + } + }); + } + + public RangeMarkerTree(Document document) { + this(document, (EqualStartIntervalComparator)RANGEMARKER_COMPARATOR); + } + + @Override + protected Trinity checkMax(MyNode root, int deltaUpToRootExclusive, boolean assertInvalid) { + if (root != null) { + RangeMarkerImpl r = (RangeMarkerImpl)root.key; + assert r.myNode == null || r.myNode == root; + } + return super.checkMax(root, deltaUpToRootExclusive, assertInvalid); + } + + private void updateMarkersOnChange(DocumentEvent e) { + try { + l.writeLock().lock(); + checkMax(true); + + modCount++; + List affected = new ArrayList(); + updateMarkersOnChange(getRoot(), e, affected); + checkMax(false); + + for (MyNode node : affected) { + // assumption: interval.getEndOffset() will never be accessed during remove() + RangeMarkerEx marker = node.key; + int startOffset = marker.getStartOffset(); + int endOffset = marker.getEndOffset(); + deleteNode(node); + node.delta = 0; + node.setParent(null); + node.setLeft(null); + node.setRight(null); + assert marker.intervalStart() == startOffset; + assert marker.intervalEnd() == endOffset; + //marker.setIntervalStart(startOffset); + //marker.setIntervalEnd(endOffset); //might have been changed by delete + } + checkMax(true); + for (MyNode node : affected) { + RangeMarkerImpl marker = (RangeMarkerImpl)node.key; + marker.setValid(true); + marker.myNode = null; + marker.documentChanged(e); + if (marker.isValid()) { + marker.registerInDocument(); + } + } + checkMax(true); + } + finally { + l.writeLock().unlock(); + } + } + + private void updateMarkersOnChange(MyNode root, + DocumentEvent e, + List affected) { + if (root == null) return; + pushDelta(root); + + int maxEnd = root.maxEnd; + RangeMarkerEx interval = root.key; + assert interval.isValid(); + + int offset = e.getOffset(); + int affectedEndOffset = offset + e.getOldLength(); + int lengthDelta = e.getNewLength() - e.getOldLength(); + if (offset > maxEnd) { + + } + else if (affectedEndOffset < interval.intervalStart()) { + root.delta += lengthDelta; + if (root.getLeft() != null) { + root.getLeft().delta -= lengthDelta; + } + pushDelta(root); + updateMarkersOnChange(root.getLeft(), e, affected); + correctMax(root, 0); + } + else { + if (offset <= interval.getEndOffset()) { + // unlucky enough so that change affects the interval + affected.add(root); + root.key.setValid(false); //make invisible + } + + updateMarkersOnChange(root.getLeft(), e, affected); + updateMarkersOnChange(root.getRight(), e, affected); + correctMax(root,0); + } + } + + + public boolean sweep(final int start, final int end, @NotNull final MarkupModelEx.SweepProcessor sweepProcessor) { + return sweep(new Generator() { + @Override + public boolean generate(Processor processor) { + return processOverlappingWith(start, end, processor); + } + }, sweepProcessor); + + } + + public interface Generator { + boolean generate(Processor processor); + } + + public static boolean sweep(@NotNull Generator generator, @NotNull final MarkupModelEx.SweepProcessor sweepProcessor) { + final Queue ends = new PriorityQueue(5, new Comparator() { + public int compare(T o1, T o2) { + return o1.getEndOffset() - o2.getEndOffset(); + } + }); + final List starts = new ArrayList(); + if (!generator.generate(new Processor() { + public boolean process(T marker) { + // decide whether previous marker ends here or new marker begins + int start = marker.getStartOffset(); + while (true) { + assert ends.size() == starts.size(); + T previous = ends.peek(); + if (previous != null) { + int prevEnd = previous.getEndOffset(); + if (prevEnd <= start) { + if (!sweepProcessor.process(prevEnd, previous, false, ends)) return false; + ends.remove(); + boolean removed = starts.remove(previous); + assert removed; + continue; + } + } + break; + } + if (!sweepProcessor.process(start, marker, true, ends)) return false; + starts.add(marker); + ends.offer(marker); + + return true; + } + })) return false; + + while (!ends.isEmpty()) { + assert ends.size() == starts.size(); + T previous = ends.remove(); + int prevEnd = previous.getEndOffset(); + if (!sweepProcessor.process(prevEnd, previous, false, ends)) return false; + boolean removed = starts.remove(previous); + assert removed; + } + + return true; + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/RedBlackTree.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RedBlackTree.java new file mode 100644 index 000000000000..ff68e8277b99 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/RedBlackTree.java @@ -0,0 +1,560 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.editor.impl; + +import org.jetbrains.annotations.NotNull; + +import java.util.Comparator; + + +/** + * User: cdr + */ +public class RedBlackTree { + public static final boolean VERIFY = false; + private static final int INDENT_STEP = 4; + protected int size; + protected int modCount; + public Node root; + + private final Comparator comparator; + + public RedBlackTree(Comparator comparator) { + this.comparator = comparator; + root = null; + verifyProperties(); + } + + protected Node lookupNode(K key, Node root) { + while (root != null) { + int compResult = comparator.compare(key, root.key); + if (compResult < 0) { + root = root.getLeft(); + } + else if (compResult > 0) { + root = root.getRight(); + } + else { + return root; + } + } + return root; + } + + protected void rotateLeft(Node n) { + Node r = n.getRight(); + replaceNode(n, r); + n.setRight(r.getLeft()); + if (r.getLeft() != null) { + r.getLeft().setParent(n); + } + r.setLeft(n); + n.setParent(r); + } + + protected void rotateRight(Node n) { + Node l = n.getLeft(); + replaceNode(n, l); + n.setLeft(l.getRight()); + if (l.getRight() != null) { + l.getRight().setParent(n); + } + l.setRight(n); + n.setParent(l); + } + + protected void replaceNode(@NotNull Node oldn, Node newn) { + Node parent = oldn.getParent(); + if (parent == null) { + root = newn; + } + else { + if (oldn == parent.getLeft()) { + parent.setLeft(newn); + } + else { + parent.setRight(newn); + } + } + if (newn != null) { + newn.setParent(parent); + } + //oldn.parent = null; + //oldn.left = null; + //oldn.right = null; + } + + protected Node createNewNode(K key) { + return new Node(key); + } + + public Node insert(Node newNode) { + modCount++; + newNode.color = Color.RED; + K key = newNode.key; + + if (root == null) { + root = newNode; + } + else { + Node n = root; + loop: + while (true) { + int compResult = comparator.compare(key, n.key); + if (compResult < 0) { + if (n.getLeft() == null) { + n.setLeft(newNode); + break loop; + } + n = n.getLeft(); + } + else if (compResult > 0) { + if (n.getRight() == null) { + n.setRight(newNode); + break loop; + } + n = n.getRight(); + } + else { + assert false : "already inserted"; + return root; + } + } + newNode.setParent(n); + } + size++; + insertCase1(newNode); + verifyProperties(); + return newNode; + } + + protected void insertCase1(Node n) { + if (n.getParent() == null) { + n.color = Color.BLACK; + } + else { + insertCase2(n); + } + } + + private void insertCase2(Node n) { + if (nodeColor(n.getParent()) != Color.BLACK) { + insertCase3(n); + } + // Tree is still valid + } + + void insertCase3(Node n) { + if (nodeColor(n.uncle()) == Color.RED) { + n.getParent().color = Color.BLACK; + n.uncle().color = Color.BLACK; + n.grandparent().color = Color.RED; + insertCase1(n.grandparent()); + } + else { + insertCase4(n); + } + } + + void insertCase4(Node n) { + if (n == n.getParent().getRight() && n.getParent() == n.grandparent().getLeft()) { + rotateLeft(n.getParent()); + n = n.getLeft(); + } + else if (n == n.getParent().getLeft() && n.getParent() == n.grandparent().getRight()) { + rotateRight(n.getParent()); + n = n.getRight(); + } + insertCase5(n); + } + + void insertCase5(Node n) { + n.getParent().color = Color.BLACK; + n.grandparent().color = Color.RED; + if (n == n.getParent().getLeft() && n.getParent() == n.grandparent().getLeft()) { + rotateRight(n.grandparent()); + } + else { + assert n == n.getParent().getRight(); + assert n.getParent() == n.grandparent().getRight(); + rotateLeft(n.grandparent()); + } + } + + private static void assertParentChild(Node node1) { + assert node1 == null || node1.getParent() == null || node1.getParent().getLeft() == node1 || node1.getParent().getRight() == node1; + } + + //returns parent of deleted node + public Node delete(K key) { + modCount++; + Node n = lookupNode(key, root); + deleteNode(n); + return n; + } + + protected void deleteNode(Node n) { + if (n == null) return; // Key not found, do nothing + if (n.getLeft() != null && n.getRight() != null) { + // Copy key/value from predecessor and then delete it instead + Node pred = maximumNode(n.getLeft()); + + //Color c = pred.color; + //swap(n, pred); + //assert pred.color == c; + //pred.color = n.color; + //n.color = c; + + n = swapWithMaxPred(n, pred); + } + + assert n.getLeft() == null || n.getRight() == null; + Node child = n.getRight() == null ? n.getLeft() : n.getRight(); + if (nodeColor(n) == Color.BLACK) { + n.color = nodeColor(child); + deleteCase1(n); + } + replaceNode(n, child); + + if (nodeColor(root) == Color.RED) { + root.color = Color.BLACK; + } + size--; + verifyProperties(); + } + + protected Node swapWithMaxPred(Node nowAscendant, Node nowDescendant) { + nowAscendant.key = nowDescendant.key; + //nowAscendant.value = nowDescendant.value; + return nowDescendant; + } + + protected Node maximumNode(Node n) { + assert n != null; + while (n.getRight() != null) { + n = n.getRight(); + } + return n; + } + + private void deleteCase1(Node n) { + if (n.getParent() != null) { + deleteCase2(n); + } + } + + private void deleteCase2(Node n) { + if (nodeColor(n.sibling()) == Color.RED) { + n.getParent().color = Color.RED; + n.sibling().color = Color.BLACK; + if (n == n.getParent().getLeft()) { + rotateLeft(n.getParent()); + } + else { + rotateRight(n.getParent()); + } + } + deleteCase3(n); + } + + private void deleteCase3(Node n) { + if (nodeColor(n.getParent()) == Color.BLACK && + nodeColor(n.sibling()) == Color.BLACK && + nodeColor(n.sibling().getLeft()) == Color.BLACK && + nodeColor(n.sibling().getRight()) == Color.BLACK) { + n.sibling().color = Color.RED; + deleteCase1(n.getParent()); + } + else { + deleteCase4(n); + } + } + + private void deleteCase4(Node n) { + if (nodeColor(n.getParent()) == Color.RED && + nodeColor(n.sibling()) == Color.BLACK && + nodeColor(n.sibling().getLeft()) == Color.BLACK && + nodeColor(n.sibling().getRight()) == Color.BLACK) { + n.sibling().color = Color.RED; + n.getParent().color = Color.BLACK; + } + else { + deleteCase5(n); + } + } + + private void deleteCase5(Node n) { + if (n == n.getParent().getLeft() && + nodeColor(n.sibling()) == Color.BLACK && + nodeColor(n.sibling().getLeft()) == Color.RED && + nodeColor(n.sibling().getRight()) == Color.BLACK) { + n.sibling().color = Color.RED; + n.sibling().getLeft().color = Color.BLACK; + rotateRight(n.sibling()); + } + else if (n == n.getParent().getRight() && + nodeColor(n.sibling()) == Color.BLACK && + nodeColor(n.sibling().getRight()) == Color.RED && + nodeColor(n.sibling().getLeft()) == Color.BLACK) { + n.sibling().color = Color.RED; + n.sibling().getRight().color = Color.BLACK; + rotateLeft(n.sibling()); + } + deleteCase6(n); + } + + private void deleteCase6(Node n) { + n.sibling().color = nodeColor(n.getParent()); + n.getParent().color = Color.BLACK; + if (n == n.getParent().getLeft()) { + assert nodeColor(n.sibling().getRight()) == Color.RED; + n.sibling().getRight().color = Color.BLACK; + rotateLeft(n.getParent()); + } + else { + assert nodeColor(n.sibling().getLeft()) == Color.RED; + n.sibling().getLeft().color = Color.BLACK; + rotateRight(n.getParent()); + } + } + + public void print() { + printHelper(root, 0); + } + + private static void printHelper(Node n, int indent) { + if (n == null) { + System.err.print(""); + return; + } + if (n.getRight() != null) { + printHelper(n.getRight(), indent + INDENT_STEP); + } + for (int i = 0; i < indent; i++) { + System.err.print(" "); + } + if (n.color == Color.BLACK) { + System.err.println(n.key); + } + else { + System.err.println("<" + n.key + ">"); + } + if (n.getLeft() != null) { + printHelper(n.getLeft(), indent + INDENT_STEP); + } + } + + /* + private static void test (int... keys) { + testDel(keys,keys); + } + private static void testDel (int[] adds, int[] dels) { + RedBlackTree t = new RedBlackTree(); + for (int i = 0; i < adds.length; i++) { + int key = adds[i]; + Node node = t.createNewNode(key); + node.value = i; + t.insert(node); + } + for (int key : dels) { + t.delete(key); + } + } + + public static void main(String[] args) { + test(0, 2, 1, 2); + test(4, 2, 0, 9, 6, 1, 3, 7); + testDel(new int[] {7, 2, 5, 0, 4}, new int[]{2,7}); + + + + + //t.insert(0, 0); + //t.insert(2, 1); //assert t.lookup(1).equals(1) : t.lookup(1); + //t.insert(1, 0); //assert t.lookup(0).equals(0) : t.lookup(0); + //t.insert(2, 1); //assert t.lookup(1).equals(1) : t.lookup(1); + + Random gen = new Random(); + + while (true) { + RedBlackTree t = new RedBlackTree(); + List adds = new ArrayList(); + List dels = new ArrayList(); + assert t.size() == 0 : t.size(); + try { + for (int i = 0; i < 500; i++) { + int x = gen.nextInt(500); + if (adds.contains(x)) continue; + adds.add(x); + Node node = t.createNewNode(x); + node.value = x; + t.insert(node); + Integer lookup = t.lookupNode(x).value; + assert lookup.equals(i) : lookup; + } + List candidates = new ArrayList(adds); + for (int i = 0; i < candidates.size(); i++) { + int x = gen.nextInt(candidates.size()); + Integer r = candidates.get(x); + candidates.remove((Object)r); + dels.add(r); + t.delete(r); + } + } + catch (AssertionError e) { + System.err.println("adds = " + adds); + System.err.println("dels = " + dels); + throw e; + } + } + } + */ + + public static class Node { + K key; + private Node left; + private Node right; + private Node parent; + protected Color color; + + public Node(K key) { + this.key = key; + color = Color.RED; + setParent(null); + } + + public Node grandparent() { + assert getParent() != null; // Not the root node + assert getParent().getParent() != null; // Not child of root + return getParent().getParent(); + } + + public Node sibling() { + Node parent = getParent(); + assert parent != null; // Root node has no sibling + return this == parent.getLeft() ? parent.getRight() : parent.getLeft(); + } + + public Node uncle() { + assert getParent() != null; // Root node has no uncle + assert getParent().getParent() != null; // Children of root have no uncle + return getParent().sibling(); + } + + public Node getLeft() { + return left; + } + + public void setLeft(Node left) { + this.left = left; + } + + public Node getRight() { + return right; + } + + public void setRight(Node right) { + this.right = right; + } + + public Node getParent() { + return parent; + } + + public void setParent(Node parent) { + this.parent = parent; + } + } + + protected static enum Color { + RED, BLACK + } + + public int size() { + return size; + } + + public void verifyProperties() { + //if (true) return; + if (VERIFY) { + verifyProperty1(root); + verifyProperty2(root); + // Property 3 is implicit + verifyProperty4(root); + verifyProperty5(root); + } + } + + private static void verifyProperty1(Node n) { + assert nodeColor(n) == Color.RED || nodeColor(n) == Color.BLACK; + if (n == null) return; + assert n.getParent() != n; + assert n.getLeft() != n; + assert n.getRight() != n; + assertParentChild(n); + + verifyProperty1(n.getLeft()); + verifyProperty1(n.getRight()); + } + + private static void verifyProperty2(Node root) { + assert nodeColor(root) == Color.BLACK; + } + + private static Color nodeColor(Node n) { + return n == null ? Color.BLACK : n.color; + } + + private static void verifyProperty4(Node n) { + if (nodeColor(n) == Color.RED) { + assert nodeColor(n.getLeft()) == Color.BLACK; + assert nodeColor(n.getRight()) == Color.BLACK; + assert nodeColor(n.getParent()) == Color.BLACK; + } + if (n == null) return; + verifyProperty4(n.getLeft()); + verifyProperty4(n.getRight()); + } + + private static void verifyProperty5(Node root) { + verifyProperty5Helper(root, 0, -1); + } + + private static int verifyProperty5Helper(Node n, int blackCount, int pathBlackCount) { + if (nodeColor(n) == Color.BLACK) { + blackCount++; + } + if (n == null) { + if (pathBlackCount == -1) { + pathBlackCount = blackCount; + } + else { + assert blackCount == pathBlackCount; + } + return pathBlackCount; + } + pathBlackCount = verifyProperty5Helper(n.getLeft(), blackCount, pathBlackCount); + pathBlackCount = verifyProperty5Helper(n.getRight(), blackCount, pathBlackCount); + + return pathBlackCount; + } + + public void clear() { + modCount++; + root = null; + size = 0; + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/TextRangeInterval.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/TextRangeInterval.java new file mode 100644 index 000000000000..1999a600d9c7 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/TextRangeInterval.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.editor.impl; + +import com.intellij.openapi.util.ProperTextRange; + +/** + * User: cdr + */ +public class TextRangeInterval extends ProperTextRange implements Interval{ + public TextRangeInterval(int startOffset, int endOffset) { + super(startOffset, endOffset); + } + public int intervalStart() { + return getStartOffset(); + } + + public int intervalEnd() { + return getEndOffset(); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/event/MarkupModelListener.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/event/MarkupModelListener.java index 1cd0b01b538d..b91757caa2c8 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/event/MarkupModelListener.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/event/MarkupModelListener.java @@ -15,9 +15,15 @@ */ package com.intellij.openapi.editor.impl.event; +import com.intellij.openapi.editor.ex.RangeHighlighterEx; +import org.jetbrains.annotations.NotNull; + import java.util.EventListener; public interface MarkupModelListener extends EventListener { MarkupModelListener[] EMPTY_ARRAY = new MarkupModelListener[0]; - void rangeHighlighterChanged(MarkupModelEvent event); + + void afterAdded(@NotNull RangeHighlighterEx highlighter); + void beforeRemoved(@NotNull RangeHighlighterEx highlighter); + void attributesChanged(@NotNull RangeHighlighterEx highlighter); } diff --git a/platform/platform-resources/src/componentSets/Lang.xml b/platform/platform-resources/src/componentSets/Lang.xml index 815293345032..cfcc673b369f 100644 --- a/platform/platform-resources/src/componentSets/Lang.xml +++ b/platform/platform-resources/src/componentSets/Lang.xml @@ -117,10 +117,6 @@ com.intellij.codeInsight.daemon.impl.GeneralHighlightingPassFactory - - com.intellij.codeInsight.daemon.impl.VisibleGeneralHighlightingPassFactory - - com.intellij.codeInsight.daemon.impl.LineMarkersPassFactory diff --git a/platform/testFramework/src/com/intellij/mock/MockDocument.java b/platform/testFramework/src/com/intellij/mock/MockDocument.java index a5144be85f77..502ba49e1cb7 100644 --- a/platform/testFramework/src/com/intellij/mock/MockDocument.java +++ b/platform/testFramework/src/com/intellij/mock/MockDocument.java @@ -28,6 +28,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.TextRange; import com.intellij.util.LocalTimeCounter; +import com.intellij.util.Processor; import com.intellij.util.containers.HashMap; import org.jetbrains.annotations.NotNull; @@ -52,9 +53,10 @@ public class MockDocument implements DocumentEx { return myText.toString(); } + @NotNull @Override - public String getText(TextRange range) { - return myText.substring(range.getStartOffset(), range.getEndOffset()); + public String getText(@NotNull TextRange range) { + return range.substring(myText.toString()); } public void replaceText(@NotNull CharSequence chars, long newModificationStamp) { @@ -235,9 +237,12 @@ public class MockDocument implements DocumentEx { public void clearLineModificationFlags() { } - public void removeRangeMarker(@NotNull RangeMarkerEx rangeMarker) {} + public boolean removeRangeMarker(@NotNull RangeMarkerEx rangeMarker) { + return false; + } - public void addRangeMarker(@NotNull RangeMarkerEx rangeMarker) {} + public void addRangeMarker(@NotNull RangeMarkerEx rangeMarker) { + } public boolean isInBulkUpdate() { return false; @@ -261,4 +266,16 @@ public class MockDocument implements DocumentEx { public List getGuardedBlocks() { return Collections.emptyList(); } + + public boolean processRangeMarkers(@NotNull Processor processor) { + return false; + } + + public boolean processRangeMarkersOverlappingWith(int start, int end, @NotNull Processor processor) { + return false; + } + + public boolean processRangeMarkersOverlappingWith(int offset, @NotNull Processor processor) { + return false; + } } diff --git a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java index 69931176e168..654e7f1fc1d9 100644 --- a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java +++ b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java @@ -18,7 +18,7 @@ package com.intellij.testFramework.fixtures.impl; import com.intellij.analysis.AnalysisScope; import com.intellij.codeHighlighting.HighlightDisplayLevel; -import com.intellij.codeHighlighting.TextEditorHighlightingPass; +import com.intellij.codeInsight.CodeInsightActionHandler; import com.intellij.codeInsight.TargetElementUtilBase; import com.intellij.codeInsight.completion.CodeCompletionHandlerBase; import com.intellij.codeInsight.completion.CompletionContext; @@ -67,12 +67,11 @@ import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.extensions.ExtensionsArea; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.fileEditor.TextEditor; import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.module.Module; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.DumbServiceImpl; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.*; @@ -108,6 +107,7 @@ import com.intellij.util.Function; import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.indexing.FileBasedIndex; +import com.intellij.util.ui.UIUtil; import gnu.trove.THashMap; import junit.framework.Assert; import org.jetbrains.annotations.NonNls; @@ -143,6 +143,7 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig @NonNls private static final String XXX = "XXX"; private PsiElement myFileContext; private final FileTreeAccessFilter myJavaFilesFilter = new FileTreeAccessFilter(); + private boolean myAllowDirt; public CodeInsightTestFixtureImpl(IdeaProjectTestFixture projectFixture, TempDirTestFixture tempDirTestFixture) { myProjectFixture = projectFixture; @@ -618,7 +619,7 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig @Override public Presentation testAction(AnAction action) { - DataContext context = DataManager.getInstance().getDataContext(); + DataContext context = DataManager.getInstance().getDataContext(getEditor().getComponent()); TestActionEvent e = new TestActionEvent(context, action); action.beforeActionPerformedUpdate(e); if (e.getPresentation().isVisible() && e.getPresentation().isVisible()) { @@ -954,6 +955,7 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig public void tearDown() throws Exception { if (SwingUtilities.isEventDispatchThread()) { LookupManager.getInstance(getProject()).hideActiveLookup(); + UIUtil.dispatchAllInvocationEvents(); } else { ApplicationManager.getApplication().invokeAndWait(new Runnable() { @@ -961,6 +963,7 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig LookupManager.getInstance(getProject()).hideActiveLookup(); } }, ModalityState.NON_MODAL); + UIUtil.pump(); } FileEditorManager editorManager = FileEditorManager.getInstance(getProject()); @@ -1202,41 +1205,18 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig return ApplicationManager.getApplication().runReadAction(new Computable>() { public List compute() { - return instantiateAndRun(getFile(), getEditor(), ArrayUtil.EMPTY_INT_ARRAY, false); + return instantiateAndRun(getFile(), getEditor(), ArrayUtil.EMPTY_INT_ARRAY, myAllowDirt); } }); } @NotNull - public static List instantiateAndRun(PsiFile file, Editor editor, int[] toIgnore, boolean allowDirt) { + public static List instantiateAndRun(@NotNull PsiFile file, @NotNull Editor editor, @NotNull int[] toIgnore, boolean allowDirt) { Project project = file.getProject(); ensureIndexesUpToDate(project); - DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(project); - codeAnalyzer.setUpdateByTimerEnabled(false); - FileStatusMap fileStatusMap = codeAnalyzer.getFileStatusMap(); - for (int ignoreId : toIgnore) { - fileStatusMap.markFileUpToDate(editor.getDocument(), file, ignoreId); - } - fileStatusMap.allowDirt(allowDirt); - try { - TextEditorBackgroundHighlighter highlighter = (TextEditorBackgroundHighlighter)TextEditorProvider.getInstance().getTextEditor(editor).getBackgroundHighlighter(); - final List passes = highlighter.getPasses(toIgnore); - final ProgressIndicator progress = new DaemonProgressIndicator(); - ProgressManager.getInstance().runProcess(new Runnable() { - public void run() { - for (TextEditorHighlightingPass pass : passes) { - pass.collectInformation(progress); - pass.applyInformationToEditor(); - } - } - }, progress); - List infos = DaemonCodeAnalyzerImpl.getHighlights(editor.getDocument(), project); - return infos == null ? Collections.emptyList() : new ArrayList(infos); - } - finally { - fileStatusMap.allowDirt(true); - codeAnalyzer.clearPasses(); - } + DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzerImpl.getInstance(project); + TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor); + return codeAnalyzer.runPasses(file, editor.getDocument(), textEditor, new DaemonProgressIndicator(), toIgnore, allowDirt, true); } public static void ensureIndexesUpToDate(Project project) { @@ -1445,4 +1425,7 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig return actualText; } + public void canChangeDocumentDuringHighlighting(boolean canI) { + myAllowDirt = canI; + } } diff --git a/platform/util/src/com/intellij/openapi/util/Segment.java b/platform/util/src/com/intellij/openapi/util/Segment.java new file mode 100644 index 000000000000..d159edc154d0 --- /dev/null +++ b/platform/util/src/com/intellij/openapi/util/Segment.java @@ -0,0 +1,24 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.util; + +/** + * User: cdr + */ +public interface Segment { + int getStartOffset(); + int getEndOffset(); +} diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/local/GroovyUnusedImportPass.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/local/GroovyUnusedImportPass.java index b2f390a8d87c..80c1ba61f226 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/local/GroovyUnusedImportPass.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/local/GroovyUnusedImportPass.java @@ -34,6 +34,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; +import com.intellij.util.Processor; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.groovy.codeInspection.GroovyImportsTracker; import org.jetbrains.plugins.groovy.codeInspection.GroovyInspectionBundle; @@ -138,8 +139,6 @@ public class GroovyUnusedImportPass extends TextEditorHighlightingPass { } private boolean containsErrorsPreventingOptimize() { - List errors = DaemonCodeAnalyzerImpl.getHighlights(myDocument, HighlightSeverity.ERROR, myProject); - // ignore unresolved imports errors final TextRange ignoreRange; final GrImportStatement[] imports = myFile.getImportStatements(); @@ -151,13 +150,15 @@ public class GroovyUnusedImportPass extends TextEditorHighlightingPass { ignoreRange = new TextRange(0, 0); } - for (HighlightInfo error : errors) { - if (!error.type.equals(HighlightInfoType.WRONG_REF)) return true; - if (!ignoreRange.contains(error.getActualStartOffset()) || !ignoreRange.contains(error.getActualEndOffset())) { - return true; + boolean hasErrorsExceptUnresolvedImports = !DaemonCodeAnalyzerImpl.processHighlights(myDocument, myProject, HighlightSeverity.ERROR, 0, myDocument.getTextLength(), new Processor() { + public boolean process(HighlightInfo error) { + int infoStart = error.getActualStartOffset(); + int infoEnd = error.getActualEndOffset(); + + return ignoreRange.containsRange(infoStart,infoEnd) && error.type.equals(HighlightInfoType.WRONG_REF); } - } - return false; + }); + return hasErrorsExceptUnresolvedImports; } diff --git a/plugins/relaxng/test/org/intellij/plugins/relaxNG/HighlightingTestBase.java b/plugins/relaxng/test/org/intellij/plugins/relaxNG/HighlightingTestBase.java index 183daee3f4a8..b9e650d42bef 100644 --- a/plugins/relaxng/test/org/intellij/plugins/relaxNG/HighlightingTestBase.java +++ b/plugins/relaxng/test/org/intellij/plugins/relaxNG/HighlightingTestBase.java @@ -204,7 +204,6 @@ public abstract class HighlightingTestBase extends TestCase implements IdeaCodeI com.intellij.codeHighlighting.Pass.UPDATE_ALL, com.intellij.codeHighlighting.Pass.UPDATE_FOLDING, com.intellij.codeHighlighting.Pass.UPDATE_OVERRIDEN_MARKERS, - com.intellij.codeHighlighting.Pass.UPDATE_VISIBLE, com.intellij.codeHighlighting.Pass.VISIBLE_LINE_MARKERS, } : new int[]{com.intellij.codeHighlighting.Pass.EXTERNAL_TOOLS}; return CodeInsightTestFixtureImpl.instantiateAndRun(myTestFixture.getFile(), editor, ignore, false); diff --git a/xml/impl/src/com/intellij/codeInsight/daemon/impl/analysis/XmlUnusedNamespaceInspection.java b/xml/impl/src/com/intellij/codeInsight/daemon/impl/analysis/XmlUnusedNamespaceInspection.java index f33998759686..604dc15be5f6 100644 --- a/xml/impl/src/com/intellij/codeInsight/daemon/impl/analysis/XmlUnusedNamespaceInspection.java +++ b/xml/impl/src/com/intellij/codeInsight/daemon/impl/analysis/XmlUnusedNamespaceInspection.java @@ -52,7 +52,6 @@ public class XmlUnusedNamespaceInspection extends XmlSuppressableInspectionTool @Override public void visitXmlAttribute(XmlAttribute attribute) { - if (!attribute.isNamespaceDeclaration()) { checkUnusedLocations(attribute, holder); return; @@ -84,6 +83,7 @@ public class XmlUnusedNamespaceInspection extends XmlSuppressableInspectionTool } else { for (PsiReference reference : getLocationReferences(namespace, parent)) { + if (!XmlHighlightVisitor.hasBadResolve(reference, false)) holder.registerProblemForReference(reference, ProblemHighlightType.LIKE_UNUSED_SYMBOL, NAMESPACE_LOCATION_IS_NEVER_USED, new RemoveNamespaceDeclarationFix(declaredPrefix)); } @@ -139,10 +139,13 @@ public class XmlUnusedNamespaceInspection extends XmlSuppressableInspectionTool PsiReference reference = references[i]; String ns = getNamespaceFromReference(reference); if (ArrayUtil.indexOf(attribute.getParent().knownNamespaces(), ns) == -1) { + if (!XmlHighlightVisitor.hasBadResolve(reference, false)) holder.registerProblemForReference(reference, ProblemHighlightType.LIKE_UNUSED_SYMBOL, NAMESPACE_LOCATION_IS_NEVER_USED, new RemoveNamespaceLocationFix(ns)); if (i + 1 < referencesLength) { - holder.registerProblemForReference(references[i + 1], ProblemHighlightType.LIKE_UNUSED_SYMBOL, NAMESPACE_LOCATION_IS_NEVER_USED, + PsiReference nextRef = references[i + 1]; + if (!XmlHighlightVisitor.hasBadResolve(nextRef, false)) + holder.registerProblemForReference(nextRef, ProblemHighlightType.LIKE_UNUSED_SYMBOL, NAMESPACE_LOCATION_IS_NEVER_USED, new RemoveNamespaceLocationFix(ns)); } }