diff --git a/platform/editor-ui-api/src/com/intellij/openapi/editor/Caret.java b/platform/editor-ui-api/src/com/intellij/openapi/editor/Caret.java
index 1ca3f7bac418..f467e246395b 100644
--- a/platform/editor-ui-api/src/com/intellij/openapi/editor/Caret.java
+++ b/platform/editor-ui-api/src/com/intellij/openapi/editor/Caret.java
@@ -302,4 +302,11 @@ public interface Caret extends UserDataHolderEx, Disposable {
* to offset and logical column number in the vicinity of caret.
*/
boolean isAtRtlLocation();
+
+ /**
+ * Returns true 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();
}
diff --git a/platform/editor-ui-api/src/com/intellij/openapi/editor/VisualPosition.java b/platform/editor-ui-api/src/com/intellij/openapi/editor/VisualPosition.java
index 633d85678b18..62f541df208c 100644
--- a/platform/editor-ui-api/src/com/intellij/openapi/editor/VisualPosition.java
+++ b/platform/editor-ui-api/src/com/intellij/openapi/editor/VisualPosition.java
@@ -77,6 +77,13 @@ public class VisualPosition {
return line > other.line;
}
+ /**
+ * Constructs a new VisualPosition 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" : "");
diff --git a/platform/lang-impl/src/com/intellij/injected/editor/InjectedCaret.java b/platform/lang-impl/src/com/intellij/injected/editor/InjectedCaret.java
index 508a9bee4f8e..468745cabb59 100644
--- a/platform/lang-impl/src/com/intellij/injected/editor/InjectedCaret.java
+++ b/platform/lang-impl/src/com/intellij/injected/editor/InjectedCaret.java
@@ -242,4 +242,9 @@ public class InjectedCaret implements Caret {
public boolean isAtRtlLocation() {
return myDelegate.isAtRtlLocation();
}
+
+ @Override
+ public boolean isAtDirectionBoundary() {
+ return myDelegate.isAtDirectionBoundary();
+ }
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/actions/MoveCaretLeftOrRightHandler.java b/platform/platform-impl/src/com/intellij/openapi/editor/actions/MoveCaretLeftOrRightHandler.java
index be2628f6741c..6edbe7a679c4 100644
--- a/platform/platform-impl/src/com/intellij/openapi/editor/actions/MoveCaretLeftOrRightHandler.java
+++ b/platform/platform-impl/src/com/intellij/openapi/editor/actions/MoveCaretLeftOrRightHandler.java
@@ -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);
+ }
}
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretImpl.java
index 1bab16ab8981..59cd0b94b504 100644
--- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretImpl.java
+++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretImpl.java
@@ -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 'y' coordinate and height in pixels.
*/
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorView.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorView.java
index 79f44decbb5b..de7cdeea888a 100644
--- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorView.java
+++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorView.java
@@ -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);
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LineLayout.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LineLayout.java
index 7260caae957b..916b7f05d4d6 100644
--- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LineLayout.java
+++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LineLayout.java
@@ -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;
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentCaret.java b/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentCaret.java
index bce02d483344..482694255f8f 100644
--- a/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentCaret.java
+++ b/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentCaret.java
@@ -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();
}
diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/EditorRtlTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/EditorRtlTest.java
index 0d773e4a94c0..28878e749fe1 100644
--- a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/EditorRtlTest.java
+++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/EditorRtlTest.java
@@ -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) {
diff --git a/platform/testFramework/src/com/intellij/testFramework/LightPlatformCodeInsightTestCase.java b/platform/testFramework/src/com/intellij/testFramework/LightPlatformCodeInsightTestCase.java
index 1caaee7a1c99..776684a8bcca 100644
--- a/platform/testFramework/src/com/intellij/testFramework/LightPlatformCodeInsightTestCase.java
+++ b/platform/testFramework/src/com/intellij/testFramework/LightPlatformCodeInsightTestCase.java
@@ -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());
}