IDEA-53596 Soft wrap for editors

1. Added ability to provide graphical indication of soft wrap start and end;
2. Added ability to customise if soft wrap start and end should be graphically animated for non-active lines;
3. Only single active visual row is highlighed now (used to highlight whole logical row);
This commit is contained in:
Denis Zhdanov
2010-07-02 18:29:16 +04:00
parent 7c7dced2f5
commit a1627fc97c
23 changed files with 848 additions and 70 deletions
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.application.options.editor.EditorAppearanceConfigurable">
<grid id="27dc6" binding="myRootPanel" layout-manager="GridLayoutManager" row-count="11" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="0">
<grid id="27dc6" binding="myRootPanel" layout-manager="GridLayoutManager" row-count="12" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="0">
<margin top="4" left="4" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="500" height="400"/>
@@ -10,7 +10,7 @@
<children>
<vspacer id="f3f11">
<constraints>
<grid row="10" column="0" row-span="1" col-span="2" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
<grid row="11" column="0" row-span="1" col-span="2" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<component id="ef15e" class="javax.swing.JCheckBox" binding="myCbBlinkCaret">
@@ -57,7 +57,7 @@
</component>
<component id="d097a" class="javax.swing.JCheckBox" binding="myShowVerticalIndentGuidesCheckBox" default-binding="true">
<constraints>
<grid row="7" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<grid row="8" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Show vertical indent guides"/>
@@ -76,7 +76,7 @@
</component>
<grid id="7b00c" binding="myAddonPanel" layout-manager="GridBagLayout">
<constraints>
<grid row="9" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="8" fill="2" indent="0" use-parent-layout="false"/>
<grid row="10" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="8" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
@@ -100,12 +100,20 @@
</component>
<component id="f1b45" class="javax.swing.JCheckBox" binding="myCbShowIconsInGutter">
<constraints>
<grid row="8" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<grid row="9" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="messages/ApplicationBundle" key="checkbox.show.small.icons.in.gutter"/>
</properties>
</component>
<component id="98de9" class="javax.swing.JCheckBox" binding="myCbShowSoftwraps">
<constraints>
<grid row="7" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="messages/ApplicationBundle" key="checkbox.show.softwraps"/>
</properties>
</component>
</children>
</grid>
</form>
@@ -48,6 +48,7 @@ public class EditorAppearanceConfigurable extends CompositeConfigurable<UnnamedC
private JCheckBox myCbRightMargin;
private JCheckBox myCbShowLineNumbers;
private JCheckBox myCbShowWhitespaces;
private JCheckBox myCbShowSoftwraps;
private JTextField myBlinkIntervalField;
private JPanel myAddonPanel;
private JCheckBox myCbShowMethodSeparators;
@@ -82,6 +83,7 @@ public class EditorAppearanceConfigurable extends CompositeConfigurable<UnnamedC
myCbShowLineNumbers.setSelected(editorSettings.isLineNumbersShown());
myCbBlockCursor.setSelected(editorSettings.isBlockCursor());
myCbShowWhitespaces.setSelected(editorSettings.isWhitespacesShown());
myCbShowSoftwraps.setSelected(editorSettings.isSoftWrapsShown());
myShowVerticalIndentGuidesCheckBox.setSelected(editorSettings.isIndentGuidesShown());
myAntialiasingInEditorCheckBox.setSelected(UISettings.getInstance().ANTIALIASING_IN_EDITOR);
myCbShowIconsInGutter.setSelected(DaemonCodeAnalyzerSettings.getInstance().SHOW_SMALL_ICONS_IN_GUTTER);
@@ -103,6 +105,7 @@ public class EditorAppearanceConfigurable extends CompositeConfigurable<UnnamedC
editorSettings.setRightMarginShown(myCbRightMargin.isSelected());
editorSettings.setLineNumbersShown(myCbShowLineNumbers.isSelected());
editorSettings.setWhitespacesShown(myCbShowWhitespaces.isSelected());
editorSettings.setSoftwrapsShown(myCbShowSoftwraps.isSelected());
editorSettings.setIndentGuidesShown(myShowVerticalIndentGuidesCheckBox.isSelected());
EditorOptionsPanel.reinitAllEditors();
@@ -135,6 +138,7 @@ public class EditorAppearanceConfigurable extends CompositeConfigurable<UnnamedC
isModified |= isModified(myCbShowLineNumbers, editorSettings.isLineNumbersShown());
isModified |= isModified(myCbShowWhitespaces, editorSettings.isWhitespacesShown());
isModified |= isModified(myCbShowSoftwraps, editorSettings.isSoftWrapsShown());
isModified |= isModified(myShowVerticalIndentGuidesCheckBox, editorSettings.isIndentGuidesShown());
isModified |= isModified(myCbShowMethodSeparators, DaemonCodeAnalyzerSettings.getInstance().SHOW_METHOD_SEPARATORS);
isModified |= isModified(myCbShowIconsInGutter, DaemonCodeAnalyzerSettings.getInstance().SHOW_SMALL_ICONS_IN_GUTTER);
@@ -33,6 +33,7 @@ import com.intellij.openapi.editor.ex.*;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.highlighter.EditorHighlighter;
import com.intellij.openapi.editor.impl.EditorImpl;
import com.intellij.openapi.editor.impl.TextDrawingCallback;
import com.intellij.openapi.editor.markup.MarkupModel;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
@@ -183,6 +184,11 @@ public class EditorWindow implements EditorEx, UserDataHolderEx {
return null;
}
@Override
public TextDrawingCallback getTextDrawingCallback() {
return myDelegate.getTextDrawingCallback();
}
@NotNull
public SelectionModel getSelectionModel() {
return mySelectionModelDelegate;
@@ -102,7 +102,7 @@ public interface CaretModel {
int getVisualLineStart();
/**
* @return document offset for the end of the logical line where caret is located
* @return document offset that points to the first symbol shown at the next visual line after the one with caret on it
*/
int getVisualLineEnd();
@@ -100,4 +100,7 @@ public interface EditorSettings {
boolean isUseSoftWraps();
void setUseSoftWraps(boolean use);
boolean isSoftWrapsShown();
void setShowSoftWraps(boolean show);
}
@@ -23,6 +23,7 @@ import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.highlighter.EditorHighlighter;
import com.intellij.openapi.editor.impl.TextDrawingCallback;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
@@ -123,4 +124,6 @@ public interface EditorEx extends Editor {
int calcColumnNumber(CharSequence text, int start, int offset, int tabSize);
int calcColumnNumber(int offset, int lineIndex);
TextDrawingCallback getTextDrawingCallback();
}
@@ -55,6 +55,7 @@ public class EditorSettingsExternalizable implements NamedJDOMExternalizable, Ex
public boolean IS_BLOCK_CURSOR = false;
public boolean IS_WHITESPACES_SHOWN = false;
public boolean IS_SOFTWRAPS_SHOWN = false;
public boolean IS_INDENT_GUIDES_SHOWN = true;
public boolean IS_ANIMATED_SCROLLING = true;
public boolean IS_CAMEL_WORDS = false;
@@ -313,6 +314,14 @@ public class EditorSettingsExternalizable implements NamedJDOMExternalizable, Ex
myOptions.IS_WHITESPACES_SHOWN = val;
}
public boolean isSoftWrapsShown() {
return myOptions.IS_SOFTWRAPS_SHOWN;
}
public void setSoftwrapsShown(boolean val) {
myOptions.IS_SOFTWRAPS_SHOWN = val;
}
public boolean isIndentGuidesShown() {
return myOptions.IS_INDENT_GUIDES_SHOWN;
}
@@ -19,8 +19,10 @@ import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.editor.SoftWrapModel;
import com.intellij.openapi.editor.TextChange;
import com.intellij.openapi.editor.VisualPosition;
import com.intellij.openapi.editor.impl.softwrap.SoftWrapDrawingType;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.util.List;
/**
@@ -69,4 +71,16 @@ public interface SoftWrapModelEx extends SoftWrapModel {
* @return unmodifiable collection of soft wraps currently registered within the current model
*/
List<TextChange> getRegisteredSoftWraps();
/**
* Asks to paint drawing of target type at the given graphics buffer at the given position.
*
* @param g target graphics buffer to draw in
* @param drawingType target drawing type
* @param x target <code>'x'</code> coordinate to use
* @param y target <code>'y'</code> coordinate to use
* @param lineHeight line height used at editor
* @return painted drawing width
*/
int paint(@NotNull Graphics g, @NotNull SoftWrapDrawingType drawingType, int x, int y, int lineHeight);
}
@@ -0,0 +1,61 @@
/*
* 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.Computable;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
/**
* Encapsulates logic of drawing arrows at graphics buffer (primary usage is to draw tabulation symbols representation arrows).
*
* @author Denis Zhdanov
* @since Jul 2, 2010 11:35:23 AM
*/
public class ArrowPainter {
private final ColorHolder myColorHolder;
private final Computable<Integer> myHeightProvider;
public ArrowPainter(@NotNull ColorHolder colorHolder, @NotNull Computable<Integer> heightProvider) {
myColorHolder = colorHolder;
myHeightProvider = heightProvider;
}
/**
* Paints arrow at the given graphics buffer using given coordinate parameters.
*
* @param g target graphics buffer to use
* @param y defines baseline of the row where the arrow should be painted
* @param start starting <code>'x'</code> position to use during drawing
* @param stop ending <code>'x'</code> position to use during drawing
*/
public void paint(Graphics g, int y, int start, int stop) {
stop -= g.getFontMetrics().charWidth(' ') / 2;
Color oldColor = g.getColor();
g.setColor(myColorHolder.getColor());
final int height = myHeightProvider.compute();
final int halfHeight = height / 2;
int mid = y - halfHeight;
int top = y - height;
UIUtil.drawLine(g, start, mid, stop, mid);
UIUtil.drawLine(g, stop, y, stop, top);
g.fillPolygon(new int[]{stop - halfHeight, stop - halfHeight, stop}, new int[]{y, y - height, y - halfHeight}, 3);
g.setColor(oldColor);
}
}
@@ -308,17 +308,13 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener {
myOffset = myEditor.logicalPositionToOffset(myLogicalCaret);
LOG.assertTrue(myOffset >= 0 && myOffset <= myEditor.getDocument().getTextLength());
int caretOffset = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line, 0)));
int caretLine = doc.getLineNumber(caretOffset);
myVisualLineStart = doc.getLineStartOffset(caretLine);
myVisualLineEnd = doc.getLineEndOffset(caretLine) + 1;
myVisualLineStart = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line, 0)));
myVisualLineEnd = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line + 1, 0)));
myEditor.updateCaretCursor();
requestRepaint(oldInfo);
if (oldCaretPosition.column + oldCaretPosition.softWrapColumnDiff != myLogicalCaret.column + myLogicalCaret.softWrapColumnDiff
|| oldCaretPosition.line + oldCaretPosition.softWrapLinesBeforeCurrentLogicalLine != myLogicalCaret.line + myLogicalCaret.softWrapLinesBeforeCurrentLogicalLine)
{
if (!oldCaretPosition.toVisualPosition().equals(myLogicalCaret.toVisualPosition())) {
CaretEvent event = new CaretEvent(myEditor, oldCaretPosition, myLogicalCaret);
for (CaretListener listener : myCaretListeners) {
listener.caretPositionChanged(event);
@@ -366,18 +362,6 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener {
return myOffset;
}
/**
* There is a possible case that single logical line is spread to more than one visual lines because of soft wraps. This method
* allows to receive information about vertical range occupied by the active logical line, i.e. it identifies
* <code>'y'</code> coordinate of the first visual line that corresponds to the logical line and total height
* of all visual lines that correspond to the active logical line.
*
* @return object that encapsulates information about visual vertical range occupied by the current logical line on a screen
*/
public VerticalInfo getVisualCaretInfo() {
return myCaretInfo;
}
public int getVisualLineStart() {
return myVisualLineStart;
}
@@ -0,0 +1,99 @@
/*
* 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.Editor;
import com.intellij.openapi.editor.colors.ColorKey;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import java.awt.*;
/**
* Defines common contract for target {@link Color} retrieving.
*
* @author Denis Zhdanov
* @since Jul 2, 2010 11:12:07 AM
*/
public abstract class ColorHolder {
/**
* @return target {@link Color} object managed by the current holder
*/
public abstract Color getColor();
/**
* Factory method for creating color holder that always returns given {@link Color} object.
*
* @param color target color to use
* @return color holder that uses given color all the time
*/
public static ColorHolder byColor(Color color) {
return new StaticColorHolder(color);
}
/**
* Shortcut for calling {@link #byColorsScheme(EditorColorsScheme, ColorKey)} with colors scheme retrieved from the given editor.
*
* @param editor target colors scheme holder
* @param key target color identifier within the colors scheme
* @return color holder that delegate target color retrieval to the colors scheme associated
* with the given editor using given color key
*/
public static ColorHolder byColorsScheme(Editor editor, ColorKey key) {
return new ColorSchemeBasedHolder(editor.getColorsScheme(), key);
}
/**
* Factory method for creating color holder that delegate target color retrieval to the given colors scheme using given color key.
*
* @param scheme target colors scheme to use
* @param key target color identifier
* @return color holder that delegate target color retrieval to the given colors scheme using given color key
*/
public static ColorHolder byColorsScheme(EditorColorsScheme scheme, ColorKey key) {
return new ColorSchemeBasedHolder(scheme, key);
}
private static class StaticColorHolder extends ColorHolder {
private final Color myColor;
StaticColorHolder(Color color) {
myColor = color;
}
@Override
public Color getColor() {
return myColor;
}
}
private static class ColorSchemeBasedHolder extends ColorHolder {
private final EditorColorsScheme myScheme;
private final ColorKey myKey;
ColorSchemeBasedHolder(EditorColorsScheme scheme, ColorKey key) {
myScheme = scheme;
myKey = key;
}
@Override
public Color getColor() {
return myScheme.getColor(myKey);
}
}
}
@@ -258,11 +258,12 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
}
private void paintCaretRowBackground(final Graphics g, final int x, final int width) {
CaretModelImpl.VerticalInfo caretInfo = myEditor.getCaretModel().getVisualCaretInfo();
final VisualPosition visCaret = myEditor.getCaretModel().getVisualPosition();
Color caretRowColor = myEditor.getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR);
if (caretRowColor != null) {
g.setColor(caretRowColor);
g.fillRect(x, caretInfo.y, width, caretInfo.height);
final Point caretPoint = myEditor.visualPositionToXY(visCaret);
g.fillRect(x, caretPoint.y, width, myEditor.getLineHeight());
}
}
@@ -49,6 +49,7 @@ 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.markup.*;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.progress.ProgressManager;
@@ -154,6 +155,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
private MyEditable myEditable;
private EditorColorsScheme myScheme;
private ArrowPainter myTabPainter;
private final boolean myIsViewer;
private final SelectionModelImpl mySelectionModel;
private final EditorMarkupModelImpl myMarkupModel;
@@ -178,6 +180,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
private boolean myUnderRepainting;
private EditorHighlighter myHighlighter;
private final TextDrawingCallback myTextDrawingCallback = new MyTextDrawingCallback();
private int myScrollBarOrientation;
private boolean myMousePressedInsideSelection;
@@ -237,11 +240,11 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
ourCaretBlinkingCommand.start();
}
public EditorImpl(Document document, boolean viewer, Project project) {
myProject = project;
myDocument = (DocumentImpl)document;
myScheme = new MyColorSchemeDelegate();
initTabPainter();
myIsViewer = viewer;
mySettings = new SettingsImpl(this);
@@ -474,6 +477,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
myEditorComponent.repaint();
initTabPainter();
updateCaretCursor();
if (myInitialMouseEvent != null) {
@@ -481,6 +485,18 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
}
}
private void initTabPainter() {
myTabPainter = new ArrowPainter(
ColorHolder.byColorsScheme(myScheme, EditorColors.WHITESPACES_COLOR),
new Computable<Integer>() {
@Override
public Integer compute() {
return getCharHeight();
}
}
);
}
public void release() {
if (isReleased) {
LOG.error("Double release. First released at: =====\n" + myReleasedAt+"\n======");
@@ -1382,6 +1398,11 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
return getBackgroundIgnoreForced();
}
@Override
public TextDrawingCallback getTextDrawingCallback() {
return myTextDrawingCallback;
}
private Color getBackgroundColor(final TextAttributes attributes) {
final Color attrColor = attributes.getBackgroundColor();
return attrColor == myScheme.getDefaultBackground() ? getBackgroundColor() : attrColor;
@@ -1543,6 +1564,14 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
CharSequence text = myDocument.getCharsNoThreadCheck();
int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
// There is a possible case that we work with visual line that is introduced by soft wrap. That implies that additional
// space is reserved before the document text represented at the current line (soft wrap drawing is located there).
// Hence, we need to take that into consideration during determining if new soft wrap should be introduced.
// Example:
// 111 222 333 <- soft wrap is here
// soft wrap drawing -> 444 555 <- we need to consider soft wrap drawing on a start of the line during checking if we need to wrap 555.
boolean onSoftWrapIntroducedVisualLine = false;
outer:
while (!iterationState.atEnd() && !lIterator.atEnd()) {
int hEnd = iterationState.getEndOffset();
@@ -1571,6 +1600,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
position.x = 0;
if (position.y > clip.y + clip.height) break;
position.y += lineHeight;
onSoftWrapIntroducedVisualLine = false;
start = lEnd;
}
@@ -1583,7 +1613,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
}
else {
TextChange softWrap = getSoftWrapModel().getSoftWrap(start);
if (softWrap == null && getSoftWrapModel().shouldWrap(myDocument.getRawChars(), start, hEnd, position)) {
if (softWrap == null
&& getSoftWrapModel().shouldWrap(g, myDocument.getRawChars(), start, hEnd, position.x, onSoftWrapIntroducedVisualLine))
{
softWrap = getSoftWrapModel().wrap(myDocument.getRawChars(), start, hEnd);
}
if (softWrap != null) {
@@ -1593,7 +1625,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
int softWrapEnd = 0;
while (softWrapEnd < softWrapText.length()) {
for (; softWrapEnd < softWrapText.length() && softWrapText.charAt(softWrapEnd) != '\n'; softWrapEnd++) ;
if (softWrapStart >= softWrapText.length() || softWrapEnd >= softWrapText.length()) {
if (softWrapEnd >= softWrapText.length()) {
position.x = drawBackground(
g, backColor, softWrapText.subSequence(softWrapStart, softWrapText.length()), position, fontType, defaultBackground, clip
);
@@ -1607,6 +1639,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
if (position.y > clip.y + clip.height) break outer;
position.x = 0;
position.y += lineHeight;
onSoftWrapIntroducedVisualLine = true;
softWrapStart = softWrapEnd + 1;
softWrapEnd = softWrapStart;
}
@@ -1839,7 +1872,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
// to the first soft wrap symbol that is not drawn yet.
int softWrapSegmentStartIndex = 0;
for (int i = 0; i < softWrapChars.length; i++) {
// Delay soft wraps symbols drawing until EOL if found.
// Delay soft wraps symbols drawing until EOL is found.
if (softWrapChars[i] != '\n') {
continue;
}
@@ -1857,6 +1890,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
g, softWrapChars, softWrapSegmentStartIndex, i, position, clip, currentColor, effectType, fontType, currentColor
);
}
mySoftWrapModel.paint(g, SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED, position.x, position.y, getLineHeight());
// Reset 'x' coordinate because of new line start.
position.x = 0;
@@ -1877,6 +1911,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
fontType, currentColor
);
}
position.x += mySoftWrapModel.paint(g, SoftWrapDrawingType.AFTER_SOFT_WRAP_LINE_FEED, position.x, position.y, getLineHeight());
activeSoftWrapProcessed = true;
}
@@ -1964,7 +1999,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
if (count > 0) {
final int lastCount = count - 1;
final Color lastColor = color[lastCount];
if (_data == myLastData && _start == ends[lastCount] && (_color == null || lastColor == null || _color == lastColor)) {
if (_data == myLastData && _start == ends[lastCount] && (_color == null || lastColor == null || _color == lastColor)
&& _y == y[lastCount] /* there is a possible case that vertical position is adjusted because of soft wrap */)
{
ends[lastCount] = _end;
if (lastColor == null) color[lastCount] = _color;
return;
@@ -2195,17 +2232,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
private void drawTabPlacer(Graphics g, int y, int start, int stop) {
if (mySettings.isWhitespacesShown()) {
stop -= g.getFontMetrics().charWidth(' ') / 2;
Color oldColor = g.getColor();
g.setColor(myScheme.getColor(EditorColors.WHITESPACES_COLOR));
final int charHeight = getCharHeight();
final int halfCharHeight = charHeight / 2;
int mid = y - halfCharHeight;
int top = y - charHeight;
UIUtil.drawLine(g, start, mid, stop, mid);
UIUtil.drawLine(g, stop, y, stop, top);
g.fillPolygon(new int[]{stop - halfCharHeight, stop - halfCharHeight, stop}, new int[]{y, y - charHeight, y - halfCharHeight}, 3);
g.setColor(oldColor);
myTabPainter.paint(g, y, start, stop);
}
}
@@ -2215,23 +2242,27 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
}
else {
FontInfo fnt = EditorUtil.fontForChar(data[start], fontType, this);
CachedFontContent cache = null;
for (CachedFontContent fontCache : myFontCache) {
if (fontCache.myFontType == fnt) {
cache = fontCache;
break;
}
}
if (cache == null) {
cache = new CachedFontContent(fnt);
myFontCache.add(cache);
}
myLastCache = cache;
cache.addContent(g, data, start, end, x, y, color);
drawCharsCached(g, data, start, end, x, y, fnt, color);
}
}
private void drawCharsCached(Graphics g, char[] data, int start, int end, int x, int y, FontInfo fnt, Color color) {
CachedFontContent cache = null;
for (CachedFontContent fontCache : myFontCache) {
if (fontCache.myFontType == fnt) {
cache = fontCache;
break;
}
}
if (cache == null) {
cache = new CachedFontContent(fnt);
myFontCache.add(cache);
}
myLastCache = cache;
cache.addContent(g, data, start, end, x, y, color);
}
private static boolean spacesOnly(char[] chars, int start, int end) {
for (int i = start; i < end; i++) {
if (chars[i] != ' ') return false;
@@ -4722,4 +4753,11 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
myOldHeight = getHeight();
}
}
private class MyTextDrawingCallback implements TextDrawingCallback {
@Override
public void drawChars(Graphics g, char[] data, int start, int end, int x, int y, Color color, FontInfo fontInfo) {
drawCharsCached(g, data, start, end, x, y, fontInfo, color);
}
}
}
@@ -73,6 +73,7 @@ public class SettingsImpl implements EditorSettings {
private Boolean myIsRenameVariablesInplace = null;
private Boolean myIsRefrainFromScrolling = null;
private Boolean myUseSoftWraps = null;
private Boolean myIsSoftWrapsShown = null;
public boolean isRightMarginShown() {
return myIsRightMarginShown != null
@@ -396,6 +397,16 @@ public class SettingsImpl implements EditorSettings {
fireEditorRefresh();
}
@Override
public boolean isSoftWrapsShown() {
return myIsSoftWrapsShown != null ? myIsWhitespacesShown.booleanValue() : EditorSettingsExternalizable.getInstance().isSoftWrapsShown();
}
@Override
public void setShowSoftWraps(boolean show) {
myIsWhitespacesShown = Boolean.valueOf(show);
}
private void fireEditorRefresh() {
if (myEditor != null) {
myEditor.reinitSettings();
@@ -23,7 +23,7 @@ import com.intellij.openapi.editor.event.VisibleAreaListener;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.SoftWrapModelEx;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.impl.softwrap.SoftWrapsStorage;
import com.intellij.openapi.editor.impl.softwrap.*;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.text.CharArrayUtil;
import gnu.trove.TIntHashSet;
@@ -64,6 +64,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
private final CharBuffer myCharBuffer = CharBuffer.allocate(1);
private final SoftWrapsStorage myStorage;
private final SoftWrapPainter myPainter;
/**
* Holds logical lines where soft wraps should be removed.
@@ -93,12 +94,13 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
private int myActive;
public SoftWrapModelImpl(@NotNull EditorEx editor) {
this(editor, new SoftWrapsStorage());
this(editor, new SoftWrapsStorage(), new CompositeSoftWrapPainter(editor));
}
public SoftWrapModelImpl(@NotNull EditorEx editor, @NotNull SoftWrapsStorage storage) {
public SoftWrapModelImpl(@NotNull EditorEx editor, @NotNull SoftWrapsStorage storage, @NotNull SoftWrapPainter painter) {
myEditor = editor;
myStorage = storage;
myPainter = painter;
}
public boolean isSoftWrappingEnabled() {
@@ -155,13 +157,16 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
* Allows to answer if symbols of the given char array located at <code>[start; end)</code> interval should be soft wrapped,
* i.e. represented on a next line.
*
* @param chars symbols holder
* @param start target symbols sub-sequence start within the given char array (inclusive)
* @param end target symbols sub-sequence end within the given char array (exclusive)
* @param position current drawing position
* @return <code>true</code> if target symbols sub-sequence should be soft-wrapped; <code>false</code> otherwise
* @param g graphics buffer being used
* @param chars symbols holder
* @param start target symbols sub-sequence start within the given char array (inclusive)
* @param end target symbols sub-sequence end within the given char array (exclusive)
* @param x <code>'x'</code> coordinate of the current drawing position
* @param onSoftWrapIntroducedVisualLine defines if current visual position belongs to soft wrap-introduced visual line
* @return <code>true</code> if target symbols sub-sequence should be soft-wrapped;
* <code>false</code> otherwise
*/
public boolean shouldWrap(char[] chars, int start, int end, Point position) {
public boolean shouldWrap(@NotNull Graphics g, @NotNull char[] chars, int start, int end, int x, boolean onSoftWrapIntroducedVisualLine) {
if (!isSoftWrappingEnabled()) {
return false;
}
@@ -178,7 +183,11 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
}
//TODO den implement
return position.x + (end - start) * 7 > myRightEdgeLocation;
int xAfterCharsDrawing = x + myPainter.getMinDrawingWidth(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED) + (end - start) * 7;
if (onSoftWrapIntroducedVisualLine) {
xAfterCharsDrawing += myPainter.getMinDrawingWidth(SoftWrapDrawingType.AFTER_SOFT_WRAP_LINE_FEED);
}
return xAfterCharsDrawing > myRightEdgeLocation;
}
private static boolean containsOnlyWhiteSpaces(char[] chars, int start, int end) {
@@ -243,6 +252,11 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
myDirtyLines.clear();
}
@Override
public int paint(@NotNull Graphics g, @NotNull SoftWrapDrawingType drawingType, int x, int y, int lineHeight) {
return myPainter.paint(g, drawingType, x, y, lineHeight);
}
@NotNull
public LogicalPosition adjustLogicalPosition(@NotNull LogicalPosition defaultLogical, @NotNull VisualPosition visual) {
if (myActive > 0 || !isSoftWrappingEnabled() || myStorage.isEmpty() || myEditor.getDocument().getTextLength() <= 0) {
@@ -706,7 +720,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
Document document = myEditor.getDocument();
if (softWrapIndex >= softWraps.size() || document.getTextLength() <= 0) {
if (softWrapIndex >= softWraps.size() || document.getTextLength() <= 0 || change.getStart() >= document.getTextLength()) {
return;
}
int firstChangedLine = document.getLineNumber(change.getStart());
@@ -723,7 +737,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
// Add modified soft wraps.
for (TextChange softWrap : toModify) {
softWraps.add(softWrap.advance(change.getDiff()));
myStorage.storeSoftWrap(softWrap.advance(change.getDiff()));
}
}
@@ -0,0 +1,48 @@
/*
* 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 java.awt.*;
/**
* IDEA editors use highly-optimized drawing algorithm that is tuned for painting large amounts of text data.
* <p/>
* However, there is a possible case that particular editor extension or third-party component may want to draw text at
* IDEA editor on it own. It's not supposed to do that directly, contrary, it's expected to delegate the processing
* to the code that knows how to do that.
* <p/>
* Current interface defines a contract for such a text drawing delegation task.
*
* @author Denis Zhdanov
* @since Jul 1, 2010 8:01:30 PM
*/
public interface TextDrawingCallback {
/**
* Asks to draw symbols from <code>[start; end)</code> range of given char array at given graphics buffer using given
* font info and color.
*
* @param g graphics buffer to use
* @param data target symbols holder
* @param start start offset within the symbols holder to use (inclusive)
* @param end end offset within the symbols holder to use (inclusive)
* @param x <code>'x'</code> coordinate to use as a start position at the given graphics buffer
* @param y <code>'y'</code> coordinate to use as a start position at the given graphics buffer
* @param fontInfo font info to use during drawing target text at the given graphics buffer
* @param color color to use during drawing target text at the given graphics buffer
*/
void drawChars(Graphics g, char[] data, int start, int end, int x, int y, Color color, FontInfo fontInfo);
}
@@ -0,0 +1,105 @@
/*
* 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.softwrap;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.impl.ArrowPainter;
import com.intellij.openapi.editor.impl.ColorHolder;
import com.intellij.openapi.util.Computable;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
/**
* {@link SoftWrapPainter} implementation that draws arrows in soft wrap location.
* <p/>
* Primary idea is to use dedicated unicode symbols as soft wrap drawings and this class is introduced only as a part
* of defensive programming - there is unlikely case that local client environment doesn't have a font that is able to
* represent target unicode symbol. We draw an arrow manually then (platform-independent approach).
*
* @author Denis Zhdanov
* @since Jul 2, 2010 11:31:36 AM
*/
public class ArrowSoftWrapPainter implements SoftWrapPainter {
private final HeightProvider myHeightProvider = new HeightProvider();
private final Editor myEditor;
private final ArrowPainter myArrowPainter;
private int myMinWidth = -1;
public ArrowSoftWrapPainter(Editor editor) {
myEditor = editor;
myArrowPainter = new ArrowPainter(ColorHolder.byColor(myEditor.getColorsScheme().getDefaultForeground()), myHeightProvider);
}
@Override
public int paint(@NotNull Graphics g, @NotNull SoftWrapDrawingType drawingType, int x, int y, int lineHeight) {
myHeightProvider.myHeight = lineHeight / 2;
int start;
int end;
int result;
switch (drawingType) {
case BEFORE_SOFT_WRAP_LINE_FEED:
start = x;
end = myEditor.getScrollingModel().getVisibleArea().width;
result = end - start;
break;
case AFTER_SOFT_WRAP_LINE_FEED:
start = 0;
end = x;
result = 0;
break;
default: throw new IllegalStateException("Soft wrap arrow painting is not set up for drawing type " + drawingType);
}
myArrowPainter.paint(g, y + lineHeight - g.getFontMetrics().getDescent(), start, end);
return result;
}
@Override
public int getDrawingHorizontalOffset(@NotNull Graphics g, @NotNull SoftWrapDrawingType drawingType, int x, int y, int lineHeight) {
switch (drawingType) {
case BEFORE_SOFT_WRAP_LINE_FEED: return myEditor.getScrollingModel().getVisibleArea().width - x;
case AFTER_SOFT_WRAP_LINE_FEED: return 0;
default: throw new IllegalStateException("Soft wrap arrow painting is not set up for drawing type " + drawingType);
}
}
@Override
public int getMinDrawingWidth(@NotNull SoftWrapDrawingType drawingType) {
if (myMinWidth < 0) {
// We need to reserve a minimal space required for representing arrow before soft wrap-introduced line feed.
myMinWidth = EditorUtil.charWidth('a', Font.PLAIN, myEditor);
}
return myMinWidth;
}
@Override
public boolean canUse() {
return true;
}
private static class HeightProvider implements Computable<Integer> {
public int myHeight;
@Override
public Integer compute() {
return myHeight;
}
}
}
@@ -0,0 +1,140 @@
/*
* 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.softwrap;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.editor.VisualPosition;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.impl.ColorHolder;
import com.intellij.openapi.editor.impl.TextDrawingCallback;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import static com.intellij.openapi.editor.impl.softwrap.SoftWrapDrawingType.AFTER_SOFT_WRAP_LINE_FEED;
import static com.intellij.openapi.editor.impl.softwrap.SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED;
import static java.util.Arrays.asList;
/**
* Composite (in terms of GoF patterns) object for {@link SoftWrapPainter} objects.
* <p/>
* Not thread-safe.
*
* @author Denis Zhdanov
* @since Jul 2, 2010 10:20:14 AM
*/
public class CompositeSoftWrapPainter implements SoftWrapPainter {
private static final List<Map<SoftWrapDrawingType, Character>> SYMBOLS = asList(
asMap(asList(BEFORE_SOFT_WRAP_LINE_FEED, AFTER_SOFT_WRAP_LINE_FEED),
asList('\uE48B', '\uE48C')),
asMap(asList(BEFORE_SOFT_WRAP_LINE_FEED, AFTER_SOFT_WRAP_LINE_FEED),
asList('\u2926', '\u2925')),
asMap(asList(BEFORE_SOFT_WRAP_LINE_FEED, AFTER_SOFT_WRAP_LINE_FEED),
asList('\u21B2', '\u21B3')),
asMap(asList(BEFORE_SOFT_WRAP_LINE_FEED, AFTER_SOFT_WRAP_LINE_FEED),
asList('\u2936', '\u2937')),
asMap(asList(BEFORE_SOFT_WRAP_LINE_FEED, AFTER_SOFT_WRAP_LINE_FEED),
asList('\u21A9', '\u21AA'))
);
private final EditorEx myEditor;
private SoftWrapPainter myDelegate;
/**
* There is a possible case that particular symbols configured to be used as a soft wrap drawings are not supported by
* available fonts. We would like to try another symbols then.
* <p/>
* Current field points to the index of symbols collection from {@link #SYMBOLS} tried last time.
*/
private int mySymbolsDrawingIndex = -1;
public CompositeSoftWrapPainter(EditorEx editor) {
myEditor = editor;
}
@Override
public int paint(@NotNull Graphics g, @NotNull SoftWrapDrawingType drawingType, int x, int y, int lineHeight) {
initDelegateIfNecessary();
if (!myEditor.getSettings().isSoftWrapsShown()) {
int visualLine = y / lineHeight;
LogicalPosition position = myEditor.visualToLogicalPosition(new VisualPosition(visualLine, 0));
if (position.line != myEditor.getCaretModel().getLogicalPosition().line) {
return myDelegate.getDrawingHorizontalOffset(g, drawingType, x, y, lineHeight);
}
}
return myDelegate.paint(g, drawingType, x, y, lineHeight);
}
@Override
public int getDrawingHorizontalOffset(@NotNull Graphics g, @NotNull SoftWrapDrawingType drawingType, int x, int y, int lineHeight) {
initDelegateIfNecessary();
return myDelegate.getDrawingHorizontalOffset(g, drawingType, x, y, lineHeight);
}
@Override
public int getMinDrawingWidth(@NotNull SoftWrapDrawingType drawingType) {
initDelegateIfNecessary();
return myDelegate.getMinDrawingWidth(drawingType);
}
@Override
public boolean canUse() {
return true;
}
private void initDelegateIfNecessary() {
if (myDelegate != null && myDelegate.canUse()) {
return;
}
if (++mySymbolsDrawingIndex < SYMBOLS.size()) {
TextDrawingCallback callback = myEditor.getTextDrawingCallback();
ColorHolder colorHolder = ColorHolder.byColorsScheme(myEditor, EditorColors.RIGHT_MARGIN_COLOR);
myDelegate = new TextBasedSoftWrapPainter(SYMBOLS.get(mySymbolsDrawingIndex), myEditor, callback, colorHolder);
initDelegateIfNecessary();
return;
}
myDelegate = new ArrowSoftWrapPainter(myEditor);
}
private static <K, V> Map<K, V> asMap(Iterable<K> keys, Iterable<V> values) throws IllegalArgumentException {
Map<K, V> result = new HashMap<K,V>();
Iterator<K> keyIterator = keys.iterator();
Iterator<V> valueIterator = values.iterator();
while (keyIterator.hasNext()) {
if (!valueIterator.hasNext()) {
throw new IllegalArgumentException(
String.format("Can't build for the given data. Reason: number of keys differs from number of values. "
+ "Keys: %s, values: %s", keys, values)
);
}
result.put(keyIterator.next(), valueIterator.next());
}
if (valueIterator.hasNext()) {
throw new IllegalArgumentException(
String.format("Can't build for the given data. Reason: number of keys differs from number of values. "
+ "Keys: %s, values: %s", keys, values)
);
}
return result;
}
}
@@ -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.softwrap;
/**
* Enumerates types soft wrap-related drawings supported by {@link SoftWrapPainter}.
*
* @author Denis Zhdanov
* @since Jul 1, 2010 5:19:45 PM
*/
public enum SoftWrapDrawingType {
BEFORE_SOFT_WRAP_LINE_FEED, AFTER_SOFT_WRAP_LINE_FEED
}
@@ -0,0 +1,84 @@
/*
* 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.softwrap;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
/**
* Defines contract for the service that manages soft wrap-related graphical effects.
* <p/>
* For example we may want to draw an arrow just before and after soft wrap-introduced line feed:
* <p/>
* <pre>
* This is a long text &#xE48B;
* &#xE48C;that is soft-wrapped
* </pre>
* <p/>
* Implementations of this interface are not obliged to be thread-safe.
*
* @author Denis Zhdanov
* @since Jul 1, 2010 5:02:37 PM
*/
public interface SoftWrapPainter {
/**
* Asks to paint drawing of target type at the given graphics buffer at the given position.
*
* @param g target graphics buffer to draw in
* @param drawingType target drawing type
* @param x target <code>'x'</code> coordinate to use
* @param y target <code>'y'</code> coordinate to use
* @param lineHeight line height used at editor
* @return horizontal offset introduced to the given 'x' coordinate after target drawing painting
*/
int paint(@NotNull Graphics g, @NotNull SoftWrapDrawingType drawingType, int x, int y, int lineHeight);
/**
* Allows to ask about horizontal offset to be applied to the given <code>'x'</code> coordinate if drawing of the given
* type is performed at the given graphics buffer.
* <p/>
* Generally, this method is useful when we don't want to perform actual drawing for now but want to reserve
* a space necessary to do that in future. I.e. the aim is to avoid horizontal movement of already drawn content
* when the drawing is actually performed.
*
* @param g target graphics buffer to draw in
* @param drawingType target drawing type
* @param x target <code>'x'</code> coordinate to use
* @param y target <code>'y'</code> coordinate to use
* @param lineHeight line height used at editor
* @return horizontal offset that would be introduced if the drawing is performed
*/
int getDrawingHorizontalOffset(@NotNull Graphics g, @NotNull SoftWrapDrawingType drawingType, int x, int y, int lineHeight);
/**
* Allows to ask for the minimal width in pixels required for painting of the given type.
*
* @param drawingType target drawing type
* @return width in pixels required for the painting of the given type
*/
int getMinDrawingWidth(@NotNull SoftWrapDrawingType drawingType);
/**
* Allows to answer if it's possible to use current painter implementation at local environment (e.g. there is a possible
* case that particular painter that exploits unicode symbols for drawing can't be used because there is no font
* at local environment that knows how to draw target symbols).
*
* @return <code>true</code> if current painter can be used at local environment; <code>false</code> otherwise
*/
boolean canUse();
}
@@ -0,0 +1,116 @@
/*
* 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.softwrap;
import com.intellij.openapi.editor.impl.ColorHolder;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.impl.FontInfo;
import com.intellij.openapi.editor.impl.TextDrawingCallback;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.util.EnumMap;
import java.util.Map;
/**
* {@link SoftWrapPainter} implementation that uses target unicode symbols as soft wrap drawings.
* <p/>
* Not thread-safe.
*
* @author Denis Zhdanov
* @since Jul 1, 2010 5:30:43 PM
*/
public class TextBasedSoftWrapPainter implements SoftWrapPainter {
private final Map<SoftWrapDrawingType, char[]> mySymbols = new EnumMap<SoftWrapDrawingType, char[]>(SoftWrapDrawingType.class);
private final Map<SoftWrapDrawingType, FontInfo> myFonts = new EnumMap<SoftWrapDrawingType, FontInfo>(SoftWrapDrawingType.class);
private final Map<SoftWrapDrawingType, Integer> myWidths = new EnumMap<SoftWrapDrawingType, Integer>(SoftWrapDrawingType.class);
private final Map<SoftWrapDrawingType, Integer> myVGaps = new EnumMap<SoftWrapDrawingType, Integer>(SoftWrapDrawingType.class);
private final TextDrawingCallback myDrawingCallback;
private final ColorHolder myColorHolder;
private final boolean myCanUse;
public TextBasedSoftWrapPainter(Map<SoftWrapDrawingType, Character> symbols, Editor editor, TextDrawingCallback drawingCallback,
ColorHolder colorHolder)
throws IllegalArgumentException
{
if (symbols.size() != SoftWrapDrawingType.values().length) {
throw new IllegalArgumentException(
String.format("Can't create text-based soft wrap painter. Reason: given 'drawing type -> symbol' mappings "
+ "are incomplete - expected size %d but got %d (%s)", SoftWrapDrawingType.values().length, symbols.size(), symbols)
);
}
myDrawingCallback = drawingCallback;
myColorHolder = colorHolder;
myCanUse = init(symbols, editor);
}
@Override
public int paint(@NotNull Graphics g, @NotNull SoftWrapDrawingType drawingType, int x, int y, int lineHeight) {
char[] buffer = mySymbols.get(drawingType);
FontInfo fontInfo = myFonts.get(drawingType);
int vGap = myVGaps.get(drawingType);
myDrawingCallback.drawChars(g, buffer, 0, buffer.length, x, y + lineHeight - vGap, myColorHolder.getColor(), fontInfo);
return getMinDrawingWidth(drawingType);
}
@Override
public int getDrawingHorizontalOffset(@NotNull Graphics g, @NotNull SoftWrapDrawingType drawingType, int x, int y, int lineHeight) {
return getMinDrawingWidth(drawingType);
}
@Override
public int getMinDrawingWidth(@NotNull SoftWrapDrawingType drawingType) {
return myWidths.get(drawingType);
}
@Override
public boolean canUse() {
return myCanUse;
}
/**
* Tries to find fonts that are capable to display all unicode symbols used by the current painter.
*
* @param symbols target symbols to use for drawing
* @param editor editor to use during font lookup
* @return <code>true</code> if target font that is capable to display all unicode symbols used by the current painter is found;
* <code>false</code> otherwise
*/
private boolean init(Map<SoftWrapDrawingType, Character> symbols, Editor editor) {
// We use dummy component here in order to being able to work with font metrics.
JLabel component = new JLabel();
for (Map.Entry<SoftWrapDrawingType, Character> entry : symbols.entrySet()) {
FontInfo fontInfo = EditorUtil.fontForChar(entry.getValue(), Font.PLAIN, editor);
if (!fontInfo.canDisplay(entry.getValue())) {
return false;
}
char[] buffer = new char[1];
buffer[0] = entry.getValue();
mySymbols.put(entry.getKey(), buffer);
myFonts.put(entry.getKey(), fontInfo);
FontMetrics metrics = component.getFontMetrics(fontInfo.getFont());
myWidths.put(entry.getKey(), metrics.charWidth(buffer[0]));
int vGap = metrics.getDescent();
myVGaps.put(entry.getKey(), vGap);
}
return true;
}
}
@@ -18,6 +18,7 @@ package com.intellij.openapi.editor.impl;
import com.intellij.mock.MockFoldRegion;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.impl.softwrap.SoftWrapPainter;
import com.intellij.openapi.editor.impl.softwrap.SoftWrapsStorage;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
@@ -191,7 +192,9 @@ public class SoftWrapModelImplTest {
});
}});
myModel = new SoftWrapModelImpl(myEditor, myStorage);
SoftWrapPainter painter = myMockery.mock(SoftWrapPainter.class);
myModel = new SoftWrapModelImpl(myEditor, myStorage, painter);
}
@After
@@ -262,6 +262,7 @@ tab.editor.settings.appearance=Appearance
groupbox.display=Display
checkbox.smooth.scrolling=Smooth scrolling
checkbox.show.whitespaces=Show whitespaces
checkbox.show.softwraps=Show soft wraps
checkbox.show.method.separators=Show method separators
checkbox.show.small.icons.in.gutter=Show icons preview in gutter for small icons (Java)
checkbox.show.line.numbers=Show line numbers