mergeable line marker infos

This commit is contained in:
Konstantin Bulenkov
2012-01-11 17:19:37 +01:00
parent c074e58f31
commit 6a0d47b27b
5 changed files with 232 additions and 5 deletions
@@ -25,6 +25,7 @@ import com.intellij.codeInsight.daemon.impl.LineMarkersPass;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiUtilBase;
import java.util.*;
@@ -34,7 +35,7 @@ public class MethodSeparatorProvider extends FileSeparatorProvider {
final List<LineMarkerInfo> result = new ArrayList<LineMarkerInfo>();
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
LineMarkersPass action = new LineMarkersPass(file.getProject(), file, document, 0, file.getTextLength(), true);
LineMarkersPass action = new LineMarkersPass(file.getProject(), file, PsiUtilBase.findEditor(file), document, 0, file.getTextLength(), true);
Collection<LineMarkerInfo> lineMarkerInfos = action.queryLineMarkers();
for (LineMarkerInfo lineMarkerInfo : lineMarkerInfos) {
if (lineMarkerInfo.separatorColor != null) {
@@ -0,0 +1,176 @@
/*
* Copyright 2000-2012 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;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.markup.GutterIconRenderer;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBList;
import com.intellij.util.Function;
import com.intellij.util.FunctionUtil;
import com.intellij.util.NotNullFunction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.MouseEvent;
import java.util.*;
/**
* @author Konstantin Bulenkov
*/
public abstract class MergeableLineMarkerInfo<T extends PsiElement> extends LineMarkerInfo<T> {
public MergeableLineMarkerInfo(@NotNull T element,
TextRange textRange,
Icon icon,
int updatePass,
@Nullable Function<? super T, String> tooltipProvider,
@Nullable GutterIconNavigationHandler<T> navHandler,
GutterIconRenderer.Alignment alignment) {
super(element, textRange, icon, updatePass, tooltipProvider, navHandler, alignment);
}
public abstract boolean canMergeWith(MergeableLineMarkerInfo<?> info);
public abstract Icon getCommonIcon(List<MergeableLineMarkerInfo> infos);
public static List<LineMarkerInfo> merge(List<MergeableLineMarkerInfo> markers) {
final List<LineMarkerInfo> result = new ArrayList<LineMarkerInfo>();
final HashSet<LineMarkerInfo> processed = new HashSet<LineMarkerInfo>();
for (MergeableLineMarkerInfo marker : markers) {
if (processed.contains(marker)) continue;
processed.add(marker);
final List<MergeableLineMarkerInfo> toMerge = new ArrayList<MergeableLineMarkerInfo>();
for (MergeableLineMarkerInfo current : markers) {
if (!processed.contains(current) && marker.canMergeWith(current)) {
toMerge.add(current);
processed.add(current);
}
}
if (toMerge.isEmpty()) {
result.add(marker);
} else {
toMerge.add(marker);
result.add(new MyLineMarkerInfo(toMerge));
}
}
return result;
}
private static class MyLineMarkerInfo extends LineMarkerInfo {
public MyLineMarkerInfo(List<MergeableLineMarkerInfo> markers) {
//noinspection unchecked,ConstantConditions
super(markers.get(0).getElement(),
getCommonTextRange(markers),
markers.get(0).getCommonIcon(markers),
4, //TODO move Pass to lang-api and make it enum
FunctionUtil.<Object, String>nullConstant(),
getCommonNavigationHandler(markers),
GutterIconRenderer.Alignment.RIGHT);
}
private static TextRange getCommonTextRange(List<MergeableLineMarkerInfo> markers) {
int startOffset = Integer.MAX_VALUE;
int endOffset = Integer.MIN_VALUE;
for (MergeableLineMarkerInfo marker : markers) {
startOffset = Math.min(startOffset, marker.startOffset);
endOffset = Math.max(endOffset, marker.endOffset);
}
return TextRange.create(startOffset, endOffset);
}
private static GutterIconNavigationHandler getCommonNavigationHandler(final List<MergeableLineMarkerInfo> markers) {
return new GutterIconNavigationHandler() {
@Override
public void navigate(final MouseEvent e, PsiElement elt) {
final List<LineMarkerInfo> infos = new ArrayList<LineMarkerInfo>(markers);
Collections.sort(infos, new Comparator<LineMarkerInfo>() {
@Override
public int compare(LineMarkerInfo o1, LineMarkerInfo o2) {
return o1.startOffset - o2.startOffset;
}
});
final JBList list = new JBList(infos);
list.setFixedCellHeight(20);
list.installCellRenderer(new NotNullFunction<Object, JComponent>() {
@NotNull
@Override
public JComponent fun(Object dom) {
if (dom instanceof LineMarkerInfo) {
Icon icon = null;
final GutterIconRenderer renderer = ((LineMarkerInfo)dom).createGutterRenderer();
if (renderer != null) {
icon = renderer.getIcon();
}
final PsiElement element = ((LineMarkerInfo)dom).getElement();
assert element != null;
final String text = StringUtil.first(element.getText(), 100, true).replace('\n', ' ');
return new JBLabel(text, icon, SwingConstants.LEFT);
}
return new JBLabel();
}
});
JBPopupFactory.getInstance().createListPopupBuilder(list)
.setItemChoosenCallback(new Runnable() {
@Override
public void run() {
final Object value = list.getSelectedValue();
if (value instanceof LineMarkerInfo) {
final GutterIconNavigationHandler handler = ((LineMarkerInfo)value).getNavigationHandler();
if (handler != null) {
//noinspection unchecked
handler.navigate(e, ((LineMarkerInfo)value).getElement());
}
}
}
}).createPopup().show(new RelativePoint(e));
}
};
}
//private static int getCommonStartOffset(List<MergeableLineMarkerInfo> markers) {
// int startOffset = Integer.MAX_VALUE;
// for (MergeableLineMarkerInfo marker : markers) {
// startOffset = Math.min(startOffset, marker.startOffset);
// }
// return startOffset;
//}
//
private static PsiElement getCommonElement(List<MergeableLineMarkerInfo> markers) {
final List<PsiElement> elements = new ArrayList<PsiElement>();
for (MergeableLineMarkerInfo marker : markers) {
elements.add(marker.getElement());
}
final AccessToken token = ApplicationManager.getApplication().acquireReadActionLock();
try {
final PsiElement parent = PsiTreeUtil.findCommonParent(elements);
return parent == null ? elements.get(0) : parent;
}
finally {
token.finish();
}
}
}
}
@@ -23,6 +23,7 @@ import com.intellij.codeHighlighting.Pass;
import com.intellij.codeInsight.daemon.LineMarkerInfo;
import com.intellij.codeInsight.daemon.LineMarkerProvider;
import com.intellij.codeInsight.daemon.LineMarkerProviders;
import com.intellij.codeInsight.daemon.MergeableLineMarkerInfo;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightLevelUtil;
import com.intellij.injected.editor.DocumentWindow;
import com.intellij.lang.Language;
@@ -30,6 +31,8 @@ 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.LogicalPosition;
import com.intellij.openapi.editor.colors.CodeInsightColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
@@ -49,6 +52,7 @@ import com.intellij.util.Function;
import com.intellij.util.FunctionUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.*;
@@ -58,17 +62,20 @@ public class LineMarkersPass extends ProgressableTextEditorHighlightingPass impl
private volatile Collection<LineMarkerInfo> myMarkers = Collections.emptyList();
@Nullable private final Editor myEditor;
private final int myStartOffset;
private final int myEndOffset;
private final boolean myUpdateAll;
public LineMarkersPass(@NotNull Project project,
@NotNull PsiFile file,
@Nullable Editor editor,
@NotNull Document document,
int startOffset,
int endOffset,
boolean updateAll) {
super(project, document, GeneralHighlightingPass.PRESENTABLE_NAME, file, false);
myEditor = editor;
myStartOffset = startOffset;
myEndOffset = endOffset;
myUpdateAll = updateAll;
@@ -92,7 +99,17 @@ public class LineMarkersPass extends ProgressableTextEditorHighlightingPass impl
PsiElement psiRoot = viewProvider.getPsi(language);
if (!HighlightLevelUtil.shouldHighlight(psiRoot)) continue;
//long time = System.currentTimeMillis();
List<PsiElement> elements = CollectHighlightsUtil.getElementsInRange(psiRoot, myStartOffset, myEndOffset);
int start = myStartOffset;
int end = myEndOffset;
//if (myEditor != null) {
// final int startLine = myEditor.offsetToLogicalPosition(start).line;
// final int endLine = myEditor.offsetToLogicalPosition(end).line;
// if (startLine != endLine) {
// start = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(startLine, 0)));
// end = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(endLine + 1, 0))) - 1;
// }
//}
List<PsiElement> elements = CollectHighlightsUtil.getElementsInRange(psiRoot, start, end);
if (elements.isEmpty()) {
elements = Collections.singletonList(psiRoot);
}
@@ -101,7 +118,40 @@ public class LineMarkersPass extends ProgressableTextEditorHighlightingPass impl
collectLineMarkersForInjected(lineMarkers, elements, this, myFile, progress);
}
myMarkers = lineMarkers;
myMarkers = mergeLineMarkers(lineMarkers);
}
private List<LineMarkerInfo> mergeLineMarkers(List<LineMarkerInfo> markers) {
List<MergeableLineMarkerInfo> forMerge = new ArrayList<MergeableLineMarkerInfo>();
final Iterator<LineMarkerInfo> iterator = markers.iterator();
while (iterator.hasNext()) {
final LineMarkerInfo marker = iterator.next();
if (marker instanceof MergeableLineMarkerInfo) {
iterator.remove();
forMerge.add((MergeableLineMarkerInfo)marker);
}
}
if (forMerge.isEmpty() || myEditor == null) return markers;
final List<LineMarkerInfo> result = new ArrayList<LineMarkerInfo>(markers);
final HashMap<Integer, List<MergeableLineMarkerInfo>> map = new HashMap<Integer, List<MergeableLineMarkerInfo>>();
for (MergeableLineMarkerInfo info : forMerge) {
final LogicalPosition position = myEditor.offsetToLogicalPosition(info.startOffset);
List<MergeableLineMarkerInfo> infos = map.get(position.line);
if (infos == null) {
infos = new ArrayList<MergeableLineMarkerInfo>();
map.put(position.line, infos);
}
infos.add(info);
}
for (List<MergeableLineMarkerInfo> infos : map.values()) {
result.addAll(MergeableLineMarkerInfo.merge(infos));
}
return result;
}
public static List<LineMarkerProvider> getMarkerProviders(Language language, Project project) {
@@ -50,7 +50,7 @@ public class LineMarkersPassFactory extends AbstractProjectComponent implements
public TextEditorHighlightingPass createHighlightingPass(@NotNull PsiFile file, @NotNull final Editor editor) {
TextRange textRange = calculateRangeToProcessForSyntaxPass(editor);
if (textRange == null) return new ProgressableTextEditorHighlightingPass.EmptyPass(myProject, editor.getDocument());
return new LineMarkersPass(myProject, file, editor.getDocument(), textRange.getStartOffset(), textRange.getEndOffset(), true);
return new LineMarkersPass(myProject, file, editor, editor.getDocument(), textRange.getStartOffset(), textRange.getEndOffset(), true);
}
@Nullable
@@ -50,6 +50,6 @@ public class VisibleLineMarkersPassFactory extends VisibleHighlightingPassFactor
TextRange textRange = calculateRangeToProcess(editor);
if (textRange == null) return null;
return new LineMarkersPass(file.getProject(), file, editor.getDocument(), textRange.getStartOffset(), textRange.getEndOffset(), false);
return new LineMarkersPass(file.getProject(), file, editor, editor.getDocument(), textRange.getStartOffset(), textRange.getEndOffset(), false);
}
}