mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
correct navigation in bidirectional text using keyboard arrow keys (with caret stopping and switching direction on ltr/rtl boundary)
This commit is contained in:
@@ -302,4 +302,11 @@ public interface Caret extends UserDataHolderEx, Disposable {
|
||||
* to offset and logical column number in the vicinity of caret.
|
||||
*/
|
||||
boolean isAtRtlLocation();
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if caret is located at a boundary between LTR and RTL text fragments. Caret can located at any side of the
|
||||
* boundary, exact location can be determined from directionality flags of caret's logical and visual position
|
||||
* ({@link LogicalPosition#leansForward} and {@link VisualPosition#leansRight}).
|
||||
*/
|
||||
boolean isAtDirectionBoundary();
|
||||
}
|
||||
|
||||
@@ -77,6 +77,13 @@ public class VisualPosition {
|
||||
return line > other.line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <code>VisualPosition</code> instance with a given value of {@link #leansRight} flag.
|
||||
*/
|
||||
public VisualPosition leanRight(boolean value) {
|
||||
return new VisualPosition(line, column, value);
|
||||
}
|
||||
|
||||
@NonNls
|
||||
public String toString() {
|
||||
return "VisualPosition: (" + line + ", " + column+")" + (leansRight ? " leans right" : "");
|
||||
|
||||
@@ -242,4 +242,9 @@ public class InjectedCaret implements Caret {
|
||||
public boolean isAtRtlLocation() {
|
||||
return myDelegate.isAtRtlLocation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAtDirectionBoundary() {
|
||||
return myDelegate.isAtDirectionBoundary();
|
||||
}
|
||||
}
|
||||
|
||||
+10
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
* Copyright 2000-2015 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.
|
||||
@@ -69,8 +69,14 @@ class MoveCaretLeftOrRightHandler extends EditorActionHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
final boolean scrollToCaret = (!(editor instanceof EditorImpl) || ((EditorImpl)editor).isScrollToCaret())
|
||||
&& caret == editor.getCaretModel().getPrimaryCaret();
|
||||
caretModel.moveCaretRelatively(myDirection == Direction.RIGHT ? 1 : -1, 0, false, false, scrollToCaret);
|
||||
VisualPosition currentPosition = caret.getVisualPosition();
|
||||
if (caret.isAtDirectionBoundary() && (myDirection == Direction.RIGHT ^ currentPosition.leansRight)) {
|
||||
caret.moveToVisualPosition(currentPosition.leanRight(!currentPosition.leansRight));
|
||||
}
|
||||
else {
|
||||
final boolean scrollToCaret = (!(editor instanceof EditorImpl) || ((EditorImpl)editor).isScrollToCaret())
|
||||
&& caret == editor.getCaretModel().getPrimaryCaret();
|
||||
caretModel.moveCaretRelatively(myDirection == Direction.RIGHT ? 1 : -1, 0, false, false, scrollToCaret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,6 +267,8 @@ public class CaretImpl extends UserDataHolderBase implements Caret {
|
||||
|
||||
int newLineNumber = visualCaret.line + lineShift;
|
||||
int newColumnNumber = visualCaret.column + columnShift;
|
||||
boolean newLeansRight = lineShift == 0 && columnShift != 0 ? columnShift < 0 : visualCaret.leansRight;
|
||||
|
||||
if (desiredX >= 0) {
|
||||
newColumnNumber = myEditor.xyToVisualPosition(new Point(desiredX, Math.max(0, newLineNumber) * myEditor.getLineHeight())).column;
|
||||
}
|
||||
@@ -305,13 +307,14 @@ public class CaretImpl extends UserDataHolderBase implements Caret {
|
||||
|
||||
VisualPosition pos = new VisualPosition(newLineNumber, newColumnNumber);
|
||||
if (!myEditor.getSoftWrapModel().isInsideSoftWrap(pos)) {
|
||||
LogicalPosition log = myEditor.visualToLogicalPosition(new VisualPosition(newLineNumber, newColumnNumber));
|
||||
LogicalPosition log = myEditor.visualToLogicalPosition(new VisualPosition(newLineNumber, newColumnNumber, newLeansRight));
|
||||
int offset = myEditor.logicalPositionToOffset(log);
|
||||
if (offset >= document.getTextLength()) {
|
||||
int lastOffsetColumn = myEditor.offsetToVisualPosition(document.getTextLength()).column;
|
||||
int lastOffsetColumn = myEditor.offsetToVisualPosition(document.getTextLength(), true).column;
|
||||
// We want to move caret to the last column if if it's located at the last line and 'Down' is pressed.
|
||||
if (lastOffsetColumn > newColumnNumber) {
|
||||
newColumnNumber = lastOffsetColumn;
|
||||
newLeansRight = true;
|
||||
desiredX = -1;
|
||||
lastColumnNumber = -1;
|
||||
}
|
||||
@@ -321,7 +324,7 @@ public class CaretImpl extends UserDataHolderBase implements Caret {
|
||||
if (offset >= 0 && offset < document.getTextLength()) {
|
||||
if (text.charAt(offset) == '\t' && (columnShift <= 0 || offset == myOffset)) {
|
||||
if (columnShift <= 0) {
|
||||
newColumnNumber = myEditor.offsetToVisualPosition(offset).column;
|
||||
newColumnNumber = myEditor.offsetToVisualPosition(offset, true).column;
|
||||
}
|
||||
else {
|
||||
SoftWrap softWrap = myEditor.getSoftWrapModel().getSoftWrap(offset + 1);
|
||||
@@ -339,7 +342,7 @@ public class CaretImpl extends UserDataHolderBase implements Caret {
|
||||
}
|
||||
}
|
||||
|
||||
pos = new VisualPosition(newLineNumber, newColumnNumber);
|
||||
pos = new VisualPosition(newLineNumber, newColumnNumber, newLeansRight);
|
||||
if (columnShift != 0 && lineShift == 0 && myEditor.getSoftWrapModel().isInsideSoftWrap(pos)) {
|
||||
LogicalPosition logical = myEditor.visualToLogicalPosition(pos);
|
||||
int softWrapOffset = myEditor.logicalPositionToOffset(logical);
|
||||
@@ -1466,6 +1469,11 @@ public class CaretImpl extends UserDataHolderBase implements Caret {
|
||||
return myEditor.myUseNewRendering && myEditor.myView.isRtlLocation(myOffset, myLogicalCaret.leansForward);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAtDirectionBoundary() {
|
||||
return myEditor.myUseNewRendering && myEditor.myView.isDirectionBoundary(myOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates information about target vertical range info - its <code>'y'</code> coordinate and height in pixels.
|
||||
*/
|
||||
|
||||
@@ -268,6 +268,14 @@ public class EditorView implements Disposable {
|
||||
return layout.isRtlLocation(offset - myDocument.getLineStartOffset(line), leanForward);
|
||||
}
|
||||
|
||||
public boolean isDirectionBoundary(int offset) {
|
||||
assertIsDispatchThread();
|
||||
if (myDocument.getTextLength() == 0) return false;
|
||||
int line = myDocument.getLineNumber(offset);
|
||||
LineLayout layout = getLineLayout(line);
|
||||
return layout.isDirectionBoundary(offset - myDocument.getLineStartOffset(line));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
LineLayout getLineLayout(int line) {
|
||||
return myTextLayoutCache.getLineLayout(line);
|
||||
|
||||
@@ -212,6 +212,17 @@ class LineLayout {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isDirectionBoundary(int offset) {
|
||||
boolean prevIsRtl = false;
|
||||
for (BidiRun run : myBidiRunsInLogicalOrder) {
|
||||
boolean curIsRtl = run.isRtl();
|
||||
if (offset == run.startOffset && curIsRtl != prevIsRtl) return true;
|
||||
if (offset < run.endOffset) return false;
|
||||
prevIsRtl = curIsRtl;
|
||||
}
|
||||
return prevIsRtl;
|
||||
}
|
||||
|
||||
private static class BidiRun {
|
||||
private final byte level;
|
||||
private final int startOffset;
|
||||
|
||||
+5
@@ -203,6 +203,11 @@ public class TextComponentCaret extends UserDataHolderBase implements Caret {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAtDirectionBoundary() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private SelectionModel getSelectionModel() {
|
||||
return myEditor.getSelectionModel();
|
||||
}
|
||||
|
||||
@@ -235,6 +235,29 @@ public class EditorRtlTest extends AbstractEditorTest {
|
||||
assertLogicalPositionsEqual("Wrong logical position", lF(1), myEditor.getCaretModel().getLogicalPosition());
|
||||
}
|
||||
|
||||
public void testNavigationWithArrowKeys() throws Exception {
|
||||
init("llrrll\nllrrll");
|
||||
assertCaretPosition(vL(0));
|
||||
right();
|
||||
assertCaretPosition(vL(1));
|
||||
right();
|
||||
assertCaretPosition(vL(2));
|
||||
right();
|
||||
assertCaretPosition(vR(2));
|
||||
right();
|
||||
assertCaretPosition(vL(3));
|
||||
right();
|
||||
assertCaretPosition(vL(4));
|
||||
down();
|
||||
assertCaretPosition(v(1, 4, false));
|
||||
left();
|
||||
assertCaretPosition(v(1, 3, true));
|
||||
left();
|
||||
assertCaretPosition(v(1, 2, true));
|
||||
up();
|
||||
assertCaretPosition(vR(2));
|
||||
}
|
||||
|
||||
private void init(String text) throws IOException {
|
||||
initText(text.replace(RTL_CHAR_REPRESENTATION, RTL_CHAR));
|
||||
}
|
||||
@@ -299,6 +322,10 @@ public class EditorRtlTest extends AbstractEditorTest {
|
||||
assertEquals(message, expectedPosition, actualPosition);
|
||||
assertEquals(message + " (direction flag)", expectedPosition.leansRight, actualPosition.leansRight);
|
||||
}
|
||||
|
||||
private static void assertCaretPosition(VisualPosition visualPosition) {
|
||||
assertVisualPositionsEqual("Wrong caret position", visualPosition, myEditor.getCaretModel().getVisualPosition());
|
||||
}
|
||||
|
||||
// logical position leaning backward
|
||||
private static LogicalPosition lB(int column) {
|
||||
|
||||
+16
@@ -574,6 +574,22 @@ public abstract class LightPlatformCodeInsightTestCase extends LightPlatformTest
|
||||
executeAction("EditorSelectLine");
|
||||
}
|
||||
|
||||
protected static void left() {
|
||||
executeAction("EditorLeft");
|
||||
}
|
||||
|
||||
protected static void right() {
|
||||
executeAction("EditorRight");
|
||||
}
|
||||
|
||||
protected static void up() {
|
||||
executeAction("EditorUp");
|
||||
}
|
||||
|
||||
protected static void down() {
|
||||
executeAction("EditorDown");
|
||||
}
|
||||
|
||||
protected static void lineComment() {
|
||||
new CommentByLineCommentAction().actionPerformedImpl(getProject(), getEditor());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user