mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
incremental highlighting: apply range highlighter as soon as it created
document range markers update speedup visible highlighting pass eliminated
This commit is contained in:
@@ -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<HighlightInfo> getHighlights() {
|
||||
return myHighlights;
|
||||
}
|
||||
|
||||
private void collectHighlights(@NotNull Collection<PsiElement> elements, @NotNull final List<HighlightInfo> 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<HighlightInfo> 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<HighlightInfo>() {
|
||||
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) {
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+10
-4
@@ -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<HighlightInfo> 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<HighlightInfo>() {
|
||||
public boolean process(HighlightInfo info) {
|
||||
return !(info.getActualStartOffset() > argRange.getStartOffset() && info.getActualEndOffset() < argRange.getEndOffset());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected PsiElement getElement() {
|
||||
|
||||
@@ -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<CodeSmellInfo> findCodeSmells(final PsiFile psiFile, final ProgressIndicator progress, final Document document) {
|
||||
|
||||
private List<CodeSmellInfo> findCodeSmells(@NotNull PsiFile psiFile, final ProgressIndicator progress, @NotNull Document document) {
|
||||
final List<CodeSmellInfo> result = new ArrayList<CodeSmellInfo>();
|
||||
|
||||
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<HighlightInfo> infos = codeAnalyzer.runMainPasses(psiFile, document, progress);
|
||||
collectErrorsAndWarnings(infos, result, document);
|
||||
|
||||
return result;
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
+241
-105
@@ -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<List<HighlightInfo>> HIGHLIGHTS_IN_EDITOR_DOCUMENT_KEY = Key.create("HIGHLIGHTS_IN_EDITOR_DOCUMENT");
|
||||
private static final Key<List<LineMarkerInfo>> MARKERS_IN_EDITOR_DOCUMENT_KEY = Key.create("MARKERS_IN_EDITOR_DOCUMENT");
|
||||
//private static final Key<IntervalTree<HighlightInfo>> HIGHLIGHTS_IN_EDITOR_DOCUMENT_KEY = Key.create("HIGHLIGHTS_IN_EDITOR_DOCUMENT");
|
||||
private static final Key<List<HighlightInfo>> HIGHLIGHTS_TO_REMOVE_KEY = Key.create("HIGHLIGHTS_TO_REMOVE");
|
||||
|
||||
private static final Key<List<LineMarkerInfo>> 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<VirtualFile> myDisabledHintsFiles = new THashSet<VirtualFile>();
|
||||
private final Collection<PsiFile> myDisabledHighlightingFiles = new THashSet<PsiFile>();
|
||||
|
||||
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<List<HighlightInfo>> 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.<HighlightInfo>alwaysFalse());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@TestOnly
|
||||
public static List<HighlightInfo> getHighlights(Document document, HighlightSeverity minSeverity, Project project) {
|
||||
List<HighlightInfo> infos = new ArrayList<HighlightInfo>();
|
||||
processHighlights(document, project, minSeverity, 0, document.getTextLength(), new CommonProcessors.CollectProcessor<HighlightInfo>(infos));
|
||||
return infos;
|
||||
}
|
||||
|
||||
public List<HighlightInfo> 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<HighlightInfo> result = new ArrayList<HighlightInfo>();
|
||||
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<HighlightInfo> 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<TextEditorHighlightingPass> 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<HighlightInfo> 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<HighlightInfo> processor) {
|
||||
LOG.assertTrue(ApplicationManager.getApplication().isReadAccessAllowed());
|
||||
MarkupModel markup = document.getMarkupModel(project);
|
||||
return getHighlights(markup);
|
||||
}
|
||||
|
||||
static List<HighlightInfo> getHighlights(MarkupModel markup) {
|
||||
return markup.getUserData(HIGHLIGHTS_IN_EDITOR_DOCUMENT_KEY);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<HighlightInfo> getHighlights(Document document, HighlightSeverity minSeverity, Project project) {
|
||||
return getHighlights(document, minSeverity, project, Integer.MIN_VALUE, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<HighlightInfo> getHighlights(Document document, HighlightSeverity minSeverity, Project project, int startOffset, int endOffset) {
|
||||
LOG.assertTrue(ApplicationManager.getApplication().isReadAccessAllowed());
|
||||
List<HighlightInfo> highlights = getHighlights(document, project);
|
||||
if (highlights == null) return Collections.emptyList();
|
||||
List<HighlightInfo> array = new ArrayList<HighlightInfo>();
|
||||
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<RangeHighlighterEx>() {
|
||||
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<HighlightInfo> getHighlightsAround(Document document, Project project, int offset) {
|
||||
LOG.assertTrue(ApplicationManager.getApplication().isReadAccessAllowed());
|
||||
List<HighlightInfo> highlights = getHighlights(document, project);
|
||||
if (highlights == null) return Collections.emptyList();
|
||||
List<HighlightInfo> array = new ArrayList<HighlightInfo>();
|
||||
|
||||
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<HighlightInfo> processor) {
|
||||
return processHighlights(document, project, null, 0, document.getTextLength(), new Processor<HighlightInfo>() {
|
||||
public boolean process(HighlightInfo info) {
|
||||
if (!isOffsetInsideHighlightInfo(offset, info, includeFixRange)) return true;
|
||||
|
||||
@Nullable
|
||||
public HighlightInfo findHighlightByOffset(Document document, int offset, boolean includeFixRange) {
|
||||
List<HighlightInfo> highlights = getHighlights(document, myProject);
|
||||
if (highlights == null) return null;
|
||||
|
||||
List<HighlightInfo> foundInfoList = new SmartList<HighlightInfo>();
|
||||
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<HighlightInfo> foundInfoList = new SmartList<HighlightInfo>();
|
||||
processHighlightsNearOffset(document, myProject, HighlightSeverity.INFORMATION, offset, includeFixRange, new Processor<HighlightInfo>() {
|
||||
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<HighlightInfo> highlightsToSet, List<HighlightInfo> 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<HighlightInfo> highlights, MarkupModel markup) {
|
||||
private static void stripWarningsCoveredByErrors(Project project, MarkupModel markup, final HighlightInfo toAdd) {
|
||||
final SeverityRegistrar severityRegistrar = SeverityRegistrar.getInstance(project);
|
||||
Collection<HighlightInfo> errors = new ArrayList<HighlightInfo>();
|
||||
for (HighlightInfo highlight : highlights) {
|
||||
if (severityRegistrar.compare(highlight.getSeverity(), HighlightSeverity.ERROR) >= 0) {
|
||||
errors.add(highlight);
|
||||
}
|
||||
}
|
||||
final Set<HighlightInfo> covered = new THashSet<HighlightInfo>();
|
||||
|
||||
for (Iterator<HighlightInfo> 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<HighlightInfo>() {
|
||||
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<HighlightInfo> highlights, MarkupModel markup) {
|
||||
// final SeverityRegistrar severityRegistrar = SeverityRegistrar.getInstance(project);
|
||||
//
|
||||
// final Set<HighlightInfo> currentErrors = new THashSet<HighlightInfo>();
|
||||
// final Set<HighlightInfo> covered = new THashSet<HighlightInfo>();
|
||||
//
|
||||
//
|
||||
// final PriorityQueue<HighlightInfo> ends = new PriorityQueue<HighlightInfo>(1, new Comparator<HighlightInfo>() {
|
||||
// public int compare(HighlightInfo o1, HighlightInfo o2) {
|
||||
// return o1.getActualEndOffset() - o2.getActualEndOffset();
|
||||
// }
|
||||
// });
|
||||
// highlights.process(new Processor<HighlightInfo>() {
|
||||
// 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<HighlightInfo> getFileLevelHighlights(Project project,PsiFile file ) {
|
||||
return UpdateHighlightersUtil.getFileLeveleHighlights(project, file);
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public void clearPasses() {
|
||||
myPassExecutorService.cancelAll(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<PsiElement> inside,
|
||||
@NotNull List<PsiElement> 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<PsiElement> inside,
|
||||
@NotNull List<PsiElement> outside) {
|
||||
final int currentOffset = root.getTextRange().getStartOffset();
|
||||
final Condition<PsiElement>[] filters = Extensions.getExtensions(CollectHighlightsUtil.EP_NAME);
|
||||
|
||||
int offset = currentOffset;
|
||||
|
||||
final TIntStack starts = new TIntStack(STARTING_TREE_HEIGHT);
|
||||
final Stack<PsiElement> elements = new Stack<PsiElement>(STARTING_TREE_HEIGHT);
|
||||
final Stack<PsiElement> children = new Stack<PsiElement>(STARTING_TREE_HEIGHT);
|
||||
PsiElement element = root;
|
||||
|
||||
PsiElement child = PsiUtilBase.NULL_PSI_ELEMENT;
|
||||
while (true) {
|
||||
ProgressManager.checkCanceled();
|
||||
|
||||
for (Condition<PsiElement> 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);
|
||||
}
|
||||
}
|
||||
+44
-55
@@ -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<HighlightInfo> myHighlights = Collections.emptyList();
|
||||
private final Map<TextRange,Collection<HighlightInfo>> myInjectedPsiHighlights = new HashMap<TextRange, Collection<HighlightInfo>>();
|
||||
@@ -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<HighlightInfo> result = new THashSet<HighlightInfo>(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<PsiElement> inside = new ArrayList<PsiElement>();
|
||||
List<PsiElement> outside = new ArrayList<PsiElement>();
|
||||
try {
|
||||
final FileViewProvider viewProvider = myFile.getViewProvider();
|
||||
final Set<Language> relevantLanguages = viewProvider.getLanguages();
|
||||
List<PsiElement> elements = null;
|
||||
for (Language language : relevantLanguages) {
|
||||
PsiElement psiRoot = viewProvider.getPsi(language);
|
||||
if (!HighlightLevelUtil.shouldHighlight(psiRoot)) continue;
|
||||
List<PsiElement> 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<PsiElement> 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<List<GeneralHighlightingPass>> 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<HighlightInfo> collection = myInjectedPsiHighlights.get(range);
|
||||
if (collection == null) {
|
||||
collection = new ArrayList<HighlightInfo>(myHighlights.size());
|
||||
}
|
||||
collection.addAll(myHighlights);
|
||||
myInjectedPsiHighlights.put(range, collection);
|
||||
myHighlights = Collections.emptyList();
|
||||
|
||||
List<GeneralHighlightingPass> 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<GeneralHighlightingPass>();
|
||||
myProgress.putUserData(UPDATE_ALL_FINISHED, updateAllFinished);
|
||||
}
|
||||
updateAllFinished.add(this);
|
||||
}
|
||||
ProperTextRange range = new ProperTextRange(myStartOffset, myEndOffset);
|
||||
Collection<HighlightInfo> infos = myInjectedPsiHighlights.get(range);
|
||||
if (infos == null) {
|
||||
infos = new ArrayList<HighlightInfo>(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<HighlightInfo> getHighlights() {
|
||||
Collection<HighlightInfo> getHighlights() {
|
||||
ArrayList<HighlightInfo> list = new ArrayList<HighlightInfo>(myHighlights);
|
||||
for (Collection<HighlightInfo> 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<Problem> problems = convertToProblems(getHighlights(), file, myHasErrorElement);
|
||||
WolfTheProblemSolver wolf = WolfTheProblemSolver.getInstance(project);
|
||||
|
||||
List<HighlightInfo> 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 {
|
||||
|
||||
+3
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+47
-50
@@ -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<HighlightInfo> 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<HighlightInfo> 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<HighlightInfo> 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<HighlightInfo>() {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<HighlightInfo> BY_START_OFFSET = new Comparator<HighlightInfo>() {
|
||||
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<IntentionAction> options, final String displayName, Icon icon) {
|
||||
this(action, options, displayName, icon, null);
|
||||
}
|
||||
|
||||
public IntentionActionDescriptor(@NotNull IntentionAction action, final List<IntentionAction> 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();
|
||||
}
|
||||
}
|
||||
|
||||
-4
@@ -143,8 +143,6 @@ public class IdentifierHighlighterPass extends TextEditorHighlightingPass {
|
||||
|
||||
public static void clearMyHighlights(Document document, Project project) {
|
||||
MarkupModel markupModel = document.getMarkupModel(project);
|
||||
List<HighlightInfo> old = DaemonCodeAnalyzerImpl.getHighlights(document, project);
|
||||
List<HighlightInfo> result = new ArrayList<HighlightInfo>(old == null ? Collections.<HighlightInfo>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.<HighlightInfo>emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
+287
-151
@@ -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<ProblemDescriptor> myDescriptors = Collections.emptyList();
|
||||
@NotNull private List<HighlightInfoType> myLevels = Collections.emptyList();
|
||||
@NotNull private List<LocalInspectionTool> myTools = Collections.emptyList();
|
||||
@NotNull private List<InjectedPsiInspectionResult> myInjectedPsiInspectionResults = Collections.emptyList();
|
||||
private final TextRange myPriorityRange;
|
||||
private final ConcurrentMap<PsiFile, List<InspectionResult>> result = new ConcurrentHashMap<PsiFile, List<InspectionResult>>();
|
||||
static final String PRESENTABLE_NAME = DaemonBundle.message("pass.inspection");
|
||||
private volatile List<HighlightInfo> 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<ProblemDescriptor>();
|
||||
myLevels = new ArrayList<HighlightInfoType>();
|
||||
myTools = new ArrayList<LocalInspectionTool>();
|
||||
if (!HighlightLevelUtil.shouldInspect(myFile)) return;
|
||||
final InspectionManagerEx iManager = (InspectionManagerEx)InspectionManager.getInstance(myProject);
|
||||
final InspectionProfileWrapper profile = myProfileWrapper;
|
||||
final List<LocalInspectionTool> 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<InspectionProfileEntry> toolWrappers, boolean ignoreSuppressed) {
|
||||
myDescriptors = new ArrayList<ProblemDescriptor>();
|
||||
myLevels = new ArrayList<HighlightInfoType>();
|
||||
myTools = new ArrayList<LocalInspectionTool>();
|
||||
|
||||
Map<LocalInspectionTool, LocalInspectionToolWrapper> tool2Wrapper = new THashMap<LocalInspectionTool, LocalInspectionToolWrapper>(toolWrappers.size());
|
||||
for (InspectionProfileEntry toolWrapper : toolWrappers) {
|
||||
tool2Wrapper.put(((LocalInspectionToolWrapper)toolWrapper).getTool(), (LocalInspectionToolWrapper)toolWrapper);
|
||||
}
|
||||
List<LocalInspectionTool> tools = new ArrayList<LocalInspectionTool>(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<InspectionResult> 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<LocalInspectionTool, LocalInspectionToolWrapper> tool2Wrapper,
|
||||
InspectionManagerEx iManager) {
|
||||
private void addDescriptorsFromInjectedResults(Map<LocalInspectionTool, LocalInspectionToolWrapper> tool2Wrapper, InspectionManagerEx iManager) {
|
||||
Set<TextRange> emptyActionRegistered = new THashSet<TextRange>();
|
||||
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<TextRange> 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<PsiFile, List<InspectionResult>> entry : result.entrySet()) {
|
||||
PsiFile file = entry.getKey();
|
||||
if (file == myFile) continue; // not injected
|
||||
DocumentWindow documentRange = (DocumentWindow)documentManager.getDocument(file);
|
||||
List<InspectionResult> 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<TextRange> 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<PsiElement> inside = new ArrayList<PsiElement>();
|
||||
ArrayList<PsiElement> outside = new ArrayList<PsiElement>();
|
||||
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<Trinity<LocalInspectionTool, ProblemsHolder, PsiElementVisitor>> init = new ArrayList<Trinity<LocalInspectionTool, ProblemsHolder, PsiElementVisitor>>();
|
||||
visitPriorityElementsAndInit(tools, iManager, isOnTheFly, ignoreSuppressed, indicator, inside, session, init);
|
||||
visitRestElementsAndCleanup(tools,isOnTheFly,ignoreSuppressed, indicator, outside, session, init);
|
||||
|
||||
indicator.checkCanceled();
|
||||
|
||||
myInfos = new ArrayList<HighlightInfo>();
|
||||
addHighlightsFromResults(myInfos);
|
||||
}
|
||||
|
||||
private void visitPriorityElementsAndInit(List<LocalInspectionTool> tools,
|
||||
final InspectionManagerEx iManager,
|
||||
final boolean isOnTheFly,
|
||||
final boolean ignoreSuppressed,
|
||||
final ProgressIndicator indicator,
|
||||
final List<PsiElement> elements,
|
||||
final LocalInspectionToolSession session,
|
||||
final List<Trinity<LocalInspectionTool, ProblemsHolder, PsiElementVisitor>> init) {
|
||||
boolean result = JobUtil.invokeConcurrentlyUnderMyProgress(tools, new Processor<LocalInspectionTool>() {
|
||||
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<HighlightInfo>(myDescriptors.size());
|
||||
addHighlightsFromDescriptors(myInfos);
|
||||
addHighlightsFromInjectedPsiProblems(myInfos);
|
||||
inspectInjectedPsi(elements, tools, isOnTheFly, ignoreSuppressed, indicator);
|
||||
}
|
||||
|
||||
void inspectInjectedPsi(final PsiElement[] elements, final List<LocalInspectionTool> tools) {
|
||||
myInjectedPsiInspectionResults = ContainerUtil.createEmptyCOWList();
|
||||
private void visitRestElementsAndCleanup(
|
||||
List<LocalInspectionTool> tools,
|
||||
final boolean isOnTheFly,
|
||||
final boolean ignoreSuppressed,
|
||||
final ProgressIndicator indicator,
|
||||
final List<PsiElement> elements,
|
||||
final LocalInspectionToolSession session,
|
||||
List<Trinity<LocalInspectionTool, ProblemsHolder, PsiElementVisitor>> init) {
|
||||
boolean result = JobUtil.invokeConcurrentlyUnderMyProgress(init, new Processor<Trinity<LocalInspectionTool, ProblemsHolder, PsiElementVisitor>>() {
|
||||
@Override
|
||||
public boolean process(Trinity<LocalInspectionTool, ProblemsHolder, PsiElementVisitor> 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<PsiElement> elements,
|
||||
final List<LocalInspectionTool> tools,
|
||||
final boolean onTheFly,
|
||||
final boolean ignoreSuppressed, final ProgressIndicator indicator) {
|
||||
final Set<PsiFile> injected = new THashSet<PsiFile>();
|
||||
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<PsiFile>(injected), new Processor<PsiFile>() {
|
||||
public boolean process(final PsiFile injectedPsi) {
|
||||
inspectInjectedPsi(injectedPsi, myInjectedPsiInspectionResults, tools);
|
||||
doInspectInjectedPsi(injectedPsi, tools,onTheFly,ignoreSuppressed,indicator);
|
||||
return true;
|
||||
}
|
||||
}, myFailFastOnAcquireReadAction)) throw new ProcessCanceledException();
|
||||
}
|
||||
|
||||
public Collection<HighlightInfo> getHighlights() {
|
||||
ArrayList<HighlightInfo> highlights = new ArrayList<HighlightInfo>(myDescriptors.size());
|
||||
addHighlightsFromDescriptors(highlights);
|
||||
addHighlightsFromInjectedPsiProblems(highlights);
|
||||
List<HighlightInfo> highlights = new ArrayList<HighlightInfo>();
|
||||
|
||||
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<ProblemDescriptor> 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<Pair<ProblemDescriptor, LocalInspectionTool>> infosToAdd = new ConcurrentLinkedQueue<Pair<ProblemDescriptor, LocalInspectionTool>>();
|
||||
private final Set<TextRange> emptyActionRegistered = Collections.synchronizedSet(new HashSet<TextRange>());
|
||||
|
||||
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<HighlightInfo> infos = new ArrayList<HighlightInfo>(2);
|
||||
while (haveInfosToProcess.compareAndSet(true, false)) {
|
||||
for (Pair<ProblemDescriptor, LocalInspectionTool> 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<ProblemDescriptor> descriptors, LocalInspectionTool tool, boolean ignoreSuppressed) {
|
||||
InspectionResult res = new InspectionResult(tool, descriptors);
|
||||
appendResult(file, res);
|
||||
}
|
||||
|
||||
private void appendResult(PsiFile file, InspectionResult res) {
|
||||
List<InspectionResult> resultList = result.get(file);
|
||||
if (resultList == null) {
|
||||
resultList = ConcurrencyUtil.cacheOrGet(result, file, new ArrayList<InspectionResult>());
|
||||
}
|
||||
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<HighlightInfo> toInfos) {
|
||||
Set<TextRange> emptyActionRegistered = new THashSet<TextRange>();
|
||||
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<HighlightInfo> infos) {
|
||||
Set<TextRange> emptyActionRegistered = new THashSet<TextRange>();
|
||||
private void addHighlightsFromResults(final List<HighlightInfo> 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<TextRange> emptyActionRegistered = new THashSet<TextRange>();
|
||||
|
||||
PsiElement injectedPsi = result.injectedPsi;
|
||||
DocumentWindow documentRange = (DocumentWindow)documentManager.getDocument((PsiFile)injectedPsi);
|
||||
for (Map.Entry<PsiFile, List<InspectionResult>> 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<TextRange> 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<InspectionResult> 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<HighlightInfo> outInfos,
|
||||
Set<TextRange> 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<TextRange> 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<TextRange> 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<InjectedPsiInspectionResult> result, List<LocalInspectionTool> tools) {
|
||||
InspectionManager inspectionManager = InspectionManager.getInstance(injectedPsi.getProject());
|
||||
final ProblemsHolder problemsHolder = new ProblemsHolder(inspectionManager, injectedPsi, true);
|
||||
private void doInspectInjectedPsi(@NotNull PsiFile injectedPsi,
|
||||
@NotNull List<LocalInspectionTool> 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<ProblemDescriptor> problems = problemsHolder.getResults();
|
||||
if (problems != null && !problems.isEmpty()) {
|
||||
InjectedPsiInspectionResult res = new InjectedPsiInspectionResult(tool, injectedPsi, new SmartList<ProblemDescriptor>(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<ProblemDescriptor> 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<ProblemDescriptor> foundProblems;
|
||||
|
||||
private InjectedPsiInspectionResult(final LocalInspectionTool tool, final PsiElement injectedPsi, final List<ProblemDescriptor> foundProblems) {
|
||||
private InspectionResult(final LocalInspectionTool tool, final List<ProblemDescriptor> foundProblems) {
|
||||
this.tool = tool;
|
||||
this.injectedPsi = injectedPsi;
|
||||
this.foundProblems = foundProblems;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -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<LocalInspectionTool> getInspectionTools(InspectionProfileWrapper profile) {
|
||||
List<LocalInspectionTool> tools = super.getInspectionTools(profile);
|
||||
List<LocalInspectionTool> result = new ArrayList<LocalInspectionTool>(tools.size());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -289,11 +289,11 @@ public class SeverityRegistrar implements JDOMExternalizable, Comparator<Highlig
|
||||
return STANDART_SEVERITIES.containsKey(severity.myName);
|
||||
}
|
||||
|
||||
public static boolean skipSeverity(HighlightSeverity minSeverity) {
|
||||
public static boolean isGotoBySeverityEnabled(HighlightSeverity minSeverity) {
|
||||
for (SeveritiesProvider provider : Extensions.getExtensions(SeveritiesProvider.EP_NAME)) {
|
||||
if (!provider.isGotoBySeverityEnabled(minSeverity)) return true;
|
||||
if (provider.isGotoBySeverityEnabled(minSeverity)) return true;
|
||||
}
|
||||
return minSeverity == HighlightSeverity.INFORMATION;
|
||||
return minSeverity != HighlightSeverity.INFORMATION;
|
||||
}
|
||||
|
||||
public static class SeverityBasedTextAttributes implements JDOMExternalizable {
|
||||
|
||||
+12
-27
@@ -27,7 +27,6 @@ import com.intellij.openapi.actionSystem.IdeActions;
|
||||
import com.intellij.openapi.application.Application;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.LogicalPosition;
|
||||
import com.intellij.openapi.keymap.KeymapUtil;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.project.Project;
|
||||
@@ -35,11 +34,10 @@ import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.util.Processor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ShowAutoImportPass extends TextEditorHighlightingPass {
|
||||
@@ -56,24 +54,13 @@ public class ShowAutoImportPass extends TextEditorHighlightingPass {
|
||||
|
||||
myEditor = editor;
|
||||
|
||||
TextRange range = getVisibleRange(myEditor);
|
||||
TextRange range = VisibleHighlightingPassFactory.calculateVisibleRange(myEditor);
|
||||
myStartOffset = range.getStartOffset();
|
||||
myEndOffset = range.getEndOffset();
|
||||
|
||||
myFile = file;
|
||||
}
|
||||
|
||||
public static TextRange getVisibleRange(Editor editor) {
|
||||
Rectangle visibleRect = editor.getScrollingModel().getVisibleArea();
|
||||
|
||||
LogicalPosition startPosition = editor.xyToLogicalPosition(new Point(visibleRect.x, visibleRect.y));
|
||||
int myStartOffset = editor.logicalPositionToOffset(startPosition);
|
||||
|
||||
LogicalPosition endPosition = editor.xyToLogicalPosition(new Point(visibleRect.x + visibleRect.width, visibleRect.y + visibleRect.height));
|
||||
int myEndOffset = editor.logicalPositionToOffset(new LogicalPosition(endPosition.line + 1, 0));
|
||||
return new TextRange(myStartOffset, myEndOffset);
|
||||
}
|
||||
|
||||
public void doCollectInformation(ProgressIndicator progress) {
|
||||
}
|
||||
|
||||
@@ -95,19 +82,17 @@ public class ShowAutoImportPass extends TextEditorHighlightingPass {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<HighlightInfo> getVisibleHighlights(int startOffset, int endOffset, Project project, Editor editor) {
|
||||
List<HighlightInfo> highlights = DaemonCodeAnalyzerImpl.getHighlights(editor.getDocument(), project);
|
||||
if (highlights == null) return Collections.emptyList();
|
||||
|
||||
List<HighlightInfo> array = new ArrayList<HighlightInfo>();
|
||||
for (HighlightInfo info : highlights) {
|
||||
if (info.hasHint()
|
||||
&& startOffset <= info.startOffset && info.endOffset <= endOffset
|
||||
&& !editor.getFoldingModel().isOffsetCollapsed(info.startOffset)) {
|
||||
array.add(info);
|
||||
private static List<HighlightInfo> getVisibleHighlights(final int startOffset, final int endOffset, Project project, final Editor editor) {
|
||||
final List<HighlightInfo> highlights = new ArrayList<HighlightInfo>();
|
||||
DaemonCodeAnalyzerImpl.processHighlights(editor.getDocument(), project, null, startOffset, endOffset, new Processor<HighlightInfo>() {
|
||||
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) {
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+38
-30
@@ -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<HighlightInfo> 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.<IntentionAction>emptyList(), text, renderer.getIcon()));
|
||||
DaemonCodeAnalyzerImpl.processHighlights(document, project, null,
|
||||
document.getLineStartOffset(line),
|
||||
document.getLineEndOffset(line), new Processor<HighlightInfo>() {
|
||||
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.<IntentionAction>emptyList(), text, renderer.getIcon()));
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-21
@@ -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<TextEditorHighlightingPass> 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()]);
|
||||
}
|
||||
|
||||
|
||||
+70
-36
@@ -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<HighlightInfo> highlights = DaemonCodeAnalyzerImpl.getHighlights(myDocument, myProject);
|
||||
if (highlights == null) highlights = Arrays.asList(HighlightInfo.EMPTY_ARRAY);
|
||||
if (fillErrorsCount) {
|
||||
highlights = new ArrayList<HighlightInfo>(highlights);
|
||||
Collections.sort(highlights, new Comparator<HighlightInfo>() {
|
||||
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<HighlightInfo>() {
|
||||
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<HighlightInfo>(highlights);
|
||||
// Collections.sort(highlights, new Comparator<HighlightInfo>() {
|
||||
// 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() {
|
||||
|
||||
+298
-194
@@ -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<HighlightInfo> BY_START_OFFSET_NODUPS = new Comparator<HighlightInfo>() {
|
||||
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<List<HighlightInfo>> FILE_LEVEL_HIGHLIGHTS = Key.create("FILE_LEVEL_HIGHLIGHTS");
|
||||
private static final Comparator<TextRange> BY_START_OFFSET = new Comparator<TextRange>() {
|
||||
@@ -112,8 +135,8 @@ public class UpdateHighlightersUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class RangeHighlightersToReuse {
|
||||
private final MultiMap<TextRange, RangeHighlighter> info2Pass = new MultiMap<TextRange, RangeHighlighter>(){
|
||||
private static class HighlightersRecycler {
|
||||
private final MultiMap<TextRange, RangeHighlighter> incinerator = new MultiMap<TextRange, RangeHighlighter>(){
|
||||
@Override
|
||||
protected Map<TextRange, Collection<RangeHighlighter>> createMap() {
|
||||
return new THashMap<TextRange, Collection<RangeHighlighter>>();
|
||||
@@ -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<RangeHighlighter> collection = info2Pass.get(range);
|
||||
Collection<RangeHighlighter> 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<? extends RangeHighlighter> forAll() {
|
||||
return info2Pass.values();
|
||||
Collection<? extends RangeHighlighter> 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<HighlightInfo> otherHighlightInTheWayProcessor = new Processor<HighlightInfo>() {
|
||||
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<TextRange, Collection<HighlightInfo>> 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<TextRange> ranges = new ArrayList<TextRange>(infos.keySet());
|
||||
final List<TextRange> ranges = new ArrayList<TextRange>(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<HighlightInfo> oldHighlights = DaemonCodeAnalyzerImpl.getHighlights(markup);
|
||||
List<HighlightInfo> oldHighlightsToRemove = DaemonCodeAnalyzerImpl.getHighlightsToRemove(markup);
|
||||
assertMarkupConsistent(markup, oldHighlights, oldHighlightsToRemove);
|
||||
assertMarkupConsistent(markup, project);
|
||||
|
||||
List<HighlightInfo> result = new ArrayList<HighlightInfo>();
|
||||
RangeHighlightersToReuse infosToRemove = new RangeHighlightersToReuse();
|
||||
for (Map.Entry<TextRange, Collection<HighlightInfo>> entry : infos.entrySet()) {
|
||||
TextRange range = entry.getKey();
|
||||
Collection<HighlightInfo> highlights = entry.getValue();
|
||||
setHighlightersInRange(range, highlights, (MarkupModelEx)markup, group, document, project);
|
||||
}
|
||||
}
|
||||
|
||||
boolean changed = false;
|
||||
List<HighlightInfo> toRemoveInAnotherPass = new SmartList<HighlightInfo>();
|
||||
private static void setHighlightersInRange(final TextRange range,
|
||||
Collection<HighlightInfo> highlightsCo,
|
||||
final MarkupModelEx markup,
|
||||
final int group,
|
||||
final Document document,
|
||||
final Project project) {
|
||||
final List<HighlightInfo> highlights = new ArrayList<HighlightInfo>(highlightsCo);
|
||||
|
||||
if (oldHighlights != null) {
|
||||
List<HighlightInfo> 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<HighlightInfo>() {
|
||||
@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<TextRange, RangeMarker> ranges2markersCache = new THashMap<TextRange, RangeMarker>(10);
|
||||
final PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document);
|
||||
final boolean[] changed = {false};
|
||||
RangeMarkerTree.sweep(new RangeMarkerTree.Generator<HighlightInfo>(){
|
||||
@Override
|
||||
public boolean generate(Processor<HighlightInfo> processor) {
|
||||
return ContainerUtil.process(highlights, processor);
|
||||
}
|
||||
}, new MarkupModelEx.SweepProcessor<HighlightInfo>() {
|
||||
@Override
|
||||
public boolean process(int offset, HighlightInfo info, boolean atStart, Collection<HighlightInfo> 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<HighlightInfo> 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<TextRange, RangeMarker> ranges2markersCache = new THashMap<TextRange, RangeMarker>(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<HighlightInfo> highlights = new ArrayList<HighlightInfo>(infos.get(range));
|
||||
Collections.sort(highlights, new Comparator<HighlightInfo>() {
|
||||
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<TextRange, RangeMarker> 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<Pair<HighlightInfo.IntentionActionDescriptor, RangeMarker>>(info.quickFixActionRanges.size());
|
||||
info.quickFixActionMarkers = ContainerUtil.createEmptyCOWList();
|
||||
for (Pair<HighlightInfo.IntentionActionDescriptor, TextRange> 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<TextRange> 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<TextRange, RangeMarker> ranges2markersCache, TextRange textRange) {
|
||||
RangeMarker marker = ranges2markersCache.get(textRange);
|
||||
private static RangeMarker getOrCreate(@NotNull Document document, @Nullable Map<TextRange, RangeMarker> 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<LineMarkerInfo> oldMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(document, project);
|
||||
List<LineMarkerInfo> array = new ArrayList<LineMarkerInfo>(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<HighlightInfo> 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<HighlightInfo> infosToRemove = new ArrayList<HighlightInfo>(DaemonCodeAnalyzerImpl.getHighlightsToRemove(markup));
|
||||
boolean highlightersChanged = false;
|
||||
List<HighlightInfo> result = new ArrayList<HighlightInfo>(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<HighlightInfo> removed = new ArrayList<HighlightInfo>();
|
||||
final boolean[] documentChangedInsideHighlighter = {false};
|
||||
DaemonCodeAnalyzerImpl.processHighlights(document, project, null, start, end, new Processor<HighlightInfo>() {
|
||||
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<HighlightInfo> highlightsToSet, List<HighlightInfo> 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<HighlightInfo>() {
|
||||
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<HighlightInfo>() {
|
||||
public boolean process(HighlightInfo highlightInfo) {
|
||||
return UpdateHighlightersUtil.BY_START_OFFSET_NODUPS.compare(highlightInfo, info) != 0;
|
||||
}
|
||||
});
|
||||
assert contains: info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-50
@@ -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);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
|
||||
+8
@@ -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());
|
||||
|
||||
+32
-23
@@ -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<IntentionAction> 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<Pair<HighlightInfo.IntentionActionDescriptor, TextRange>>();
|
||||
}
|
||||
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<IntentionAction> 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<Pair<HighlightInfo.IntentionActionDescriptor, TextRange>>();
|
||||
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<IntentionAction> condition) {
|
||||
for (Iterator<Pair<HighlightInfo.IntentionActionDescriptor, TextRange>> it = info.quickFixActionRanges.iterator(); it.hasNext();) {
|
||||
Pair<HighlightInfo.IntentionActionDescriptor, TextRange> pair = it.next();
|
||||
@@ -89,24 +94,28 @@ public final class QuickFixAction {
|
||||
* Is invoked inside atomic action.
|
||||
*/
|
||||
@NotNull
|
||||
public static List<HighlightInfo.IntentionActionDescriptor> getAvailableActions(@NotNull Editor editor, @NotNull PsiFile file, final int passId) {
|
||||
int offset = editor.getCaretModel().getOffset();
|
||||
public static List<HighlightInfo.IntentionActionDescriptor> getAvailableActions(@NotNull final Editor editor, @NotNull final PsiFile file, final int passId) {
|
||||
final int offset = editor.getCaretModel().getOffset();
|
||||
final Project project = file.getProject();
|
||||
|
||||
List<HighlightInfo.IntentionActionDescriptor> result = new ArrayList<HighlightInfo.IntentionActionDescriptor>();
|
||||
List<HighlightInfo> 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<HighlightInfo.IntentionActionDescriptor> result = new ArrayList<HighlightInfo.IntentionActionDescriptor>();
|
||||
DaemonCodeAnalyzerImpl.processHighlightsNearOffset(editor.getDocument(), project, HighlightSeverity.INFORMATION, offset, true, new Processor<HighlightInfo>() {
|
||||
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<HighlightInfo.IntentionActionDescriptor> outList,
|
||||
int[] groups,
|
||||
private static void addAvailableActionsForGroups(HighlightInfo info,
|
||||
Editor editor,
|
||||
PsiFile file,
|
||||
List<HighlightInfo.IntentionActionDescriptor> 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<HighlightInfo.IntentionActionDescriptor, RangeMarker> pair : info.quickFixActionMarkers) {
|
||||
HighlightInfo.IntentionActionDescriptor actionInGroup = pair.first;
|
||||
RangeMarker range = pair.second;
|
||||
|
||||
@@ -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<RangeMarker> getGuardedBlocks() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
//todo convert injected RMs to host
|
||||
public boolean processRangeMarkers(@NotNull Processor<RangeMarker> processor) {
|
||||
return myDelegate.processRangeMarkers(processor);
|
||||
}
|
||||
|
||||
public boolean processRangeMarkersOverlappingWith(int start, int end, @NotNull Processor<RangeMarker> processor) {
|
||||
return myDelegate.processRangeMarkersOverlappingWith(start, end, processor);
|
||||
}
|
||||
|
||||
public boolean processRangeMarkersOverlappingWith(int offset, @NotNull Processor<RangeMarker> processor) {
|
||||
return myDelegate.processRangeMarkersOverlappingWith(offset, processor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<FoldRegion> result = new ArrayList<FoldRegion>();
|
||||
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<FoldingRegionWindow> 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;
|
||||
|
||||
@@ -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<? super RangeHighlighterEx> processor) {
|
||||
//todo
|
||||
return false;
|
||||
}
|
||||
|
||||
public Iterator<RangeHighlighterEx> iterator() {
|
||||
// todo
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Iterator<RangeHighlighterEx> iteratorFrom(@NotNull Interval interval) {
|
||||
// todo convert
|
||||
return myHostModel.iteratorFrom(interval);
|
||||
}
|
||||
public boolean sweep(int start, int end, @NotNull SweepProcessor<RangeHighlighterEx> sweepProcessor) {
|
||||
// todo convert
|
||||
return myHostModel.sweep(start, end, sweepProcessor);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<RangeMarker> getGuardedBlocks();
|
||||
|
||||
boolean processRangeMarkers(@NotNull Processor<RangeMarker> processor);
|
||||
boolean processRangeMarkersOverlappingWith(int start, int end, @NotNull Processor<RangeMarker> processor);
|
||||
boolean processRangeMarkersOverlappingWith(int offset, @NotNull Processor<RangeMarker> processor);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -39,7 +39,5 @@ public interface EditorMarkupModel extends MarkupModel {
|
||||
@NotNull
|
||||
ErrorStripTooltipRendererProvider getErrorStripTooltipRendererProvider();
|
||||
|
||||
int getMinMarkHeight();
|
||||
|
||||
void setMinMarkHeight(int minMarkHeight);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<RangeHighlighterEx> {
|
||||
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<? super RangeHighlighterEx> processor);
|
||||
@NotNull
|
||||
Iterator<RangeHighlighterEx> iteratorFrom(@NotNull Interval interval);
|
||||
|
||||
interface SweepProcessor<T> {
|
||||
boolean process(int offset, T interval, boolean atStart, Collection<T> overlappingIntervals);
|
||||
}
|
||||
|
||||
boolean sweep(int start, int end, @NotNull final SweepProcessor<RangeHighlighterEx> sweepProcessor);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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<DocumentListener> myDocumentListeners = new ArrayList<DocumentListener>();
|
||||
private final WeakHashMap<RangeMarkerEx,String> myRangeMarkers = new WeakHashMap<RangeMarkerEx, String>();
|
||||
private final RangeMarkerTree<RangeMarkerEx> myRangeMarkers = new RangeMarkerTree<RangeMarkerEx>(this);
|
||||
private final List<RangeMarker> myGuardedBlocks = new ArrayList<RangeMarker>();
|
||||
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<RangeMarkerEx> 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<RangeMarkerEx> 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<RangeMarkerEx> 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<RangeMarker> processor) {
|
||||
return myRangeMarkers.process(processor);
|
||||
}
|
||||
|
||||
public boolean processRangeMarkersOverlappingWith(int start, int end, @NotNull Processor<RangeMarker> processor) {
|
||||
return myRangeMarkers.processOverlappingWith(start, end, processor);
|
||||
}
|
||||
|
||||
public boolean processRangeMarkersOverlappingWith(int offset, @NotNull Processor<RangeMarker> processor) {
|
||||
return myRangeMarkers.processOverlappingWith(offset, processor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-11
@@ -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<RangeHighlighterImpl> docHighlighters = docList != null ? docList.getHighlighterIterator() : null;
|
||||
Iterator<RangeHighlighterEx> docHighlighters = docMarkup.iterator();
|
||||
|
||||
final MarkupModelEx editorMarkup = (MarkupModelEx)myEditor.getMarkupModel();
|
||||
final HighlighterList editorList = editorMarkup.getHighlighterList();
|
||||
Iterator<RangeHighlighterImpl> editorHighlighters = editorList != null ? editorList.getHighlighterIterator() : null;
|
||||
Iterator<RangeHighlighterEx> 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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+284
-428
@@ -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<ErrorStripeListener> myErrorMarkerListeners = new ArrayList<ErrorStripeListener>();
|
||||
private ErrorStripeListener[] myCachedErrorMarkerListeners = null;
|
||||
private List<RangeHighlighter> myCachedSortedHighlighters = null;
|
||||
private final MarkSpots myMarkSpots = new MarkSpots();
|
||||
private int myScrollBarHeight;
|
||||
private static final Comparator<RangeHighlighter> LAYER_COMPARATOR = new Comparator<RangeHighlighter>() {
|
||||
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<MarkSpot> myWideSpots;
|
||||
private List<MarkSpot> 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<RangeHighlighter> highlighters = new THashSet<RangeHighlighter>();
|
||||
|
||||
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<MarkSpot> nearestMarkSpots = getNearestMarkSpots(e, width);
|
||||
if (nearestMarkSpots.isEmpty()) return false;
|
||||
Set<RangeHighlighter> highlighters = new THashSet<RangeHighlighter>(nearestMarkSpots.size() + 4);
|
||||
for (MarkSpot markSpot : nearestMarkSpots) {
|
||||
ContainerUtil.addAll(highlighters, markSpot.highlighters);
|
||||
private RangeHighlighter getNearestRangeHighlighter(final MouseEvent e, final int width) {
|
||||
List<RangeHighlighter> highlighters = new ArrayList<RangeHighlighter>();
|
||||
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<RangeHighlighter> 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<RangeHighlighterEx>() {
|
||||
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<MarkSpot>();
|
||||
myNarrowSpots = new ArrayList<MarkSpot>();
|
||||
|
||||
buildSpots(false, myWideSpots);
|
||||
buildSpots(true, myNarrowSpots);
|
||||
}
|
||||
|
||||
private void buildSpots(boolean narrow, List<MarkSpot> spots) {
|
||||
final List<RangeHighlighter> sortedHighlighters = getSortedHighlighters();
|
||||
|
||||
if (sortedHighlighters.isEmpty()) return;
|
||||
Queue<PositionedRangeHighlighter> startQueue =
|
||||
new PriorityQueue<PositionedRangeHighlighter>(5, new Comparator<PositionedRangeHighlighter>() {
|
||||
public int compare(final PositionedRangeHighlighter o1, final PositionedRangeHighlighter o2) {
|
||||
return o1.yStart - o2.yStart;
|
||||
}
|
||||
});
|
||||
Queue<PositionedRangeHighlighter> endQueue =
|
||||
new PriorityQueue<PositionedRangeHighlighter>(5, new Comparator<PositionedRangeHighlighter>() {
|
||||
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<PositionedRangeHighlighter> 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<PositionedRangeHighlighter> startQueue,
|
||||
List<MarkSpot> 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<MarkSpot> 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<MarkSpot> 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<MarkSpot> 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<MarkSpot> 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<MarkSpot> getNearestMarkSpots(final MouseEvent e, final double width) {
|
||||
List<MarkSpot> nearestSpot = findSpots(e, width, null, myWideSpots);
|
||||
nearestSpot = findSpots(e, width, nearestSpot, myNarrowSpots);
|
||||
return nearestSpot == null ? Collections.<MarkSpot>emptyList() : nearestSpot;
|
||||
}
|
||||
|
||||
private List<MarkSpot> findSpots(MouseEvent e, double width, List<MarkSpot> nearestSpot, List<MarkSpot> spots) {
|
||||
for (MarkSpot markSpot : spots) {
|
||||
if (markSpot.near(e, width)) {
|
||||
if (nearestSpot == null) {
|
||||
nearestSpot = new SmartList<MarkSpot>();
|
||||
}
|
||||
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<RangeHighlighter> getSortedHighlighters() {
|
||||
if (myCachedSortedHighlighters == null) {
|
||||
myCachedSortedHighlighters = new ArrayList<RangeHighlighter>();
|
||||
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<RangeHighlighter>() {
|
||||
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<PositionedStripe> thinEnds = new PriorityQueue<PositionedStripe>(5, new Comparator<PositionedStripe>() {
|
||||
public int compare(PositionedStripe o1, PositionedStripe o2) {
|
||||
return o1.yEnd - o2.yEnd;
|
||||
}
|
||||
});
|
||||
final Queue<PositionedStripe> wideEnds = new PriorityQueue<PositionedStripe>(5, new Comparator<PositionedStripe>() {
|
||||
public int compare(PositionedStripe o1, PositionedStripe o2) {
|
||||
return o1.yEnd - o2.yEnd;
|
||||
}
|
||||
});
|
||||
// sorted by layer
|
||||
final List<PositionedStripe> thinStripes = new ArrayList<PositionedStripe>();
|
||||
final List<PositionedStripe> wideStripes = new ArrayList<PositionedStripe>();
|
||||
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<RangeHighlighterEx>() {
|
||||
public boolean process(RangeHighlighterEx highlighter) {
|
||||
Color color = highlighter.getErrorStripeMarkColor();
|
||||
if (color == null) return true;
|
||||
boolean isThin = highlighter.isThinErrorStripeMark();
|
||||
int[] yStart = isThin ? thinYStart : wideYStart;
|
||||
List<PositionedStripe> stripes = isThin ? thinStripes : wideStripes;
|
||||
Queue<PositionedStripe> 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<PositionedStripe> ends,
|
||||
List<PositionedStripe> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<? super RangeHighlighterEx> processor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public Iterator<RangeHighlighterEx> iterator() {
|
||||
return ContainerUtil.emptyIterator();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Iterator<RangeHighlighterEx> iteratorFrom(@NotNull Interval interval) {
|
||||
return iterator();
|
||||
}
|
||||
public boolean sweep(int start, int end, @NotNull SweepProcessor<RangeHighlighterEx> sweepProcessor) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<FoldRegion> myRegions = CollectionFactory.arrayList(); //sorted in tree left-to-right topdown traversal order
|
||||
private static final Comparator<FoldRegion> BY_END_OFFSET = new Comparator<FoldRegion>() {
|
||||
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<? super FoldRegion> BY_END_OFFSET_REVERSE = Collections.reverseOrder(BY_END_OFFSET);
|
||||
|
||||
void clear() {
|
||||
myCachedVisible = null;
|
||||
myCachedTopLevelRegions = null;
|
||||
myCachedEndOffsets = null;
|
||||
myCachedStartOffsets = null;
|
||||
myCachedFoldedLines = null;
|
||||
myRegions = new ArrayList<FoldRegion>();
|
||||
}
|
||||
|
||||
private boolean isFoldingEnabledAndUpToDate() {
|
||||
return isFoldingEnabled() && myCachedVisible != null;
|
||||
}
|
||||
|
||||
protected abstract boolean isFoldingEnabled();
|
||||
|
||||
protected abstract boolean isBatchFoldingProcessing();
|
||||
|
||||
void rebuild() {
|
||||
ArrayList<FoldRegion> topLevels = new ArrayList<FoldRegion>(myRegions.size() / 2);
|
||||
ArrayList<FoldRegion> visible = new ArrayList<FoldRegion>(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<FoldRegion> allCollapsed = new ArrayList<FoldRegion>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
+37
-304
@@ -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<FoldingGroup, FoldRegion> myGroups = new MultiMap<FoldingGroup, FoldRegion>();
|
||||
private static final Comparator<FoldRegion> BY_END_OFFSET = new Comparator<FoldRegion>() {
|
||||
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<? super FoldRegion> 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<FoldRegion> 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<FoldRegion> 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<FoldRegion>();
|
||||
}
|
||||
|
||||
private boolean isFoldingEnabled() {
|
||||
return FoldingModelImpl.this.isFoldingEnabled() && myCachedVisible != null;
|
||||
}
|
||||
|
||||
void rebuild() {
|
||||
ArrayList<FoldRegion> topLevels = new ArrayList<FoldRegion>(myRegions.size() / 2);
|
||||
ArrayList<FoldRegion> visible = new ArrayList<FoldRegion>(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<FoldRegion> allCollapsed = new ArrayList<FoldRegion>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<RangeHighlighterImpl> mySegmentHighlighters = new SortedList<RangeHighlighterImpl>(MY_RANGE_COMPARATOR) {
|
||||
@Override
|
||||
protected void sort(List<RangeHighlighterImpl> delegate) {
|
||||
Iterator<RangeHighlighterImpl> 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<RangeHighlighterImpl> MY_RANGE_COMPARATOR = new Comparator<RangeHighlighterImpl>() {
|
||||
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<RangeHighlighterImpl> 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<RangeHighlighterImpl> getHighlighterIterator() {
|
||||
if (myIsDirtied) sortMarkers();
|
||||
return mySegmentHighlighters.iterator();
|
||||
}
|
||||
|
||||
List<RangeHighlighterImpl> 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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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<T extends Interval> extends Iterable<T> {
|
||||
boolean process(@NotNull Processor<? super T> processor);
|
||||
boolean processOverlappingWith(int start, int end, @NotNull Processor<? super T> processor);
|
||||
boolean processOverlappingWith(int offset, @NotNull Processor<? super T> processor);
|
||||
|
||||
Object add(@NotNull T interval);
|
||||
boolean remove(@NotNull T interval);
|
||||
|
||||
}
|
||||
@@ -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<T extends MutableInterval> extends RedBlackTree<T> implements IntervalTree<T> {
|
||||
protected final ReadWriteLock l = new ReentrantReadWriteLock();
|
||||
private final EqualStartIntervalComparator<T> comparator;
|
||||
|
||||
public class MyNode extends DeltaNode<T> {
|
||||
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<T extends MutableInterval> extends RedBlackTree.Node<T> {
|
||||
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<T> lookupNode(T key, Node<T> 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<T extends MutableInterval> {
|
||||
int compare(T i1, T i2);
|
||||
}
|
||||
|
||||
public IntervalTreeImpl(EqualStartIntervalComparator<T> comparator) {
|
||||
super(null);
|
||||
this.comparator = comparator;
|
||||
}
|
||||
|
||||
protected MyNode getRoot() {
|
||||
return (MyNode)root;
|
||||
}
|
||||
|
||||
public boolean process(@NotNull Processor<? super T> processor) {
|
||||
try {
|
||||
l.readLock().lock();
|
||||
checkMax(true);
|
||||
return process(getRoot(), processor, modCount);
|
||||
}
|
||||
finally {
|
||||
l.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean process(Node<T> root, Processor<? super T> 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<? super T> 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<? super T> 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<? super T> 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<? super T> 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<Integer,Integer,Integer> 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<Integer, Integer, Integer> l = checkMax(root.getLeft(), delta, assertInvalid);
|
||||
int minLeftStart = l.first;
|
||||
int maxLeftStart = l.second;
|
||||
int maxLeftEnd = l.third;
|
||||
Trinity<Integer, Integer, Integer> 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<T> maximumNode(Node<T> 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<T> 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<T> root, Node<T> 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 <T extends MutableInterval> int computeDeltaUpToRoot(DeltaNode<T> node) {
|
||||
int delta = 0;
|
||||
while (node != null) {
|
||||
if (node.key.isValid()) {
|
||||
delta += node.delta;
|
||||
}
|
||||
node = (DeltaNode<T>)node.getParent();
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void rotateRight(Node<T> 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<T> 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<T> node, Node<T> 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<T> 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<T> iteratorFrom(@NotNull Interval interval) {
|
||||
MyNode firstOverlap = findMinOverlappingWith(getRoot(), interval, modCount, 0);
|
||||
if (firstOverlap == null) {
|
||||
return ContainerUtil.emptyIterator();
|
||||
}
|
||||
return createIteratorFrom(firstOverlap);
|
||||
}
|
||||
|
||||
private Iterator<T> createIteratorFrom(final MyNode firstNode) {
|
||||
checkMax(true);
|
||||
|
||||
final int modCountBefore = modCount;
|
||||
return new Iterator<T>() {
|
||||
Node<T> element = firstNode;
|
||||
|
||||
public boolean hasNext() {
|
||||
return element != null;
|
||||
}
|
||||
|
||||
public T next() {
|
||||
assert modCount == modCountBefore;
|
||||
if (element == null) throw new NoSuchElementException();
|
||||
Node<T> prev = element;
|
||||
element = getNext(element);
|
||||
return prev.key;
|
||||
}
|
||||
|
||||
private Node<T> getNext(Node<T> element) {
|
||||
Node<T> n = element.getRight();
|
||||
if (n != null) {
|
||||
while (n.getLeft()!= null) {
|
||||
n = n.getLeft();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
Node<T> 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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<RangeHighlighterImpl> myViewHighlighters;
|
||||
private int myCurrentViewHighlighterIdx;
|
||||
private final HighlighterSweep myView = new HighlighterSweep();
|
||||
|
||||
private final List<RangeHighlighterImpl> 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<RangeHighlighterImpl> myCurrentHighlighters;
|
||||
|
||||
private RangeHighlighterImpl myNextViewHighlighter = null;
|
||||
private RangeHighlighterImpl myNextDocumentHighlighter = null;
|
||||
private final List<RangeHighlighterEx> 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<TextAttributes> myCachedAttributesList;
|
||||
private final List<TextAttributes> 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<RangeHighlighterImpl>();
|
||||
|
||||
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<RangeHighlighterImpl>();
|
||||
|
||||
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<TextAttributes>(5);
|
||||
|
||||
reinit();
|
||||
myCurrentHighlighters = new ArrayList<RangeHighlighterEx>();
|
||||
|
||||
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<RangeHighlighterImpl> 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<RangeHighlighterEx> 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<RangeHighlighterImpl>();
|
||||
}
|
||||
}
|
||||
else if (!myCurrentHighlighters.isEmpty()) {
|
||||
ArrayList<RangeHighlighterImpl> copy = new ArrayList<RangeHighlighterImpl>(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<TextAttributes> 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<RangeHighlighterImpl> {
|
||||
private static class LayerComparator implements Comparator<RangeHighlighterEx> {
|
||||
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;
|
||||
|
||||
@@ -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<RangeHighlighter> myHighlighters = new ArrayList<RangeHighlighter>();
|
||||
private RangeHighlighter[] myCachedHighlighters;
|
||||
private final List<MarkupModelListener> myListeners = new ArrayList<MarkupModelListener>();
|
||||
private MarkupModelListener[] myCachedListeners;
|
||||
private final List<MarkupModelListener> 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<RangeHighlighterEx> list = new ArrayList<RangeHighlighterEx>();
|
||||
myHighlighterTree.process(new CommonProcessors.CollectProcessor<RangeHighlighterEx>(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<RangeMarkerEx>() {
|
||||
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<RangeHighlighterEx>() {
|
||||
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<? super RangeHighlighterEx> processor) {
|
||||
return myHighlighterTree.processOverlappingWith(start, end, processor);
|
||||
}
|
||||
|
||||
public Iterator<RangeHighlighterEx> iterator() {
|
||||
return myHighlighterTree.iterator();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Iterator<RangeHighlighterEx> iteratorFrom(@NotNull Interval interval) {
|
||||
return myHighlighterTree.iteratorFrom(interval);
|
||||
}
|
||||
|
||||
public boolean sweep(int start, int end, @NotNull SweepProcessor<RangeHighlighterEx> sweepProcessor) {
|
||||
return myHighlighterTree.sweep(start, end, sweepProcessor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
+4
-3
@@ -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 {
|
||||
|
||||
+166
@@ -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();
|
||||
}
|
||||
}
|
||||
+241
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+53
-157
@@ -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> T getUserData(@NotNull Key<T> key) {
|
||||
return myRangeMarker.getUserData(key);
|
||||
}
|
||||
|
||||
public <T> void putUserData(@NotNull Key<T> key, T value) {
|
||||
myRangeMarker.putUserData(key, value);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return myRangeMarker.toString();
|
||||
public int getAffectedAreaEndOffset() {
|
||||
return data.getAffectedAreaEndOffset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<RangeHighlighterEx> {
|
||||
public RangeHighlighterTree(Document document) {
|
||||
super(document, COMPARATOR);
|
||||
}
|
||||
|
||||
private static final EqualStartIntervalComparator<RangeHighlighterEx> COMPARATOR = new EqualStartIntervalComparator<RangeHighlighterEx>() {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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<RangeMarkerEx>.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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<T extends RangeMarkerEx> extends IntervalTreeImpl<T> {
|
||||
private static final EqualStartIntervalComparator<RangeMarkerEx> RANGEMARKER_COMPARATOR = new EqualStartIntervalComparator<RangeMarkerEx>() {
|
||||
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<T> 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<T>)RANGEMARKER_COMPARATOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Trinity<Integer, Integer, Integer> 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<MyNode> affected = new ArrayList<MyNode>();
|
||||
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<MyNode> 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<T> sweepProcessor) {
|
||||
return sweep(new Generator<T>() {
|
||||
@Override
|
||||
public boolean generate(Processor<T> processor) {
|
||||
return processOverlappingWith(start, end, processor);
|
||||
}
|
||||
}, sweepProcessor);
|
||||
|
||||
}
|
||||
|
||||
public interface Generator<T> {
|
||||
boolean generate(Processor<T> processor);
|
||||
}
|
||||
|
||||
public static <T extends Segment> boolean sweep(@NotNull Generator<T> generator, @NotNull final MarkupModelEx.SweepProcessor<T> sweepProcessor) {
|
||||
final Queue<T> ends = new PriorityQueue<T>(5, new Comparator<T>() {
|
||||
public int compare(T o1, T o2) {
|
||||
return o1.getEndOffset() - o2.getEndOffset();
|
||||
}
|
||||
});
|
||||
final List<T> starts = new ArrayList<T>();
|
||||
if (!generator.generate(new Processor<T>() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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<K> {
|
||||
public static final boolean VERIFY = false;
|
||||
private static final int INDENT_STEP = 4;
|
||||
protected int size;
|
||||
protected int modCount;
|
||||
public Node<K> root;
|
||||
|
||||
private final Comparator<K> comparator;
|
||||
|
||||
public RedBlackTree(Comparator<K> comparator) {
|
||||
this.comparator = comparator;
|
||||
root = null;
|
||||
verifyProperties();
|
||||
}
|
||||
|
||||
protected Node<K> lookupNode(K key, Node<K> 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<K> n) {
|
||||
Node<K> 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<K> n) {
|
||||
Node<K> 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<K> oldn, Node<K> newn) {
|
||||
Node<K> 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<K> createNewNode(K key) {
|
||||
return new Node<K>(key);
|
||||
}
|
||||
|
||||
public Node<K> insert(Node<K> newNode) {
|
||||
modCount++;
|
||||
newNode.color = Color.RED;
|
||||
K key = newNode.key;
|
||||
|
||||
if (root == null) {
|
||||
root = newNode;
|
||||
}
|
||||
else {
|
||||
Node<K> 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<K> n) {
|
||||
if (n.getParent() == null) {
|
||||
n.color = Color.BLACK;
|
||||
}
|
||||
else {
|
||||
insertCase2(n);
|
||||
}
|
||||
}
|
||||
|
||||
private void insertCase2(Node<K> n) {
|
||||
if (nodeColor(n.getParent()) != Color.BLACK) {
|
||||
insertCase3(n);
|
||||
}
|
||||
// Tree is still valid
|
||||
}
|
||||
|
||||
void insertCase3(Node<K> 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<K> 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<K> 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 <K> void assertParentChild(Node<K> node1) {
|
||||
assert node1 == null || node1.getParent() == null || node1.getParent().getLeft() == node1 || node1.getParent().getRight() == node1;
|
||||
}
|
||||
|
||||
//returns parent of deleted node
|
||||
public Node<K> delete(K key) {
|
||||
modCount++;
|
||||
Node<K> n = lookupNode(key, root);
|
||||
deleteNode(n);
|
||||
return n;
|
||||
}
|
||||
|
||||
protected void deleteNode(Node<K> 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<K> 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<K> 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<K> swapWithMaxPred(Node<K> nowAscendant, Node<K> nowDescendant) {
|
||||
nowAscendant.key = nowDescendant.key;
|
||||
//nowAscendant.value = nowDescendant.value;
|
||||
return nowDescendant;
|
||||
}
|
||||
|
||||
protected Node<K> maximumNode(Node<K> n) {
|
||||
assert n != null;
|
||||
while (n.getRight() != null) {
|
||||
n = n.getRight();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
private void deleteCase1(Node<K> n) {
|
||||
if (n.getParent() != null) {
|
||||
deleteCase2(n);
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteCase2(Node<K> 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<K> 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<K> 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<K> 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<K> 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("<empty tree>");
|
||||
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<Integer> t = new RedBlackTree<Integer>();
|
||||
for (int i = 0; i < adds.length; i++) {
|
||||
int key = adds[i];
|
||||
Node<Integer> 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<Integer> t = new RedBlackTree<Integer>();
|
||||
List<Integer> adds = new ArrayList<Integer>();
|
||||
List<Integer> dels = new ArrayList<Integer>();
|
||||
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<Integer> node = t.createNewNode(x);
|
||||
node.value = x;
|
||||
t.insert(node);
|
||||
Integer lookup = t.lookupNode(x).value;
|
||||
assert lookup.equals(i) : lookup;
|
||||
}
|
||||
List<Integer> candidates = new ArrayList<Integer>(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> {
|
||||
K key;
|
||||
private Node<K> left;
|
||||
private Node<K> right;
|
||||
private Node<K> parent;
|
||||
protected Color color;
|
||||
|
||||
public Node(K key) {
|
||||
this.key = key;
|
||||
color = Color.RED;
|
||||
setParent(null);
|
||||
}
|
||||
|
||||
public Node<K> grandparent() {
|
||||
assert getParent() != null; // Not the root node
|
||||
assert getParent().getParent() != null; // Not child of root
|
||||
return getParent().getParent();
|
||||
}
|
||||
|
||||
public Node<K> sibling() {
|
||||
Node<K> parent = getParent();
|
||||
assert parent != null; // Root node has no sibling
|
||||
return this == parent.getLeft() ? parent.getRight() : parent.getLeft();
|
||||
}
|
||||
|
||||
public Node<K> uncle() {
|
||||
assert getParent() != null; // Root node has no uncle
|
||||
assert getParent().getParent() != null; // Children of root have no uncle
|
||||
return getParent().sibling();
|
||||
}
|
||||
|
||||
public Node<K> getLeft() {
|
||||
return left;
|
||||
}
|
||||
|
||||
public void setLeft(Node<K> left) {
|
||||
this.left = left;
|
||||
}
|
||||
|
||||
public Node<K> getRight() {
|
||||
return right;
|
||||
}
|
||||
|
||||
public void setRight(Node<K> right) {
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
public Node<K> getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public void setParent(Node<K> 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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
+7
-1
@@ -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);
|
||||
}
|
||||
|
||||
@@ -117,10 +117,6 @@
|
||||
<implementation-class>com.intellij.codeInsight.daemon.impl.GeneralHighlightingPassFactory</implementation-class>
|
||||
<skipForDefaultProject/>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>com.intellij.codeInsight.daemon.impl.VisibleGeneralHighlightingPassFactory</implementation-class>
|
||||
<skipForDefaultProject/>
|
||||
</component>
|
||||
<component>
|
||||
<implementation-class>com.intellij.codeInsight.daemon.impl.LineMarkersPassFactory</implementation-class>
|
||||
<skipForDefaultProject/>
|
||||
|
||||
@@ -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<RangeMarker> getGuardedBlocks() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public boolean processRangeMarkers(@NotNull Processor<RangeMarker> processor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean processRangeMarkersOverlappingWith(int start, int end, @NotNull Processor<RangeMarker> processor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean processRangeMarkersOverlappingWith(int offset, @NotNull Processor<RangeMarker> processor) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+15
-32
@@ -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<List<HighlightInfo>>() {
|
||||
public List<HighlightInfo> compute() {
|
||||
return instantiateAndRun(getFile(), getEditor(), ArrayUtil.EMPTY_INT_ARRAY, false);
|
||||
return instantiateAndRun(getFile(), getEditor(), ArrayUtil.EMPTY_INT_ARRAY, myAllowDirt);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<HighlightInfo> instantiateAndRun(PsiFile file, Editor editor, int[] toIgnore, boolean allowDirt) {
|
||||
public static List<HighlightInfo> 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<TextEditorHighlightingPass> 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<HighlightInfo> infos = DaemonCodeAnalyzerImpl.getHighlights(editor.getDocument(), project);
|
||||
return infos == null ? Collections.<HighlightInfo>emptyList() : new ArrayList<HighlightInfo>(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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
+9
-8
@@ -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<HighlightInfo> 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<HighlightInfo>() {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
+5
-2
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user