diff --git a/platform/lang-impl/src/com/intellij/injected/editor/CaretModelWindow.java b/platform/lang-impl/src/com/intellij/injected/editor/CaretModelWindow.java index 7e11bf4e3554..0ca9958af275 100644 --- a/platform/lang-impl/src/com/intellij/injected/editor/CaretModelWindow.java +++ b/platform/lang-impl/src/com/intellij/injected/editor/CaretModelWindow.java @@ -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() { diff --git a/platform/platform-api/src/com/intellij/openapi/editor/CaretModel.java b/platform/platform-api/src/com/intellij/openapi/editor/CaretModel.java index 0b56a2bad9ff..84c5ad3c2ca3 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/CaretModel.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/CaretModel.java @@ -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 'false' 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. + * Note: 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. * diff --git a/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java b/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java index 1371104f5af3..13279c0295c9 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java @@ -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(); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/actions/BackspaceAction.java b/platform/platform-impl/src/com/intellij/openapi/editor/actions/BackspaceAction.java index 24327465e86e..86b8b771ee11 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/actions/BackspaceAction.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/actions/BackspaceAction.java @@ -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) { diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/actions/DeleteAction.java b/platform/platform-impl/src/com/intellij/openapi/editor/actions/DeleteAction.java index af8466bd9231..6bfb0a282bed 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/actions/DeleteAction.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/actions/DeleteAction.java @@ -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()) diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java index 2dc7161ef06e..503ff6715dfd 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java @@ -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); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java index 90778c6723a0..1e07bbfe6381 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java @@ -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); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java index 405de6210f6f..0bacaf8c4731 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java @@ -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()); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java index c1283a0b4a02..1c8967076f5f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java @@ -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 myDocumentListeners = new ArrayList(); 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); + } + } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/CompositeSoftWrapPainter.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/CompositeSoftWrapPainter.java index b5eef3985a10..57d9ba6c69bc 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/CompositeSoftWrapPainter.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/CompositeSoftWrapPainter.java @@ -45,10 +45,10 @@ import static java.util.Arrays.asList; public class CompositeSoftWrapPainter implements SoftWrapPainter { private static final List> 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), diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/DefaultSoftWrapApplianceManager.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/DefaultSoftWrapApplianceManager.java index d3583b094aa7..5ba742fd8b46 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/DefaultSoftWrapApplianceManager.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/DefaultSoftWrapApplianceManager.java @@ -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() { diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapDocumentChangeManager.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapDocumentChangeManager.java index ac32e135de49..4ca0cf4f654f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapDocumentChangeManager.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapDocumentChangeManager.java @@ -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 myDeferredChanges = new ArrayList(); + 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 true if given offset points to soft wrap that was made hard wrap; false 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 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 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 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); } } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapHelper.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapHelper.java new file mode 100644 index 000000000000..f213fac8ac36 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapHelper.java @@ -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: + *
+   * 
    + *
  • positions from visual line with soft wrap start;
  • + *
  • positions from visual line with soft wrap end;
  • + *
+ *
+ *

+ * 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 true 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: + *

+   * 
    + *
  • positions from visual line with soft wrap start;
  • + *
  • positions from visual line with soft wrap end;
  • + *
+ *
+ *

+ * 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 true 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; + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/TextChangeImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/TextChangeImpl.java index d2f62f48c029..0a676dcb94f4 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/TextChangeImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/TextChangeImpl.java @@ -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} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentCaretModel.java b/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentCaretModel.java index 0e133324936d..79439578f2ad 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentCaretModel.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentCaretModel.java @@ -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())); }