blob: 6463a7a6e746379ecc48845f18dcc54f78bfedc7 [file] [log] [blame]
/*
* Copyright (c) 2000-2006 JetBrains s.r.o. All Rights Reserved.
*/
package com.intellij.coverage;
import com.intellij.application.options.colors.ColorAndFontOptions;
import com.intellij.application.options.colors.ColorAndFontPanelFactory;
import com.intellij.application.options.colors.NewColorAndFontPanel;
import com.intellij.application.options.colors.SimpleEditorPreview;
import com.intellij.codeInsight.hint.EditorFragmentComponent;
import com.intellij.codeInsight.hint.HintManagerImpl;
import com.intellij.coverage.actions.HideCoverageInfoAction;
import com.intellij.coverage.actions.ShowCoveringTestsAction;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.editor.colors.CodeInsightColors;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
import com.intellij.openapi.editor.impl.EditorImpl;
import com.intellij.openapi.editor.markup.ActiveGutterRenderer;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.options.SearchableConfigurable;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.options.colors.pages.GeneralColorsPage;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.rt.coverage.data.LineCoverage;
import com.intellij.rt.coverage.data.LineData;
import com.intellij.ui.ColorUtil;
import com.intellij.ui.ColoredSideBorder;
import com.intellij.ui.HintHint;
import com.intellij.ui.LightweightHint;
import com.intellij.util.Function;
import com.intellij.util.ImageLoader;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.TreeMap;
/**
* @author ven
*/
public class CoverageLineMarkerRenderer implements ActiveGutterRenderer {
private static final int THICKNESS = 8;
private final TextAttributesKey myKey;
private final String myClassName;
private final TreeMap<Integer, LineData> myLines;
private final boolean myCoverageByTestApplicable;
private final Function<Integer, Integer> myNewToOldConverter;
private final Function<Integer, Integer> myOldToNewConverter;
private final CoverageSuitesBundle myCoverageSuite;
private final boolean mySubCoverageActive;
protected CoverageLineMarkerRenderer(final TextAttributesKey textAttributesKey, @Nullable final String className, final TreeMap<Integer, LineData> lines,
final boolean coverageByTestApplicable,
final Function<Integer, Integer> newToOldConverter,
final Function<Integer, Integer> oldToNewConverter,
final CoverageSuitesBundle coverageSuite, boolean subCoverageActive) {
myKey = textAttributesKey;
myClassName = className;
myLines = lines;
myCoverageByTestApplicable = coverageByTestApplicable;
myNewToOldConverter = newToOldConverter;
myOldToNewConverter = oldToNewConverter;
myCoverageSuite = coverageSuite;
mySubCoverageActive = subCoverageActive;
}
public void paint(Editor editor, Graphics g, Rectangle r) {
final TextAttributes color = editor.getColorsScheme().getAttributes(myKey);
Color bgColor = color.getBackgroundColor();
if (bgColor == null) {
bgColor = color.getForegroundColor();
}
if (editor.getSettings().isLineNumbersShown() || ((EditorGutterComponentEx)editor.getGutter()).isAnnotationsShown()) {
if (bgColor != null) {
bgColor = ColorUtil.toAlpha(bgColor, 150);
}
}
if (bgColor != null) {
g.setColor(bgColor);
}
g.fillRect(0, r.y, THICKNESS, r.height);
final LineData lineData = getLineData(editor.xyToLogicalPosition(new Point(0, r.y)).line);
if (lineData != null && lineData.isCoveredByOneTest()) {
g.drawImage( ImageLoader.loadFromResource("/gutter/unique.png"), 0, r.y, 8, 8, editor.getComponent());
}
}
public static CoverageLineMarkerRenderer getRenderer(int lineNumber,
@Nullable final String className,
final TreeMap<Integer, LineData> lines,
final boolean coverageByTestApplicable,
@NotNull final CoverageSuitesBundle coverageSuite,
final Function<Integer, Integer> newToOldConverter,
final Function<Integer, Integer> oldToNewConverter, boolean subCoverageActive) {
return new CoverageLineMarkerRenderer(getAttributesKey(lineNumber, lines), className, lines, coverageByTestApplicable, newToOldConverter,
oldToNewConverter, coverageSuite, subCoverageActive);
}
public static TextAttributesKey getAttributesKey(final int lineNumber,
final TreeMap<Integer, LineData> lines) {
return getAttributesKey(lines.get(lineNumber));
}
private static TextAttributesKey getAttributesKey(LineData lineData) {
if (lineData != null) {
switch (lineData.getStatus()) {
case LineCoverage.FULL:
return CodeInsightColors.LINE_FULL_COVERAGE;
case LineCoverage.PARTIAL:
return CodeInsightColors.LINE_PARTIAL_COVERAGE;
}
}
return CodeInsightColors.LINE_NONE_COVERAGE;
}
public boolean canDoAction(final MouseEvent e) {
return e.getX() < THICKNESS;
}
public void doAction(final Editor editor, final MouseEvent e) {
e.consume();
final JComponent comp = (JComponent)e.getComponent();
final JRootPane rootPane = comp.getRootPane();
final JLayeredPane layeredPane = rootPane.getLayeredPane();
final Point point = SwingUtilities.convertPoint(comp, THICKNESS, e.getY(), layeredPane);
showHint(editor, point, editor.xyToLogicalPosition(e.getPoint()).line);
}
private void showHint(final Editor editor, final Point point, final int lineNumber) {
final JPanel panel = new JPanel(new BorderLayout());
panel.add(createActionsToolbar(editor, lineNumber), BorderLayout.NORTH);
final LineData lineData = getLineData(lineNumber);
final EditorImpl uEditor;
if (lineData != null && lineData.getStatus() != LineCoverage.NONE && !mySubCoverageActive) {
final EditorFactory factory = EditorFactory.getInstance();
final Document doc = factory.createDocument(getReport(editor, lineNumber));
doc.setReadOnly(true);
uEditor = (EditorImpl)factory.createEditor(doc, editor.getProject());
panel.add(EditorFragmentComponent.createEditorFragmentComponent(uEditor, 0, doc.getLineCount(), false, false), BorderLayout.CENTER);
} else {
uEditor = null;
}
final LightweightHint hint = new LightweightHint(panel){
@Override
public void hide() {
if (uEditor != null) EditorFactory.getInstance().releaseEditor(uEditor);
super.hide();
}
};
HintManagerImpl.getInstanceImpl().showEditorHint(hint, editor, point,
HintManagerImpl.HIDE_BY_ANY_KEY | HintManagerImpl.HIDE_BY_TEXT_CHANGE | HintManagerImpl.HIDE_BY_OTHER_HINT | HintManagerImpl.HIDE_BY_SCROLLING, -1, false, new HintHint(editor, point));
}
private String getReport(final Editor editor, final int lineNumber) {
final LineData lineData = getLineData(lineNumber);
final Document document = editor.getDocument();
final Project project = editor.getProject();
assert project != null;
final PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document);
assert psiFile != null;
final int lineStartOffset = document.getLineStartOffset(lineNumber);
final int lineEndOffset = document.getLineEndOffset(lineNumber);
return myCoverageSuite.getCoverageEngine().generateBriefReport(editor, psiFile, lineNumber, lineStartOffset, lineEndOffset, lineData);
}
protected JComponent createActionsToolbar(final Editor editor, final int lineNumber) {
final JComponent editorComponent = editor.getComponent();
final DefaultActionGroup group = new DefaultActionGroup();
final GotoPreviousCoveredLineAction prevAction = new GotoPreviousCoveredLineAction(editor, lineNumber);
final GotoNextCoveredLineAction nextAction = new GotoNextCoveredLineAction(editor, lineNumber);
group.add(prevAction);
group.add(nextAction);
prevAction.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.ALT_MASK|InputEvent.SHIFT_MASK)), editorComponent);
nextAction.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.ALT_MASK|InputEvent.SHIFT_MASK)), editorComponent);
final LineData lineData = getLineData(lineNumber);
if (myCoverageByTestApplicable) {
group.add(new ShowCoveringTestsAction(myClassName, lineData));
}
final AnAction byteCodeViewAction = ActionManager.getInstance().getAction("ByteCodeViewer");
if (byteCodeViewAction != null) {
group.add(byteCodeViewAction);
}
group.add(new EditCoverageColorsAction(editor, lineNumber));
group.add(new HideCoverageInfoAction());
final JComponent toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.FILEHISTORY_VIEW_TOOLBAR, group, true).getComponent();
final Color background = ((EditorEx)editor).getBackgroundColor();
final Color foreground = editor.getColorsScheme().getColor(EditorColors.CARET_COLOR);
toolbar.setBackground(background);
toolbar.setBorder(new ColoredSideBorder(foreground, foreground, lineData == null || lineData.getStatus() == LineCoverage.NONE || mySubCoverageActive ? foreground : null, foreground, 1));
return toolbar;
}
public void moveToLine(final int lineNumber, final Editor editor) {
final int firstOffset = editor.getDocument().getLineStartOffset(lineNumber);
editor.getCaretModel().moveToOffset(firstOffset);
editor.getScrollingModel().scrollToCaret(ScrollType.CENTER);
editor.getScrollingModel().runActionOnScrollingFinished(new Runnable() {
public void run() {
Point p = editor.visualPositionToXY(editor.offsetToVisualPosition(firstOffset));
EditorGutterComponentEx editorComponent = (EditorGutterComponentEx)editor.getGutter();
JLayeredPane layeredPane = editorComponent.getRootPane().getLayeredPane();
p = SwingUtilities.convertPoint(editorComponent, THICKNESS, p.y, layeredPane);
showHint(editor, p, lineNumber);
}
});
}
@Nullable
public LineData getLineData(int lineNumber) {
return myLines != null ? myLines.get(myNewToOldConverter != null ? myNewToOldConverter.fun(lineNumber).intValue() : lineNumber) : null;
}
public Color getErrorStripeColor(final Editor editor) {
return editor.getColorsScheme().getAttributes(myKey).getErrorStripeColor();
}
private class GotoPreviousCoveredLineAction extends BaseGotoCoveredLineAction {
public GotoPreviousCoveredLineAction(final Editor editor, final int lineNumber) {
super(editor, lineNumber);
copyFrom(ActionManager.getInstance().getAction(IdeActions.ACTION_PREVIOUS_OCCURENCE));
getTemplatePresentation().setText("Previous Coverage Mark");
}
protected boolean hasNext(final int idx, final List<Integer> list) {
return idx > 0;
}
protected int next(final int idx) {
return idx - 1;
}
@Override
public void update(AnActionEvent e) {
super.update(e);
final String nextChange = getNextChange();
if (nextChange != null) {
e.getPresentation().setText("Previous " + nextChange);
}
}
}
private class GotoNextCoveredLineAction extends BaseGotoCoveredLineAction {
public GotoNextCoveredLineAction(final Editor editor, final int lineNumber) {
super(editor, lineNumber);
copyFrom(ActionManager.getInstance().getAction(IdeActions.ACTION_NEXT_OCCURENCE));
getTemplatePresentation().setText("Next Coverage Mark");
}
protected boolean hasNext(final int idx, final List<Integer> list) {
return idx < list.size() - 1;
}
protected int next(final int idx) {
return idx + 1;
}
@Override
public void update(AnActionEvent e) {
super.update(e);
final String nextChange = getNextChange();
if (nextChange != null) {
e.getPresentation().setText("Next " + nextChange);
}
}
}
private abstract class BaseGotoCoveredLineAction extends AnAction {
private final Editor myEditor;
private final int myLineNumber;
public BaseGotoCoveredLineAction(final Editor editor, final int lineNumber) {
myEditor = editor;
myLineNumber = lineNumber;
}
public void actionPerformed(final AnActionEvent e) {
final Integer lineNumber = getLineEntry();
if (lineNumber != null) {
moveToLine(lineNumber.intValue(), myEditor);
}
}
protected abstract boolean hasNext(int idx, List<Integer> list);
protected abstract int next(int idx);
@Nullable
private Integer getLineEntry() {
final ArrayList<Integer> list = new ArrayList<Integer>(myLines.keySet());
Collections.sort(list);
final LineData data = getLineData(myLineNumber);
final int currentStatus = data != null ? data.getStatus() : LineCoverage.NONE;
int idx = list.indexOf(myNewToOldConverter != null ? myNewToOldConverter.fun(myLineNumber).intValue() : myLineNumber);
while (hasNext(idx, list)) {
final int index = next(idx);
final LineData lineData = myLines.get(list.get(index));
idx = index;
if (lineData != null && lineData.getStatus() != currentStatus) {
final Integer line = list.get(idx);
if (myOldToNewConverter != null) {
final int newLine = myOldToNewConverter.fun(line).intValue();
if (newLine != 0) return newLine;
} else {
return line;
}
}
}
return null;
}
@Nullable
protected String getNextChange() {
Integer entry = getLineEntry();
if (entry != null) {
final LineData lineData = getLineData(entry);
if (lineData != null) {
switch (lineData.getStatus()) {
case LineCoverage.NONE:
return "Uncovered";
case LineCoverage.PARTIAL:
return "Partial Covered";
case LineCoverage.FULL:
return "Fully Covered";
}
}
}
return null;
}
@Override
public void update(final AnActionEvent e) {
e.getPresentation().setEnabled(getLineEntry() != null);
}
}
private class EditCoverageColorsAction extends AnAction {
private final Editor myEditor;
private final int myLineNumber;
private EditCoverageColorsAction(Editor editor, int lineNumber) {
super("Edit coverage colors", "Edit coverage colors", AllIcons.General.EditColors);
myEditor = editor;
myLineNumber = lineNumber;
}
@Override
public void update(AnActionEvent e) {
e.getPresentation().setVisible(getLineData(myLineNumber) != null);
}
@Override
public void actionPerformed(AnActionEvent e) {
final ColorAndFontOptions colorAndFontOptions = new ColorAndFontOptions(){
@Override
protected List<ColorAndFontPanelFactory> createPanelFactories() {
final GeneralColorsPage colorsPage = new GeneralColorsPage();
final ColorAndFontPanelFactory panelFactory = new ColorAndFontPanelFactory() {
@NotNull
@Override
public NewColorAndFontPanel createPanel(@NotNull ColorAndFontOptions options) {
final SimpleEditorPreview preview = new SimpleEditorPreview(options, colorsPage);
return NewColorAndFontPanel.create(preview, colorsPage.getDisplayName(), options, null, colorsPage);
}
@NotNull
@Override
public String getPanelDisplayName() {
return "Editor | " + getDisplayName() + " | " + colorsPage.getDisplayName();
}
};
return Collections.singletonList(panelFactory);
}
};
final Configurable[] configurables = colorAndFontOptions.buildConfigurables();
try {
final SearchableConfigurable general = colorAndFontOptions.findSubConfigurable(GeneralColorsPage.class);
if (general != null) {
final LineData lineData = getLineData(myLineNumber);
ShowSettingsUtil.getInstance().editConfigurable(myEditor.getProject(), general,
general.enableSearch(getAttributesKey(lineData).getExternalName()));
}
}
finally {
for (Configurable configurable : configurables) {
configurable.disposeUIResources();
}
colorAndFontOptions.disposeUIResources();
}
}
}
}