IDEA-56405 Soft wrap, editing: typing on line split border replaces wrap sign with line feed; AssertionError at SoftWrapModelImpl.beforeDocumentChangeAtCaret()

1. Corrected soft wraps processing on document change;
2. Implemented soft wraps resources releasing on editor disposing;
3. Corrected 'active row' background painting algorithm for soft-wrapped lines;
4. Changed default 'soft wrap arrows' to another unicode symbols (they are supposed to correctly represented by Windows 7 fonts);
This commit is contained in:
Denis Zhdanov
2010-08-11 15:09:40 +04:00
parent b9fb70aec2
commit 35fa4b10b4
15 changed files with 332 additions and 151 deletions
@@ -56,9 +56,14 @@ public class CaretModelWindow implements CaretModel {
myDelegate.moveToLogicalPosition(hostPos);
}
public void moveToOffset(final int offset) {
@Override
public void moveToOffset(int offset) {
moveToOffset(offset, false);
}
public void moveToOffset(final int offset, boolean locateBeforeSoftWrap) {
int hostOffset = myEditorWindow.getDocument().injectedToHost(offset);
myDelegate.moveToOffset(hostOffset);
myDelegate.moveToOffset(hostOffset, locateBeforeSoftWrap);
}
public LogicalPosition getLogicalPosition() {
@@ -55,12 +55,23 @@ public interface CaretModel {
void moveToVisualPosition(VisualPosition pos);
/**
* Moves the caret to the specified offset in the document.
* Short hand for calling {@link #moveToOffset(int, boolean)} with <code>'false'</code> as a second argument.
*
* @param offset the offset to move to.
* @param offset the offset to move to
*/
void moveToOffset(int offset);
/**
* Moves the caret to the specified offset in the document.
*
* @param offset the offset to move to.
* @param locateBeforeSoftWrap there is a possible case that there is a soft wrap at the given offset, hence, the same offset
* corresponds to two different visual positions - just before soft wrap and just after soft wrap.
* We may want to clearly indicate where to put the caret then. Given parameter allows to do that.
* <b>Note:</b> it's ignored if there is no soft wrap at the given offset
*/
void moveToOffset(int offset, boolean locateBeforeSoftWrap);
/**
* Returns the logical position of the caret.
*
@@ -81,7 +81,7 @@ public class EditorModificationUtil {
public static int insertStringAtCaret(Editor editor, String s, boolean toProcessOverwriteMode, boolean toMoveCaret) {
final SelectionModel selectionModel = editor.getSelectionModel();
if (selectionModel.hasSelection()) {
editor.getCaretModel().moveToOffset(selectionModel.getSelectionStart());
editor.getCaretModel().moveToOffset(selectionModel.getSelectionStart(), true);
}
// There is a possible case that particular soft wraps become hard wraps if the caret is located at soft wrap-introduced virtual
@@ -114,7 +114,7 @@ public class EditorModificationUtil {
int offset = oldOffset + s.length();
if (toMoveCaret){
editor.getCaretModel().moveToOffset(offset);
editor.getCaretModel().moveToOffset(offset, true);
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
selectionModel.removeSelection();
}
@@ -81,10 +81,10 @@ public class BackspaceAction extends EditorAction {
}
else {
int offset = editor.getCaretModel().getOffset();
editor.getCaretModel().moveToOffset(offset-1);
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
editor.getSelectionModel().removeSelection();
document.deleteString(offset-1, offset);
editor.getCaretModel().moveToOffset(offset - 1, true);
}
}
else if(lineNumber > 0) {
@@ -97,6 +97,7 @@ public class DeleteAction extends EditorAction {
if(afterLineEnd < 0) {
int offset = editor.getCaretModel().getOffset();
document.deleteString(offset, offset + 1);
editor.getCaretModel().moveToOffset(offset);
return;
}
if(lineNumber + 1 >= document.getLineCount())
@@ -35,6 +35,7 @@ import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
import com.intellij.openapi.editor.ex.PrioritizedDocumentListener;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.impl.event.DocumentEventImpl;
import com.intellij.openapi.editor.impl.softwrap.SoftWrapHelper;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.ContainerUtil;
@@ -133,9 +134,13 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener {
}
public void moveToOffset(int offset) {
moveToOffset(offset, false);
}
public void moveToOffset(int offset, boolean locateBeforeSoftWrap) {
assertIsDispatchThread();
validateCallContext();
moveToLogicalPosition(myEditor.offsetToLogicalPosition(offset));
moveToLogicalPosition(myEditor.offsetToLogicalPosition(offset), locateBeforeSoftWrap);
if (!myEditor.offsetToLogicalPosition(myOffset).equals(myEditor.offsetToLogicalPosition(offset))) {
LOG.error("caret moved to wrong offset. Requested:" + offset + " but actual:" + myOffset);
}
@@ -244,6 +249,10 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener {
}
public void moveToLogicalPosition(LogicalPosition pos) {
moveToLogicalPosition(pos, false);
}
private void moveToLogicalPosition(LogicalPosition pos, boolean locateBeforeSoftWrap) {
assertIsDispatchThread();
validateCallContext();
int column = pos.column;
@@ -325,6 +334,14 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener {
myEditor.updateCaretCursor();
requestRepaint(oldInfo);
if (locateBeforeSoftWrap && SoftWrapHelper.isCaretAfterSoftWrap(myEditor)) {
int lineToUse = myVisibleCaret.line - 1;
if (lineToUse >= 0) {
moveToVisualPosition(new VisualPosition(lineToUse, EditorUtil.getLastVisualLineColumnNumber(myEditor, lineToUse)));
return;
}
}
if (!oldCaretPosition.toVisualPosition().equals(myLogicalCaret.toVisualPosition())) {
CaretEvent event = new CaretEvent(myEditor, oldCaretPosition, myLogicalCaret);
for (CaretListener listener : myCaretListeners) {
@@ -408,15 +425,19 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener {
DocumentEventImpl event = (DocumentEventImpl)e;
final Document document = myEditor.getDocument();
boolean performSoftWrapAdjustment = e.getNewLength() > 0 // We want to put caret just after the last added symbol
// There is a possible case that the user removes text just before the soft wrap. We want to keep caret
// on a visual line with soft wrap start then.
|| myEditor.getSoftWrapModel().getSoftWrap(e.getOffset()) != null;
if (event.isWholeTextReplaced()) {
int newLength = document.getTextLength();
if (myOffset == newLength - e.getNewLength() + e.getOldLength() || newLength == 0) {
moveToOffset(newLength);
moveToOffset(newLength, performSoftWrapAdjustment);
}
else {
final int line = event.translateLineViaDiff(myLogicalCaret.line);
moveToLogicalPosition(new LogicalPosition(line, myLogicalCaret.column));
moveToLogicalPosition(new LogicalPosition(line, myLogicalCaret.column), performSoftWrapAdjustment);
}
}
else {
@@ -435,9 +456,8 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener {
newOffset = Math.min(newOffset, document.getTextLength());
//TODO:ask max about this code
// if (newOffset != myOffset) {
moveToOffset(newOffset);
moveToOffset(newOffset, performSoftWrapAdjustment);
//}
//else {
// moveToVisualPosition(oldPosition);
@@ -50,6 +50,7 @@ import com.intellij.openapi.editor.highlighter.HighlighterClient;
import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
import com.intellij.openapi.editor.impl.event.MarkupModelListener;
import com.intellij.openapi.editor.impl.softwrap.SoftWrapDrawingType;
import com.intellij.openapi.editor.impl.softwrap.SoftWrapHelper;
import com.intellij.openapi.editor.markup.*;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.progress.ProgressManager;
@@ -294,6 +295,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
myDocument.addDocumentListener(myCaretModel);
myDocument.addDocumentListener(mySelectionModel);
myDocument.addDocumentListener(myEditorDocumentAdapter);
myDocument.addDocumentListener(mySoftWrapModel);
myIndentsModel = new IndentsModelImpl(this);
myCaretModel.addCaretListener(new CaretListener() {
@@ -522,6 +524,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
myDocument.removeDocumentListener(myFoldingModel);
myDocument.removeDocumentListener(myCaretModel);
myDocument.removeDocumentListener(mySelectionModel);
myDocument.removeDocumentListener(mySoftWrapModel);
MarkupModelEx markupModel = (MarkupModelEx)myDocument.getMarkupModel(myProject, false);
if (markupModel instanceof MarkupModelImpl) {
@@ -1126,7 +1129,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
}
// We need to consider 'before soft wrap drawing'.
TextChange softWrap = getSoftWrapModel().getSoftWrap(offset);
if (softWrap != null) {
if (softWrap != null && offset > startOffset) {
column++;
x += getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED);
if (column >= length) {
@@ -1585,6 +1588,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
@SuppressWarnings({"StatementWithEmptyBody"})
private void paintBackgrounds(Graphics g, Rectangle clip) {
boolean locateBeforeSoftWrap = !SoftWrapHelper.isCaretAfterSoftWrap(this);
Color defaultBackground = getBackgroundColor();
g.setColor(defaultBackground);
g.fillRect(clip.x, clip.y, clip.width, clip.height);
@@ -1702,6 +1706,22 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight, defaultBackground);
}
// Perform additional activity if soft wrap is added or removed during repainting.
if (mySoftWrapsChanged) {
mySoftWrapsChanged = false;
validateSize();
// Repaint editor to the bottom in order to ensure that its content is shown correctly after new soft wrap introduction.
repaintToScreenBottom(xyToLogicalPosition(position).line);
// Repaint gutter at all space that is located after active clip in order to ensure that line numbers are correctly redrawn
// in accordance with the newly introduced soft wrap(s).
myGutterComponent.repaint(0, clip.y, myGutterComponent.getWidth(), myGutterComponent.getHeight() - clip.y);
// Ask caret model to update visual caret position.
getCaretModel().moveToOffset(getCaretModel().getOffset(), locateBeforeSoftWrap);
}
}
private void paintRectangularSelection(Graphics g) {
@@ -1756,7 +1776,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
if (startToUse < softWrapStart) {
position.x = drawBackground(g, backColor, text.subSequence(startToUse, softWrapStart), position, fontType, defaultBackground, clip);
}
drawSoftWrap(g, backColor, softWrap, position, fontType, defaultBackground, clip);
drawSoftWrap(g, softWrap, position, fontType, defaultBackground, clip);
startToUse = softWrapStart;
}
@@ -1766,37 +1786,61 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
return position.x;
}
private void drawSoftWrap(Graphics g, Color backColor, TextChange softWrap, Point position,
int fontType, Color defaultBackground, Rectangle clip)
{
position.x = drawBackground(
g, backColor, getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED), position,
defaultBackground, clip
);
private void drawSoftWrap(Graphics g, TextChange softWrap, Point position, int fontType, Color defaultBackground, Rectangle clip) {
// The main idea is to to do the following:
// *) update given drawing position coordinates in accordance with the current soft wrap;
// *) draw 'active line' background at soft wrap-introduced virtual space if necessary;
CharSequence softWrapText = softWrap.getText();
int start = 0;
for (
int end = CharArrayUtil.shiftForwardUntil(softWrapText, start, "\n");
start < softWrapText.length() && end < softWrapText.length();
end = CharArrayUtil.shiftForwardUntil(softWrapText, start, "\n"))
{
drawBackground(g, backColor, softWrapText.subSequence(start, end), position, fontType, defaultBackground, clip);
start = end + 1;
position.x = 0;
position.y += getLineHeight();
int activeRowY = getCaretModel().getVisualPosition().line * getLineHeight();
if (position.y == activeRowY) {
// Draw 'active line' background after soft wrap.
Color caretRowColor = getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR);
drawBackground(g, caretRowColor, clip.x + clip.width - position.x, position, defaultBackground, clip);
}
if (start < softWrapText.length()) {
position.x = drawBackground(
g, backColor, softWrapText.subSequence(start, softWrapText.length()), position, fontType, defaultBackground, clip
);
int i = CharArrayUtil.lastIndexOf(softWrapText, "\n", softWrapText.length()) + 1;
position.x = getTextSegmentWidth(softWrapText.subSequence(i, softWrapText.length()), 0, fontType, clip);
position.x += getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP);
position.y += getLineHeight();
if (position.y == activeRowY) {
// Draw 'active line' background for the soft wrap-introduced virtual space.
Color caretRowColor = getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR);
drawBackground(g, caretRowColor, position.x, new Point(0, activeRowY), defaultBackground, clip);
}
position.x = drawBackground(
g, backColor, getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP), position,
defaultBackground, clip
);
// The code below draws background for soft wrap-introduced virtual space. It's is considered that we don't want
// to do that for now. Uncomment if that decision is changed.
//position.x = drawBackground(
// g, backColor, getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED), position,
// defaultBackground, clip
//);
//
//CharSequence softWrapText = softWrap.getText();
//int start = 0;
//for (
// int end = CharArrayUtil.shiftForwardUntil(softWrapText, start, "\n");
// start < softWrapText.length() && end < softWrapText.length();
// end = CharArrayUtil.shiftForwardUntil(softWrapText, start, "\n"))
//{
// drawBackground(g, backColor, softWrapText.subSequence(start, end), position, fontType, defaultBackground, clip);
// start = end + 1;
// position.x = 0;
// position.y += getLineHeight();
//}
//
//if (start < softWrapText.length()) {
// position.x = drawBackground(
// g, backColor, softWrapText.subSequence(start, softWrapText.length()), position, fontType, defaultBackground, clip
// );
//}
//
//position.x = drawBackground(
// g, backColor, getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP), position,
// defaultBackground, clip
//);
}
private int drawBackground(Graphics g, Color backColor, CharSequence text, Point position, int fontType, Color defaultBackground,
@@ -1861,7 +1905,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
// We use AtomicReference here just as a holder for LogicalPosition
// The main idea is that there is a possible case that we need to perform painting starting from soft-wrapped logical line.
// We may want to skip necessary of visual lines then. Hence, we remember logical position that corresponds to the starting
// We may want to skip necessary number of visual lines then. Hence, we remember logical position that corresponds to the starting
// visual line in order to use it for further processing. As soon as necessary number of visual lines is skipped, logical
// position is expected to be set to null as an indication that no soft wrap-introduced visual lines should be skipped on
// current painting iteration.
@@ -1975,22 +2019,6 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
}
flushCachedChars(g);
// Perform additional activity if soft wrap is added or removed during repainting.
if (mySoftWrapsChanged) {
mySoftWrapsChanged = false;
validateSize();
// Repaint editor to the bottom in order to ensure that its content is shown correctly after new soft wrap introduction.
repaintToScreenBottom(xyToLogicalPosition(position).line);
// Repaint gutter at all space that is located after active clip in order to ensure that line numbers are correctly redrawn
// in accordance with the newly introduced soft wrap(s).
myGutterComponent.repaint(0, clip.y, myGutterComponent.getWidth(), myGutterComponent.getHeight() - clip.y);
// Ask caret model to update visual caret position.
getCaretModel().moveToOffset(getCaretModel().getOffset());
}
}
private boolean paintSelection() {
@@ -3536,7 +3564,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
super(orientation);
}
void setPersistendUI(ScrollBarUI ui) {
void setPersistentUI(ScrollBarUI ui) {
myPersistentUI = ui;
setUI(ui);
}
@@ -463,10 +463,10 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark
public void setErrorStripeVisible(boolean val) {
if (val) {
myEditor.getVerticalScrollBar().setPersistendUI(new MyErrorPanel());
myEditor.getVerticalScrollBar().setPersistentUI(new MyErrorPanel());
}
else {
myEditor.getVerticalScrollBar().setPersistendUI(ButtonlessScrollBarUI.createNormal());
myEditor.getVerticalScrollBar().setPersistentUI(ButtonlessScrollBarUI.createNormal());
}
}
@@ -16,6 +16,8 @@
package com.intellij.openapi.editor.impl;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.SoftWrapChangeListener;
import com.intellij.openapi.editor.ex.SoftWrapModelEx;
@@ -27,6 +29,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -41,7 +44,9 @@ import java.util.List;
* @author Denis Zhdanov
* @since Jun 8, 2010 12:47:32 PM
*/
public class SoftWrapModelImpl implements SoftWrapModelEx {
public class SoftWrapModelImpl implements SoftWrapModelEx, DocumentListener {
private final List<DocumentListener> myDocumentListeners = new ArrayList<DocumentListener>();
private final SoftWrapDataMapper myDataMapper;
private final SoftWrapsStorage myStorage;
@@ -69,6 +74,14 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
);
}
public SoftWrapModelImpl(@NotNull EditorEx editor, @NotNull SoftWrapsStorage storage, @NotNull SoftWrapPainter painter,
@NotNull DefaultSoftWrapApplianceManager applianceManager, @NotNull SoftWrapDataMapper dataMapper,
@NotNull SoftWrapDocumentChangeManager documentChangeManager)
{
this(editor, storage, painter, (SoftWrapApplianceManager)applianceManager, dataMapper, documentChangeManager);
myDocumentListeners.add(applianceManager.getDocumentListener());
}
public SoftWrapModelImpl(@NotNull EditorEx editor, @NotNull SoftWrapsStorage storage, @NotNull SoftWrapPainter painter,
@NotNull SoftWrapApplianceManager applianceManager, @NotNull SoftWrapDataMapper dataMapper,
@NotNull SoftWrapDocumentChangeManager documentChangeManager)
@@ -79,6 +92,8 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
myApplianceManager = applianceManager;
myDataMapper = dataMapper;
myDocumentChangeManager = documentChangeManager;
myDocumentListeners.add(myDocumentChangeManager);
}
public boolean isSoftWrappingEnabled() {
@@ -349,26 +364,35 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
return result;
}
@Override
public void beforeDocumentChangeAtCaret() {
CaretModel caretModel = myEditor.getCaretModel();
VisualPosition visualCaretPosition = caretModel.getVisualPosition();
if (!isInsideSoftWrap(visualCaretPosition)) {
return;
}
int offset = caretModel.getOffset();
TextChangeImpl softWrap = myStorage.getSoftWrap(offset);
if (softWrap == null) {
return;
if (myDocumentChangeManager.makeHardWrap(caretModel.getOffset())) {
// Restore caret position.
caretModel.moveToVisualPosition(visualCaretPosition);
}
myDocumentChangeManager.makeHardWrap(softWrap);
// Restore caret position.
caretModel.moveToVisualPosition(visualCaretPosition);
}
@Override
public boolean addSoftWrapChangeListener(@NotNull SoftWrapChangeListener listener) {
return myStorage.addSoftWrapChangeListener(listener);
}
@Override
public void beforeDocumentChange(DocumentEvent event) {
for (DocumentListener listener : myDocumentListeners) {
listener.beforeDocumentChange(event);
}
}
@Override
public void documentChanged(DocumentEvent event) {
for (DocumentListener listener : myDocumentListeners) {
listener.documentChanged(event);
}
}
}
@@ -45,10 +45,10 @@ import static java.util.Arrays.asList;
public class CompositeSoftWrapPainter implements SoftWrapPainter {
private static final List<Map<SoftWrapDrawingType, Character>> SYMBOLS = asList(
asMap(asList(BEFORE_SOFT_WRAP_LINE_FEED, AFTER_SOFT_WRAP),
asList('\uE48B', '\uE48C')),
asMap(asList(BEFORE_SOFT_WRAP_LINE_FEED, AFTER_SOFT_WRAP),
asList('\u2926', '\u2925')),
asMap(asList(BEFORE_SOFT_WRAP_LINE_FEED, AFTER_SOFT_WRAP),
asList('\uE48B', '\uE48C')),
asMap(asList(BEFORE_SOFT_WRAP_LINE_FEED, AFTER_SOFT_WRAP),
asList('\u21B2', '\u21B3')),
asMap(asList(BEFORE_SOFT_WRAP_LINE_FEED, AFTER_SOFT_WRAP),
@@ -18,6 +18,7 @@ package com.intellij.openapi.editor.impl.softwrap;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.VisualPosition;
import com.intellij.openapi.editor.actions.EditorActionUtil;
import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.impl.EditorTextRepresentationHelper;
@@ -58,7 +59,6 @@ import java.nio.CharBuffer;
*/
public class DefaultSoftWrapApplianceManager implements SoftWrapApplianceManager {
/** Enumerates possible type of soft wrap indents to use. */
enum IndentType {
/** Don't apply special indent to soft-wrapped line at all. */
@@ -103,6 +103,18 @@ public class DefaultSoftWrapApplianceManager implements SoftWrapApplianceManager
}
private final TIntHashSet myProcessedLogicalLines = new TIntHashSet();
private final DocumentListener myDocumentListener = new LineOrientedDocumentChangeAdapter() {
@Override
public void beforeDocumentChange(int startLine, int endLine, int symbolsDifference) {
for (int i = startLine; i <= endLine; i++) {
myProcessedLogicalLines.remove(i);
}
}
@Override
public void afterDocumentChange(int startLine, int endLine, int symbolsDifference) {
}
};
private final EditorTextRepresentationHelper myTextRepresentationHelper;
private final SoftWrapsStorage myStorage;
@@ -120,7 +132,6 @@ public class DefaultSoftWrapApplianceManager implements SoftWrapApplianceManager
myEditor = editor;
myPainter = painter;
myTextRepresentationHelper = textRepresentationHelper;
init(editor.getDocument());
}
@SuppressWarnings({"AssignmentToForLoopParameter"})
@@ -147,19 +158,8 @@ public class DefaultSoftWrapApplianceManager implements SoftWrapApplianceManager
}
}
private void init(final Document document) {
document.addDocumentListener(new LineOrientedDocumentChangeAdapter() {
@Override
public void beforeDocumentChange(int startLine, int endLine, int symbolsDifference) {
for (int i = startLine; i <= endLine; i++) {
myProcessedLogicalLines.remove(i);
}
}
@Override
public void afterDocumentChange(int startLine, int endLine, int symbolsDifference) {
}
});
public DocumentListener getDocumentListener() {
return myDocumentListener;
}
private void dropDataIfNecessary() {
@@ -17,11 +17,12 @@ package com.intellij.openapi.editor.impl.softwrap;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
import gnu.trove.TIntHashSet;
import gnu.trove.TIntProcedure;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
@@ -30,9 +31,9 @@ import java.util.List;
* @author Denis Zhdanov
* @since Jul 7, 2010 2:28:10 PM
*/
public class SoftWrapDocumentChangeManager {
public class SoftWrapDocumentChangeManager implements DocumentListener {
private final List<DeferredChange> myDeferredChanges = new ArrayList<DeferredChange>();
private final TIntHashSet mySoftWrapsToRemoveIndices = new TIntHashSet();
private final SoftWrapsStorage myStorage;
private final Editor myEditor;
@@ -40,16 +41,23 @@ public class SoftWrapDocumentChangeManager {
public SoftWrapDocumentChangeManager(@NotNull Editor editor, @NotNull SoftWrapsStorage storage) {
myStorage = storage;
myEditor = editor;
init(editor.getDocument());
}
/**
* Performs {@code 'soft wrap' -> 'hard wrap'} conversion for the given soft wrap.
* Performs {@code 'soft wrap' -> 'hard wrap'} conversion for soft wrap at the given offset if any.
*
* @param softWrap soft wrap to make hard wrap
* @param offset offset that may point to soft wrap to make hard wrap
* @return <code>true</code> if given offset points to soft wrap that was made hard wrap; <code>false</code> otherwise
*/
public void makeHardWrap(@NotNull TextChangeImpl softWrap) {
public boolean makeHardWrap(int offset) {
TextChangeImpl softWrap = myStorage.getSoftWrap(offset);
if (softWrap == null) {
return false;
}
myStorage.removeByIndex(myStorage.getSoftWrapIndex(offset));
myEditor.getDocument().replaceString(softWrap.getStart(), softWrap.getEnd(), softWrap.getText());
return true;
}
/**
@@ -59,74 +67,63 @@ public class SoftWrapDocumentChangeManager {
* until this method is called.
*/
public void syncSoftWraps() {
Document document = myEditor.getDocument();
TIntHashSet softWrapsToRemoveIndices = new TIntHashSet();
// Update offsets for soft wraps that remain after the changed line(s).
List<TextChangeImpl> softWraps = myStorage.getSoftWraps();
for (DeferredChange change : myDeferredChanges) {
if (change.startOffset >= document.getTextLength()) {
continue;
}
int index = myStorage.getSoftWrapIndex(change.startOffset);
if (index < 0) {
index = -index -1;
}
for (int i = index; i < softWraps.size(); i++) {
TextChangeImpl softWrap = softWraps.get(i);
if (softWrapsToRemoveIndices.contains(i)) {
continue;
}
if (softWrap.getStart() < change.endOffset || softWrap.getStart() >= document.getTextLength()) {
softWrapsToRemoveIndices.add(i);
continue;
}
softWrap.advance(change.symbolsDifference);
}
}
// Removes soft wraps from changed lines.
softWrapsToRemoveIndices.forEach(new TIntProcedure() {
mySoftWrapsToRemoveIndices.forEach(new TIntProcedure() {
@Override
public boolean execute(int value) {
myStorage.removeByIndex(value);
return true;
}
});
myDeferredChanges.clear();
mySoftWrapsToRemoveIndices.clear();
}
private void init(final Document document) {
document.addDocumentListener(new LineOrientedDocumentChangeAdapter() {
@Override
public void beforeDocumentChange(int startLine, int endLine, int symbolsDifference) {
myDeferredChanges.add(
new DeferredChange(document.getLineStartOffset(startLine), document.getLineEndOffset(endLine), symbolsDifference)
);
}
@Override
public void beforeDocumentChange(DocumentEvent event) {
// Mark soft wraps to be removed.
Document document = myEditor.getDocument();
int startLine = document.getLineNumber(event.getOffset());
int startOffset = document.getLineStartOffset(startLine);
int endLine = document.getLineNumber(event.getOffset() + event.getOldLength());
int endOffset = document.getLineEndOffset(endLine);
markSoftWrapsForDeletion(startOffset, endOffset);
@Override
public void afterDocumentChange(int startLine, int endLine, int symbolsDifference) {
}
});
// Update offsets for soft wraps that remain after the changed line(s).
applyDocumentChangeDiff(event);
}
private static class DeferredChange {
final int startOffset;
final int endOffset;
final int symbolsDifference;
@Override
public void documentChanged(DocumentEvent event) {
}
DeferredChange(int startOffset, int endOffset, int symbolsDifference) {
this.startOffset = startOffset;
this.endOffset = endOffset;
this.symbolsDifference = symbolsDifference;
private void markSoftWrapsForDeletion(int startOffset, int endOffset) {
List<TextChangeImpl> softWraps = myStorage.getSoftWraps();
int index = myStorage.getSoftWrapIndex(startOffset);
if (index < 0) {
index = -index - 1;
}
for (int i = index; i < softWraps.size(); i++) {
TextChangeImpl softWrap = softWraps.get(i);
if (softWrap.getStart() >= endOffset) {
break;
}
mySoftWrapsToRemoveIndices.add(i);
}
}
private void applyDocumentChangeDiff(DocumentEvent event) {
List<TextChangeImpl> softWraps = myStorage.getSoftWraps();
// We use 'offset + 1' here because soft wrap is represented before the document symbol at the same offset, hence, document
// modification at particular offset doesn't affect soft wrap registered for the same offset.
int index = myStorage.getSoftWrapIndex(event.getOffset() + 1);
if (index < 0) {
index = -index - 1;
}
@Override
public String toString() {
return startOffset + "-" + endOffset + ": " + symbolsDifference;
int diff = event.getNewLength() - event.getOldLength();
for (int i = index; i < softWraps.size(); i++) {
TextChangeImpl softWrap = softWraps.get(i);
softWrap.advance(diff);
}
}
}
@@ -0,0 +1,91 @@
/*
* 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.CaretModel;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.SoftWrapModel;
import com.intellij.openapi.editor.TextChange;
/**
* Holds utility methods for soft wraps-related processing.
*
* @author Denis Zhdanov
* @since Aug 11, 2010 11:03:43 AM
*/
public class SoftWrapHelper {
private SoftWrapHelper() {
}
/**
* Every soft wrap implies that multiple visual positions correspond to the same document offset. We can classify
* such positions by the following criteria:
* <pre>
* <ul>
* <li>positions from visual line with soft wrap start;</li>
* <li>positions from visual line with soft wrap end;</li>
* </ul>
* </pre>
* <p/>
* This method allows to answer if caret offset of the given editor points to soft wrap and visual caret position
* belongs to the visual line where soft wrap start is located.
*
* @param editor target editor
* @return <code>true</code> if caret offset of the given editor points to visual position that belongs to
* visual line before the soft wrap
*/
public static boolean isCaretBeforeSoftWrap(Editor editor) {
CaretModel caretModel = editor.getCaretModel();
SoftWrapModel softWrapModel = editor.getSoftWrapModel();
int offset = caretModel.getOffset();
TextChange softWrap = softWrapModel.getSoftWrap(offset);
if (softWrap == null) {
return false;
}
return editor.offsetToVisualPosition(offset).line != caretModel.getVisualPosition().line;
}
/**
* Every soft wrap implies that multiple visual positions correspond to the same document offset. We can classify
* such positions by the following criteria:
* <pre>
* <ul>
* <li>positions from visual line with soft wrap start;</li>
* <li>positions from visual line with soft wrap end;</li>
* </ul>
* </pre>
* <p/>
* This method allows to answer if caret offset of the given editor points to soft wrap and visual caret position
* belongs to the visual line where soft wrap end is located.
*
* @param editor target editor
* @return <code>true</code> if caret offset of the given editor points to visual position that belongs to
* visual line where soft wrap end is located
*/
public static boolean isCaretAfterSoftWrap(Editor editor) {
CaretModel caretModel = editor.getCaretModel();
SoftWrapModel softWrapModel = editor.getSoftWrapModel();
int offset = caretModel.getOffset();
TextChange softWrap = softWrapModel.getSoftWrap(offset);
if (softWrap == null) {
return false;
}
return editor.offsetToVisualPosition(offset).line == caretModel.getVisualPosition().line;
}
}
@@ -134,8 +134,7 @@ public class TextChangeImpl implements TextChange {
}
/**
* Creates new {@link TextChange} on the basis of the current object with given offset applied to its {@link #getStart() start}
* and {@link #getEnd() end} properties.
* Applies given offset applied to the {@link #getStart() start} and {@link #getEnd() end} properties of current text change object.
*
* @param offset offset to apply to the current change object
* @return text change that is built on the basis of the current object that with {@link #getStart() start}
@@ -44,14 +44,19 @@ public class TextComponentCaretModel implements CaretModel {
}
public void moveToLogicalPosition(final LogicalPosition pos) {
moveToOffset(myEditor.logicalPositionToOffset(pos));
moveToOffset(myEditor.logicalPositionToOffset(pos), false);
}
public void moveToVisualPosition(final VisualPosition pos) {
moveToLogicalPosition(myEditor.visualToLogicalPosition(pos));
}
public void moveToOffset(final int offset) {
@Override
public void moveToOffset(int offset) {
moveToOffset(offset, false);
}
public void moveToOffset(final int offset, boolean locateBeforeSoftWrap) {
myTextComponent.setCaretPosition(Math.min(offset, myTextComponent.getText().length()));
}