incremental highlighting: apply range highlighter as soon as it created

document range markers update speedup
visible highlighting pass eliminated
This commit is contained in:
Alexey Kudravtsev
2010-09-02 14:05:10 +04:00
parent cca55289ef
commit caa18a1d54
73 changed files with 4482 additions and 2321 deletions
@@ -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) {
@@ -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);
}
}
@@ -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;
@@ -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;
}
@@ -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);
}
}
@@ -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 {
@@ -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);
}
}
@@ -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();
}
}
@@ -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());
}
}
@@ -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) {
@@ -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;
}
}
@@ -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 {
@@ -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) {
@@ -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);
}
}
}
@@ -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;
}
});
}
}
@@ -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()]);
}
@@ -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() {
@@ -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;
}
}
}
@@ -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);
}
}
@@ -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
@@ -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());
@@ -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();
}
}
@@ -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);
}
}
@@ -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) {
@@ -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;
}
}
@@ -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);
}
@@ -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 {
@@ -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();
}
}
@@ -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);
}
}
}
}
@@ -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();
}
}
@@ -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;
}
}
@@ -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();
}
@@ -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);
@@ -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));
}
}