mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
@@ -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/>
|
||||
|
||||
+13
-4
@@ -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;
|
||||
}
|
||||
|
||||
-5
@@ -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) {
|
||||
|
||||
-8
@@ -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()) {
|
||||
|
||||
+10
-8
@@ -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();
|
||||
}
|
||||
|
||||
+5
@@ -53,6 +53,11 @@ public class TextComponentSoftWrapModel implements SoftWrapModel {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisible(TextChange softWrap) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeDocumentChangeAtCaret() {
|
||||
}
|
||||
|
||||
-9
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user