IDEA-57125 Soft wrap: Throwable at EditorActionUtil.moveCaretToLineEnd() on pressing End in a wrapped line with tab characters if the last "word" in the line is a single symbol

This commit is contained in:
Denis Zhdanov
2010-08-04 16:00:08 +04:00
parent 55adcd688e
commit 970c9b47dc
11 changed files with 76 additions and 40 deletions
@@ -244,6 +244,19 @@ public class EditorModificationUtil {
int anchorLineEndOffset = document.getLineEndOffset(lineNumber);
List<? extends TextChange> softWraps = editor.getSoftWrapModel().getSoftWrapsForLine(logicalPosition.line);
for (TextChange softWrap : softWraps) {
if (!editor.getSoftWrapModel().isVisible(softWrap)) {
continue;
}
if (softWrap.getStart() == caretOffset) {
// There are two possible situations:
// *) caret is located on a visual line before soft wrap-introduced line feed;
// *) caret is located on a visual line after soft wrap-introduced line feed;
VisualPosition position = editor.offsetToVisualPosition(caretOffset - 1);
VisualPosition visualCaret = caretModel.getVisualPosition();
if (position.line == visualCaret.line) {
return visualCaret.column - position.column - 1;
}
}
if (softWrap.getStart() > caretOffset) {
anchorLineEndOffset = softWrap.getStart();
break;
@@ -106,6 +106,16 @@ public interface SoftWrapModel {
@NotNull
List<? extends TextChange> getSoftWrapsForLine(int documentLine);
/**
* Allows to answer if given soft wrap is shown.
* <p/>
* The soft wrap may be not shown if it's located, for example, inside collapsed folding region.
*
* @param softWrap soft wrap to check
* @return <code>true</code> if given soft wrap is visible; <code>false</code> otherwise
*/
boolean isVisible(TextChange softWrap);
/**
* Notifies current model that target document is about to be changed at current caret location.
* <p/>
@@ -215,7 +215,7 @@ public class EditorActionUtil {
if (currentVisCaret.line > caretLogLineStartVis.line) {
// Caret is located not at the first visual line of soft-wrapped logical line.
moveCaretToStartOfSoftWrappedLine(editor, currentVisCaret);
moveCaretToStartOfSoftWrappedLine(editor, currentVisCaret, currentVisCaret.line - caretLogLineStartVis.line);
setupSelection(editor, isWithSelection, selectionStart, blockSelectionStart);
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
return;
@@ -249,7 +249,7 @@ public class EditorActionUtil {
LogicalPosition logLineEndLog = editor.offsetToLogicalPosition(document.getLineEndOffset(logLineToUse));
VisualPosition logLineEndVis = editor.logicalToVisualPosition(logLineEndLog);
if (logLineEndLog.softWrapLinesOnCurrentLogicalLine > 0) {
moveCaretToStartOfSoftWrappedLine(editor, logLineEndVis);
moveCaretToStartOfSoftWrappedLine(editor, logLineEndVis, logLineEndLog.softWrapLinesOnCurrentLogicalLine);
}
else {
int line = logLineEndVis.line;
@@ -265,7 +265,7 @@ public class EditorActionUtil {
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
}
private static void moveCaretToStartOfSoftWrappedLine(Editor editor, VisualPosition currentVisual) {
private static void moveCaretToStartOfSoftWrappedLine(Editor editor, VisualPosition currentVisual, int softWrappedLines) {
CaretModel caretModel = editor.getCaretModel();
int line = currentVisual.line;
@@ -275,8 +275,17 @@ public class EditorActionUtil {
if (currentVisual.column <= 1) {
line--;
softWrappedLines--;
// There is a possible case that caret is located at the start of the second visual line of soft-wrapped line.
// Hence, it should be moved to the start of the previous visual line which anchor column is not '1'
// (after soft wrap drawing) but '0'.
int nonSpaceColumn = findFirstNonSpaceColumnOnTheLine(editor, line);
column = nonSpaceColumn >= 1 ? nonSpaceColumn : 1;
if (softWrappedLines <= 0) {
column = nonSpaceColumn >= 0 ? nonSpaceColumn : 0;
}
else {
column = nonSpaceColumn >= 1 ? nonSpaceColumn : 1;
}
}
else {
int nonSpaceColumn = findFirstNonSpaceColumnOnTheLine(editor, currentVisual.line);
@@ -317,7 +317,7 @@ public class EditorUtil {
if (column == columnNumber) {
return offset;
}
if (column > columnNumber && text.charAt(offset) == '\t') {
if (column > columnNumber && offset > 0 && text.charAt(offset - 1) == '\t') {
return offset - 1;
}
currentColumn.set(column);
@@ -419,15 +419,18 @@ public class EditorUtil {
public static int textWidthInColumns(@NotNull Editor editor, CharSequence text, int start, int end, int x) {
int result = 0;
int prevX;
int spaceSize = getSpaceWidth(Font.PLAIN, editor);
for (int i = start; i < end; i++) {
char c = text.charAt(i);
prevX = x;
switch (c) {
case '\t': x = nextTabStop(x, editor); break;
case '\t':
x = nextTabStop(x, editor);
result += columnsNumber(x - prevX, spaceSize);
break;
case '\n': x = result = 0; break;
default: x += charWidth(c, Font.PLAIN, editor);
default: x += charWidth(c, Font.PLAIN, editor); result++;
}
result += columnsNumber(c, x, prevX, getSpaceWidth(Font.PLAIN, editor));
}
return result;
}
@@ -38,11 +38,6 @@ public class DefaultEditorTextRepresentationHelper implements EditorTextRepresen
return EditorUtil.textWidthInColumns(myEditor, text, start, end, x);
}
@Override
public int toVisualColumnSymbolsNumber(int width) {
return EditorUtil.columnsNumber(width, EditorUtil.getSpaceWidth(Font.PLAIN, myEditor));
}
@Override
public int textWidth(@NotNull CharSequence text, int start, int end, int x) {
return EditorUtil.textWidth(myEditor, text, start, end, Font.PLAIN, x);
@@ -1033,7 +1033,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
int linesToSkip = logical.softWrapLinesOnCurrentLogicalLine;
List<? extends TextChange> softWraps = getSoftWrapModel().getSoftWrapsForLine(logLine);
for (TextChange softWrap : softWraps) {
if (myFoldingModel.isOffsetCollapsed(softWrap.getStart())) {
if (myFoldingModel.isOffsetCollapsed(softWrap.getStart()) && myFoldingModel.isOffsetCollapsed(softWrap.getStart() - 1)) {
continue;
}
int lineFeeds = StringUtil.countNewLines(softWrap.getText());
@@ -1061,7 +1061,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
}
}
if (logLine <= 0) {
if (logLine < 0) {
lineStartOffset = 0;
}
else if (lineStartOffset < 0) {
@@ -41,14 +41,6 @@ public interface EditorTextRepresentationHelper {
*/
int toVisualColumnSymbolsNumber(@NotNull CharSequence text, int start, int end, int x);
/**
* Allows to answer how many visual columns is necessary for representing text of the given width.
*
* @param width target width
* @return number of visual columns necessary for representation of the text with the given width
*/
int toVisualColumnSymbolsNumber(int width);
/**
* Allows to retrieve width (in pixels) necessary to represent given region (<code>[start; end)</code>) starting
* at the given <code>'x'</code> offset from visual line start using given font type.
@@ -17,6 +17,7 @@ package com.intellij.openapi.editor.impl;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.FoldingModelEx;
import com.intellij.openapi.editor.ex.SoftWrapChangeListener;
import com.intellij.openapi.editor.ex.SoftWrapModelEx;
import com.intellij.openapi.editor.ex.util.EditorUtil;
@@ -168,6 +169,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
}
}
@Override
public List<? extends TextChange> getRegisteredSoftWraps() {
if (!isSoftWrappingEnabled()) {
return Collections.emptyList();
@@ -175,6 +177,20 @@ public class SoftWrapModelImpl implements SoftWrapModelEx {
return myStorage.getSoftWraps();
}
@Override
public boolean isVisible(TextChange softWrap) {
FoldingModel foldingModel = myEditor.getFoldingModel();
int start = softWrap.getStart();
if (!foldingModel.isOffsetCollapsed(start)) {
return false;
}
// There is a possible case that soft wrap and collapsed folding region share the same offset, i.e. soft wrap is represented
// before the folding. We need to return 'true' in such situation. Hence, we check if offset just before the soft wrap
// is collapsed as well.
return start <= 0 || !foldingModel.isOffsetCollapsed(start - 1);
}
@Override
public int paint(@NotNull Graphics g, @NotNull SoftWrapDrawingType drawingType, int x, int y, int lineHeight) {
if (!isSoftWrappingEnabled()) {
@@ -264,9 +264,8 @@ public class SoftWrapDataMapper {
int i = CharArrayUtil.shiftBackwardUntil(text, region.getEndOffset() - 1, "\n");
// Process multi-line folding.
if (i >= region.getStartOffset()) {
int width = myTextRepresentationHelper.textWidth(text, i + 1, region.getEndOffset(), 0);
afterFolding.logicalColumn = myTextRepresentationHelper.toVisualColumnSymbolsNumber(width);
afterFolding.x = width;
afterFolding.x = myTextRepresentationHelper.textWidth(text, i + 1, region.getEndOffset(), 0);
afterFolding.logicalColumn = myTextRepresentationHelper.toVisualColumnSymbolsNumber(text, i + 1, region.getEndOffset(), 0);
afterFolding.softWrapLinesBefore += afterFolding.softWrapLinesCurrent;
afterFolding.softWrapLinesCurrent = 0;
afterFolding.softWrapColumnDiff = 0;
@@ -276,7 +275,9 @@ public class SoftWrapDataMapper {
// Process single-line folding
else {
int width = myTextRepresentationHelper.textWidth(text, region.getStartOffset(), region.getEndOffset(), context.x);
int logicalColumnInc = myTextRepresentationHelper.toVisualColumnSymbolsNumber(width);
int logicalColumnInc = myTextRepresentationHelper.toVisualColumnSymbolsNumber(
text, region.getStartOffset(), region.getEndOffset(), context.x
);
afterFolding.logicalColumn += logicalColumnInc;
afterFolding.x += width;
afterFolding.foldingColumnDiff += visualColumnInc - logicalColumnInc;
@@ -342,19 +343,20 @@ public class SoftWrapDataMapper {
// Update state to the offset that corresponds to the same logical line that was used last time.
if (currentLogicalLine == lastUsedLogicalLine) {
int width = myTextRepresentationHelper.textWidth(text, result.offset, newOffset, result.x);
int columnDiff = myTextRepresentationHelper.toVisualColumnSymbolsNumber(width);
int columnDiff = myTextRepresentationHelper.toVisualColumnSymbolsNumber(text, result.offset, newOffset, result.x);
result.x += width;
result.logicalColumn += columnDiff;
result.visualColumn += columnDiff;
}
// Update state to the start of the folding that doesn't belong to the same logical line that was used last time.
// Update state to offset that doesn't correspond to the same logical line that was used last time.
else {
int lineDiff = currentLogicalLine - lastUsedLogicalLine;
result.logicalLine += lineDiff;
result.visualLine += lineDiff;
int startLineOffset = document.getLineStartOffset(currentLogicalLine);
result.x = myTextRepresentationHelper.textWidth(text, startLineOffset, newOffset, result.x);
result.visualColumn = myTextRepresentationHelper.toVisualColumnSymbolsNumber(result.x);
int newX = myTextRepresentationHelper.textWidth(text, startLineOffset, newOffset, result.x);
result.visualColumn = myTextRepresentationHelper.toVisualColumnSymbolsNumber(text, startLineOffset, newOffset, 0);
result.x = newX;
result.logicalColumn = result.visualColumn;
result.onNewLine();
}
@@ -53,6 +53,11 @@ public class TextComponentSoftWrapModel implements SoftWrapModel {
return Collections.emptyList();
}
@Override
public boolean isVisible(TextChange softWrap) {
return false;
}
@Override
public void beforeDocumentChangeAtCaret() {
}
@@ -858,15 +858,6 @@ public class SoftWrapDataMapperTest {
return result;
}
@Override
public int toVisualColumnSymbolsNumber(int width) {
int result = width / SPACE_SIZE;
if (width % SPACE_SIZE > 0) {
result++;
}
return result;
}
@Override
public int textWidth(@NotNull CharSequence text, int start, int end, int x) {
int result = 0;