js repl: draw line separators using range marker highlighter

This commit is contained in:
Vladimir Krivosheev
2014-02-19 12:56:13 +01:00
parent e66c8055f4
commit bfdc9c9c2e
3 changed files with 302 additions and 81 deletions
@@ -41,6 +41,8 @@ class ConsoleGutterComponent extends JComponent implements MouseMotionListener {
addListeners();
addMouseMotionListener(this);
setOpaque(true);
}
private void addListeners() {
@@ -68,6 +70,8 @@ class ConsoleGutterComponent extends JComponent implements MouseMotionListener {
}
private void computeMaxAnnotationWidth() {
gutterContentProvider.beforeUiComponentUpdate(editor);
if (!gutterContentProvider.hasText()) {
maxAnnotationWidth = 0;
return;
@@ -75,7 +79,6 @@ class ConsoleGutterComponent extends JComponent implements MouseMotionListener {
FontMetrics fontMetrics = editor.getFontMetrics(Font.PLAIN);
int lineCount = editor.getDocument().getLineCount();
gutterContentProvider.beforeUiComponentUpdate(editor);
int gutterSize = 0;
for (int i = 0; i < lineCount; i++) {
String text = gutterContentProvider.getText(i, editor);
@@ -101,9 +104,6 @@ class ConsoleGutterComponent extends JComponent implements MouseMotionListener {
return;
}
g.setColor(editor.getBackgroundColor());
g.fillRect(clip.x, clip.y, clip.width, clip.height);
UISettings.setupAntialiasing(g);
Graphics2D g2 = (Graphics2D)g;
@@ -27,4 +27,8 @@ public abstract class GutterContentProvider {
public abstract void doAction(int line, @NotNull Editor editor);
public abstract void drawIcon(int line, @NotNull Graphics g, int y, @NotNull Editor editor);
public boolean isShowSeparatorLine(int line, @NotNull Editor editor) {
return true;
}
}
@@ -4,18 +4,26 @@ import com.intellij.execution.process.ProcessHandler;
import com.intellij.lang.Language;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.VisualPosition;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.event.DocumentAdapter;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.ex.DocumentBulkUpdateListener;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.RangeHighlighterEx;
import com.intellij.openapi.editor.impl.EditorComponentImpl;
import com.intellij.openapi.editor.impl.EditorImpl;
import com.intellij.openapi.editor.impl.RangeMarkerImpl;
import com.intellij.openapi.editor.markup.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Conditions;
import com.intellij.openapi.util.Getter;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.components.JBLayeredPane;
import com.intellij.util.Consumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -112,40 +120,6 @@ public class LanguageConsoleBuilder {
}
}
private static class GutteredEditorPanel extends JPanel {
private final EditorEx editor;
public GutteredEditorPanel(EditorEx editor) {
super(new BorderLayout());
this.editor = editor;
}
@Override
public void paint(Graphics g) {
super.paint(g);
Rectangle clip = g.getClipBounds();
if (clip.height < 0) {
return;
}
int lineHeight = editor.getLineHeight();
int startLine = clip.y / lineHeight;
int endLine = Math.min(((clip.y + clip.height) / lineHeight) + 1, ((EditorImpl)editor).getVisibleLineCount());
if (startLine >= endLine) {
return;
}
int y = ((startLine + 1) * lineHeight);
g.setColor(editor.getColorsScheme().getColor(EditorColors.INDENT_GUIDE_COLOR));
for (int i = startLine; i < endLine; i++) {
g.drawLine(ConsoleIconGutterComponent.ICON_AREA_WIDTH, y, clip.width, y);
y += lineHeight;
}
}
}
private static class MyLanguageConsole extends LanguageConsoleImpl {
@Nullable
private GutterContentProvider gutterContentProvider;
@@ -155,7 +129,7 @@ public class LanguageConsoleBuilder {
}
@Override
protected void setupEditorDefault(@NotNull EditorEx editor) {
protected void setupEditorDefault(@NotNull final EditorEx editor) {
super.setupEditorDefault(editor);
if (editor == getConsoleEditor()) {
@@ -163,52 +137,53 @@ public class LanguageConsoleBuilder {
editor.getSettings().setAdditionalLinesCount(1);
}
else if (gutterContentProvider != null) {
JScrollPane scrollPane = editor.getScrollPane();
JPanel panel = new GutteredEditorPanel(editor);
final ConsoleIconGutterComponent lineStartGutter = new ConsoleIconGutterComponent(editor, gutterContentProvider);
panel.add(lineStartGutter, BorderLayout.LINE_START);
panel.add(scrollPane.getViewport().getView(), BorderLayout.CENTER);
final ConsoleGutterComponent lineEndGutter = new ConsoleGutterComponent(editor, gutterContentProvider);
panel.add(lineEndGutter, BorderLayout.LINE_END);
scrollPane.setViewportView(panel);
getProject().getMessageBus().connect(this).subscribe(DocumentBulkUpdateListener.TOPIC, new DocumentBulkUpdateListener.Adapter() {
JLayeredPane layeredPane = new JBLayeredPane() {
@Override
public void updateFinished(@NotNull Document document) {
if (document.getTextLength() == 0) {
gutterContentProvider.documentCleared(getHistoryViewer());
}
lineStartGutter.updateSize();
lineEndGutter.updateSize();
}
});
editor.getDocument().addDocumentListener(new DocumentAdapter() {
@Override
public void documentChanged(DocumentEvent event) {
EditorEx editor = getHistoryViewer();
DocumentEx document = editor.getDocument();
if (document.isInBulkUpdate()) {
return;
}
if (document.getTextLength() > 0) {
int startDocLine = document.getLineNumber(event.getOffset());
int endDocLine = document.getLineNumber(event.getOffset() + event.getNewLength());
if (event.getOldLength() > event.getNewLength() || startDocLine != endDocLine || StringUtil.indexOf(event.getOldFragment(), '\n') != -1) {
lineStartGutter.updateSize();
lineEndGutter.updateSize();
public void doLayout() {
EditorComponentImpl editor = null;
Component lineStartGutter = null;
Component lineEndGutter = null;
for (int i = getComponentCount() - 1; i >= 0; i--) {
Component component = getComponent(i);
if (component instanceof EditorComponentImpl) {
editor = (EditorComponentImpl)component;
}
else if (getLayer(component) == JLayeredPane.DEFAULT_LAYER) {
lineStartGutter = component;
}
else {
lineEndGutter = component;
}
}
else if (event.getOldLength() > 0) {
gutterContentProvider.documentCleared(editor);
}
assert editor != null && lineStartGutter != null && lineEndGutter != null;
int w = getWidth();
int h = getHeight();
Dimension lineStartGutterDimension = lineStartGutter.getPreferredSize();
lineStartGutter.setBounds(0, 0, lineStartGutterDimension.width, h);
editor.setBounds(lineStartGutterDimension.width, 0, w - lineStartGutterDimension.width, h);
Dimension lineEndGutterDimension = lineEndGutter.getPreferredSize();
lineEndGutter.setBounds(w - lineEndGutterDimension.width - editor.getEditor().getScrollPane().getVerticalScrollBar().getWidth(), 0, lineEndGutterDimension.width, h);
}
});
};
ConsoleIconGutterComponent lineStartGutter = new ConsoleIconGutterComponent(editor, gutterContentProvider);
layeredPane.add(lineStartGutter, JLayeredPane.DEFAULT_LAYER);
JScrollPane scrollPane = editor.getScrollPane();
layeredPane.add(scrollPane.getViewport().getView(), JLayeredPane.DEFAULT_LAYER);
ConsoleGutterComponent lineEndGutter = new ConsoleGutterComponent(editor, gutterContentProvider);
layeredPane.add(lineEndGutter, JLayeredPane.PALETTE_LAYER);
scrollPane.setViewportView(layeredPane);
GutterUpdateScheduler gutterUpdateScheduler = new GutterUpdateScheduler(lineStartGutter, lineEndGutter);
getProject().getMessageBus().connect(this).subscribe(DocumentBulkUpdateListener.TOPIC, gutterUpdateScheduler);
editor.getDocument().addDocumentListener(gutterUpdateScheduler);
}
}
@@ -221,5 +196,247 @@ public class LanguageConsoleBuilder {
gutterContentProvider.beforeEvaluate(getHistoryViewer());
}
}
private class GutterUpdateScheduler extends DocumentAdapter implements DocumentBulkUpdateListener {
private final ConsoleIconGutterComponent lineStartGutter;
private final ConsoleGutterComponent lineEndGutter;
private boolean lineSeparatorPainterAdded;
public GutterUpdateScheduler(@NotNull ConsoleIconGutterComponent lineStartGutter, @NotNull ConsoleGutterComponent lineEndGutter) {
this.lineStartGutter = lineStartGutter;
this.lineEndGutter = lineEndGutter;
}
private void addLineSeparatorPainterIfNeed() {
if (lineSeparatorPainterAdded) {
return;
}
lineSeparatorPainterAdded = true;
EditorEx editor = getHistoryViewer();
editor.getMarkupModel().addRangeHighlighter(new MyRangeMarkerImpl(editor), 0, getDocument().getTextLength(), false, false, HighlighterLayer.ADDITIONAL_SYNTAX);
}
private DocumentEx getDocument() {
return getHistoryViewer().getDocument();
}
@Override
public void documentChanged(DocumentEvent event) {
DocumentEx document = getDocument();
if (document.isInBulkUpdate()) {
return;
}
if (document.getTextLength() > 0) {
addLineSeparatorPainterIfNeed();
int startDocLine = document.getLineNumber(event.getOffset());
int endDocLine = document.getLineNumber(event.getOffset() + event.getNewLength());
if (event.getOldLength() > event.getNewLength() || startDocLine != endDocLine || StringUtil.indexOf(event.getOldFragment(), '\n') != -1) {
lineStartGutter.updateSize();
lineEndGutter.updateSize();
}
}
else if (event.getOldLength() > 0) {
assert gutterContentProvider != null;
gutterContentProvider.documentCleared(getHistoryViewer());
}
}
@Override
public void updateStarted(@NotNull Document doc) {
}
@Override
public void updateFinished(@NotNull Document doc) {
if (getDocument().getTextLength() == 0) {
assert gutterContentProvider != null;
gutterContentProvider.documentCleared(getHistoryViewer());
}
else {
addLineSeparatorPainterIfNeed();
}
lineStartGutter.updateSize();
lineEndGutter.updateSize();
}
}
private class MyRangeMarkerImpl extends RangeMarkerImpl implements RangeHighlighterEx, Getter<RangeHighlighterEx> {
private final CustomHighlighterRenderer renderer = new CustomHighlighterRenderer() {
@Override
public void paint(@NotNull Editor editor, @NotNull RangeHighlighter highlighter, @NotNull Graphics g) {
Rectangle clip = g.getClipBounds();
if (clip.height < 0) {
return;
}
int lineHeight = editor.getLineHeight();
int startLine = clip.y / lineHeight;
int endLine = Math.min(((clip.y + clip.height) / lineHeight) + 1, ((EditorImpl)editor).getVisibleLineCount());
if (startLine >= endLine) {
return;
}
int y = ((startLine + 1) * lineHeight);
g.setColor(editor.getColorsScheme().getColor(EditorColors.INDENT_GUIDE_COLOR));
assert gutterContentProvider != null;
for (int i = startLine; i < endLine; i++) {
if (gutterContentProvider.isShowSeparatorLine(editor.visualToLogicalPosition(new VisualPosition(i, 0)).line, editor)) {
g.drawLine(0, y, clip.width, y);
}
y += lineHeight;
}
}
};
public MyRangeMarkerImpl(@NotNull EditorEx editor) {
super(editor.getDocument(), 0, 1, false);
}
@Override
protected void changedUpdateImpl(DocumentEvent e) {
setIntervalEnd(myDocument.getTextLength());
}
@Override
public boolean isAfterEndOfLine() {
return false;
}
@Override
public void setAfterEndOfLine(boolean value) {
}
@Override
public int getAffectedAreaStartOffset() {
return 0;
}
@Override
public int getAffectedAreaEndOffset() {
return myDocument.getTextLength();
}
@Override
public void setTextAttributes(@NotNull TextAttributes textAttributes) {
}
@NotNull
@Override
public HighlighterTargetArea getTargetArea() {
return HighlighterTargetArea.EXACT_RANGE;
}
@Nullable
@Override
public TextAttributes getTextAttributes() {
return null;
}
@Nullable
@Override
public LineMarkerRenderer getLineMarkerRenderer() {
return null;
}
@Override
public void setLineMarkerRenderer(@Nullable LineMarkerRenderer renderer) {
}
@Nullable
@Override
public CustomHighlighterRenderer getCustomRenderer() {
return renderer;
}
@Override
public void setCustomRenderer(CustomHighlighterRenderer renderer) {
}
@Nullable
@Override
public GutterIconRenderer getGutterIconRenderer() {
return null;
}
@Override
public void setGutterIconRenderer(@Nullable GutterIconRenderer renderer) {
}
@Nullable
@Override
public Color getErrorStripeMarkColor() {
return null;
}
@Override
public void setErrorStripeMarkColor(@Nullable Color color) {
}
@Nullable
@Override
public Object getErrorStripeTooltip() {
return null;
}
@Override
public void setErrorStripeTooltip(@Nullable Object tooltipObject) {
}
@Override
public boolean isThinErrorStripeMark() {
return false;
}
@Override
public void setThinErrorStripeMark(boolean value) {
}
@Nullable
@Override
public Color getLineSeparatorColor() {
return null;
}
@Override
public void setLineSeparatorColor(@Nullable Color color) {
}
@Override
public void setLineSeparatorRenderer(LineSeparatorRenderer renderer) {
}
@Override
public LineSeparatorRenderer getLineSeparatorRenderer() {
return null;
}
@Nullable
@Override
public SeparatorPlacement getLineSeparatorPlacement() {
return null;
}
@Override
public void setLineSeparatorPlacement(@Nullable SeparatorPlacement placement) {
}
@Override
public void setEditorFilter(@NotNull MarkupEditorFilter filter) {
}
@NotNull
@Override
public MarkupEditorFilter getEditorFilter() {
return MarkupEditorFilter.EMPTY;
}
@Override
public RangeHighlighterEx get() {
return this;
}
}
}
}