From 0b9404bccbc274086537d9c49bf8a31a26ba4d7a Mon Sep 17 00:00:00 2001 From: Dmitry Batrak Date: Fri, 10 Mar 2017 17:51:07 +0300 Subject: [PATCH] IDEA-169157 Right-to-left bug - fix for JSX files --- .../openapi/editor/impl/view/LineLayout.java | 10 +- .../openapi/editor/impl/AbstractRtlTest.java | 203 ++++++++++++++++++ .../openapi/editor/impl/EditorRtlTest.java | 157 +------------- .../editor/bidi/XmlBidiRegionsSeparator.java | 5 +- 4 files changed, 216 insertions(+), 159 deletions(-) create mode 100644 platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/AbstractRtlTest.java 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 e0c23165d74a..514e1cdde53a 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 @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -17,6 +17,7 @@ package com.intellij.openapi.editor.impl.view; import com.intellij.lang.CodeDocumentationAwareCommenter; import com.intellij.lang.Commenter; +import com.intellij.lang.Language; import com.intellij.lang.LanguageCommenters; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; @@ -28,6 +29,7 @@ import com.intellij.openapi.editor.impl.ComplementaryFontsRegistry; import com.intellij.openapi.editor.impl.FontInfo; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.StringEscapesTokenTypes; +import com.intellij.psi.TokenType; import com.intellij.psi.tree.IElementType; import com.intellij.util.BitUtil; import com.intellij.util.DocumentUtil; @@ -213,8 +215,10 @@ abstract class LineLayout { if (token1 == null || token2 == null) return true; if (StringEscapesTokenTypes.STRING_LITERAL_ESCAPES.contains(token1) || StringEscapesTokenTypes.STRING_LITERAL_ESCAPES.contains(token2)) return false; - if (!token1.getLanguage().is(token2.getLanguage())) return true; - BidiRegionsSeparator separator = LanguageBidiRegionsSeparator.INSTANCE.forLanguage(token1.getLanguage()); + if (token1 != TokenType.WHITE_SPACE && token2 != TokenType.WHITE_SPACE && !token1.getLanguage().is(token2.getLanguage())) return true; + Language language = token1.getLanguage(); + if (language == Language.ANY) language = token2.getLanguage(); + BidiRegionsSeparator separator = LanguageBidiRegionsSeparator.INSTANCE.forLanguage(language); return separator.createBorderBetweenTokens(token1, token2); } diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/AbstractRtlTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/AbstractRtlTest.java new file mode 100644 index 000000000000..c7fd18e21954 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/AbstractRtlTest.java @@ -0,0 +1,203 @@ +/* + * Copyright 2000-2017 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; + +import com.intellij.openapi.editor.Caret; +import com.intellij.openapi.editor.LogicalPosition; +import com.intellij.openapi.editor.VisualPosition; +import com.intellij.testFramework.TestFileType; + +import java.awt.*; +import java.io.IOException; +import java.util.ArrayList; + +/** + * To simplify the representation of input text, 'R' character in these tests represents an RTL character. + */ +public abstract class AbstractRtlTest extends AbstractEditorTest { + private static final char RTL_CHAR_REPRESENTATION = 'R'; + private static final char RTL_CHAR = '\u05d0'; // Hebrew 'aleph' letter + private static final char BIDI_BOUNDARY_MARKER = '|'; + + protected void prepareText(String text) throws IOException { + prepare(text, TestFileType.TEXT); + } + + protected void prepare(String text, TestFileType fileType) throws IOException { + init(text.replace(RTL_CHAR_REPRESENTATION, RTL_CHAR), fileType); + } + + protected void checkResult(String text) { + checkResultByText(text.replace(RTL_CHAR_REPRESENTATION, RTL_CHAR)); + } + + protected static void checkOffsetConversions(int offset, + LogicalPosition logicalPosition, + VisualPosition visualPositionTowardsSmallerOffsets, + VisualPosition visualPositionTowardsLargerOffsets, + Point xy) { + checkOffsetConversions(offset, logicalPosition, visualPositionTowardsSmallerOffsets, visualPositionTowardsLargerOffsets, xy, xy); + } + + protected static void checkOffsetConversions(int offset, + LogicalPosition logicalPosition, + VisualPosition visualPositionTowardsSmallerOffsets, + VisualPosition visualPositionTowardsLargerOffsets, + Point xyTowardsSmallerOffsets, + Point xyTowardsLargerOffsets) { + assertLogicalPositionsEqual("Wrong offset->logicalPosition calculation", logicalPosition, myEditor.offsetToLogicalPosition(offset)); + assertVisualPositionsEqual("Wrong beforeOffset->visualPosition calculation", + visualPositionTowardsSmallerOffsets, myEditor.offsetToVisualPosition(offset, false, false)); + assertVisualPositionsEqual("Wrong afterOffset->visualPosition calculation", + visualPositionTowardsLargerOffsets, myEditor.offsetToVisualPosition(offset, true, false)); + assertEquals("Wrong afterOffset->visualLine calculation", + visualPositionTowardsLargerOffsets.line, ((EditorImpl)myEditor).offsetToVisualLine(offset)); + assertEquals("Wrong beforeOffset->xy calculation", xyTowardsSmallerOffsets, ((EditorImpl)myEditor).offsetToXY(offset, false)); + assertEquals("Wrong afterOffset->xy calculation", xyTowardsLargerOffsets, ((EditorImpl)myEditor).offsetToXY(offset, true)); + } + + protected static void checkLPConversions(int logicalColumn, int offset, + VisualPosition visualPositionForPrecedingLp, VisualPosition visualPositionForSucceedingLp) { + checkLPConversions(lB(logicalColumn), offset, visualPositionForPrecedingLp); + checkLPConversions(lF(logicalColumn), offset, visualPositionForSucceedingLp); + + } + + protected static void checkLPConversions(LogicalPosition logicalPosition, int offset, VisualPosition visualPosition) { + assertEquals("Wrong logicalPosition->offset calculation", offset, myEditor.logicalPositionToOffset(logicalPosition)); + assertVisualPositionsEqual("Wrong logicalPosition->visualPosition calculation", + visualPosition, myEditor.logicalToVisualPosition(logicalPosition)); + } + + protected static void checkVPConversions(int visualColumn, LogicalPosition logicalPositionForLeftLeaningVp, + LogicalPosition logicalPositionForRightLeaningVp, Point xy) { + checkVPConversions(vL(visualColumn), logicalPositionForLeftLeaningVp, xy); + checkVPConversions(vR(visualColumn), logicalPositionForRightLeaningVp, xy); + } + + protected static void checkVPConversions(VisualPosition visualPosition, LogicalPosition logicalPosition, Point xy) { + assertLogicalPositionsEqual("Wrong visualPosition->logicalPosition calculation", + logicalPosition, myEditor.visualToLogicalPosition(visualPosition)); + assertEquals("Wrong visualPosition->xy calculation", xy, myEditor.visualPositionToXY(visualPosition)); + } + + protected static void checkXYConversion(Point xy, + VisualPosition visualPosition) { + assertVisualPositionsEqual("Wrong xy->visualPosition calculation", visualPosition, myEditor.xyToVisualPosition(xy)); + } + + protected static void assertLogicalPositionsEqual(String message, LogicalPosition expectedPosition, LogicalPosition actualPosition) { + assertEquals(message, expectedPosition, actualPosition); + assertEquals(message + " (direction flag)", expectedPosition.leansForward, actualPosition.leansForward); + } + + protected static void assertVisualPositionsEqual(String message, VisualPosition expectedPosition, VisualPosition actualPosition) { + assertEquals(message, expectedPosition, actualPosition); + assertEquals(message + " (direction flag)", expectedPosition.leansRight, actualPosition.leansRight); + } + + protected static void assertCaretPosition(VisualPosition visualPosition) { + assertVisualPositionsEqual("Wrong caret position", visualPosition, myEditor.getCaretModel().getVisualPosition()); + } + + protected static void assertVisualCaretLocation(int visualColumn, boolean reversedDirection) { + assertVisualCaretLocation(0, visualColumn, reversedDirection); + } + + protected static void assertVisualCaretLocation(int visualLine, int visualColumn, boolean reversedDirection) { + assertEquals(1, myEditor.getCaretModel().getCaretCount()); + Caret caret = myEditor.getCaretModel().getPrimaryCaret(); + assertEquals(visualLine, caret.getVisualPosition().line); + assertEquals(visualColumn, caret.getVisualPosition().column); + assertEquals(reversedDirection, caret.isAtRtlLocation()); + } + + /** + * Text should contain {@link #BIDI_BOUNDARY_MARKER} characters at expected bidi run boundaries' positions. + */ + protected void checkBidiRunBoundaries(String textWithBoundaryMarkers, String fileExtension) { + java.util.List expectedBoundaryPositions = new ArrayList<>(); + StringBuilder rawTextBuilder = new StringBuilder(); + for (int i = 0; i < textWithBoundaryMarkers.length(); i++) { + char c = textWithBoundaryMarkers.charAt(i); + if (c == BIDI_BOUNDARY_MARKER) { + expectedBoundaryPositions.add(rawTextBuilder.length()); + } + else { + rawTextBuilder.append(c); + } + } + String rawText = rawTextBuilder.toString(); + configureFromFileText(getTestName(false) + "." + fileExtension, rawText.replace(RTL_CHAR_REPRESENTATION, RTL_CHAR)); + + java.util.List actualBoundaryPositions = new ArrayList<>(); + for (int i = 1; i < rawText.length(); i++) { + if (!myEditor.offsetToVisualPosition(i, false, false).equals( + myEditor.offsetToVisualPosition(i, true, false))) { + actualBoundaryPositions.add(i); + } + } + assertEquals("Unexpected bidi regions boundaries' positions", expectedBoundaryPositions, actualBoundaryPositions); + } + + // logical position leaning backward + protected static LogicalPosition lB(int column) { + return new LogicalPosition(0, column); + } + + // logical position leaning backward + protected static LogicalPosition lB(int line, int column) { + return new LogicalPosition(line, column); + } + + // logical position leaning forward + protected static LogicalPosition lF(int column) { + return new LogicalPosition(0, column, true); + } + + // logical position leaning forward + protected static LogicalPosition lF(int line, int column) { + return new LogicalPosition(line, column, true); + } + + // visual position leaning to the left + protected static VisualPosition vL(int column) { + return new VisualPosition(0, column); + } + + // visual position leaning to the left + protected static VisualPosition vL(int line, int column) { + return new VisualPosition(line, column); + } + + // visual position leaning to the right + protected static VisualPosition vR(int column) { + return new VisualPosition(0, column, true); + } + + // visual position leaning to the right + protected static VisualPosition vR(int line, int column) { + return new VisualPosition(line, column, true); + } + + protected static Point xy(int x) { + return new Point(x, 0); + } + + protected static Point xy(int x, int y) { + return new Point(x, y); + } +} 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 b960e62083b7..ad960c90f0bc 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 @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -16,23 +16,13 @@ package com.intellij.openapi.editor.impl; import com.intellij.openapi.actionSystem.IdeActions; -import com.intellij.openapi.editor.Caret; -import com.intellij.openapi.editor.LogicalPosition; -import com.intellij.openapi.editor.VisualPosition; -import com.intellij.openapi.editor.ex.EditorSettingsExternalizable; import com.intellij.openapi.editor.ex.BidiTextDirection; +import com.intellij.openapi.editor.ex.EditorSettingsExternalizable; import com.intellij.testFramework.TestFileType; -import java.awt.*; import java.io.IOException; -/** - * To simplify the representation of input text, 'R' character in these tests represents an RTL character. - */ -public class EditorRtlTest extends AbstractEditorTest { - private static final char RTL_CHAR_REPRESENTATION = 'R'; - private static final char RTL_CHAR = '\u05d0'; // Hebrew 'aleph' letter - +public class EditorRtlTest extends AbstractRtlTest { public void testPositionCalculations() throws IOException { prepareText("LLRR"); @@ -600,145 +590,4 @@ public class EditorRtlTest extends AbstractEditorTest { right(); checkResult("// R"); } - - private void prepareText(String text) throws IOException { - prepare(text, TestFileType.TEXT); - } - - private void prepare(String text, TestFileType fileType) throws IOException { - init(text.replace(RTL_CHAR_REPRESENTATION, RTL_CHAR), fileType); - } - - private void checkResult(String text) { - checkResultByText(text.replace(RTL_CHAR_REPRESENTATION, RTL_CHAR)); - } - - private static void checkOffsetConversions(int offset, - LogicalPosition logicalPosition, - VisualPosition visualPositionTowardsSmallerOffsets, - VisualPosition visualPositionTowardsLargerOffsets, - Point xy) { - checkOffsetConversions(offset, logicalPosition, visualPositionTowardsSmallerOffsets, visualPositionTowardsLargerOffsets, xy, xy); - } - - private static void checkOffsetConversions(int offset, - LogicalPosition logicalPosition, - VisualPosition visualPositionTowardsSmallerOffsets, - VisualPosition visualPositionTowardsLargerOffsets, - Point xyTowardsSmallerOffsets, - Point xyTowardsLargerOffsets) { - assertLogicalPositionsEqual("Wrong offset->logicalPosition calculation", logicalPosition, myEditor.offsetToLogicalPosition(offset)); - assertVisualPositionsEqual("Wrong beforeOffset->visualPosition calculation", - visualPositionTowardsSmallerOffsets, myEditor.offsetToVisualPosition(offset, false, false)); - assertVisualPositionsEqual("Wrong afterOffset->visualPosition calculation", - visualPositionTowardsLargerOffsets, myEditor.offsetToVisualPosition(offset, true, false)); - assertEquals("Wrong afterOffset->visualLine calculation", - visualPositionTowardsLargerOffsets.line, ((EditorImpl)myEditor).offsetToVisualLine(offset)); - assertEquals("Wrong beforeOffset->xy calculation", xyTowardsSmallerOffsets, ((EditorImpl)myEditor).offsetToXY(offset, false)); - assertEquals("Wrong afterOffset->xy calculation", xyTowardsLargerOffsets, ((EditorImpl)myEditor).offsetToXY(offset, true)); - } - - private static void checkLPConversions(int logicalColumn, int offset, - VisualPosition visualPositionForPrecedingLp, VisualPosition visualPositionForSucceedingLp) { - checkLPConversions(lB(logicalColumn), offset, visualPositionForPrecedingLp); - checkLPConversions(lF(logicalColumn), offset, visualPositionForSucceedingLp); - - } - - private static void checkLPConversions(LogicalPosition logicalPosition, int offset, VisualPosition visualPosition) { - assertEquals("Wrong logicalPosition->offset calculation", offset, myEditor.logicalPositionToOffset(logicalPosition)); - assertVisualPositionsEqual("Wrong logicalPosition->visualPosition calculation", - visualPosition, myEditor.logicalToVisualPosition(logicalPosition)); - } - - private static void checkVPConversions(int visualColumn, LogicalPosition logicalPositionForLeftLeaningVp, - LogicalPosition logicalPositionForRightLeaningVp, Point xy) { - checkVPConversions(vL(visualColumn), logicalPositionForLeftLeaningVp, xy); - checkVPConversions(vR(visualColumn), logicalPositionForRightLeaningVp, xy); - } - - private static void checkVPConversions(VisualPosition visualPosition, LogicalPosition logicalPosition, Point xy) { - assertLogicalPositionsEqual("Wrong visualPosition->logicalPosition calculation", - logicalPosition, myEditor.visualToLogicalPosition(visualPosition)); - assertEquals("Wrong visualPosition->xy calculation", xy, myEditor.visualPositionToXY(visualPosition)); - } - - private static void checkXYConversion(Point xy, - VisualPosition visualPosition) { - assertVisualPositionsEqual("Wrong xy->visualPosition calculation", visualPosition, myEditor.xyToVisualPosition(xy)); - } - - private static void assertLogicalPositionsEqual(String message, LogicalPosition expectedPosition, LogicalPosition actualPosition) { - assertEquals(message, expectedPosition, actualPosition); - assertEquals(message + " (direction flag)", expectedPosition.leansForward, actualPosition.leansForward); - } - - private static void assertVisualPositionsEqual(String message, VisualPosition expectedPosition, VisualPosition actualPosition) { - 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()); - } - - private static void assertVisualCaretLocation(int visualColumn, boolean reversedDirection) { - assertVisualCaretLocation(0, visualColumn, reversedDirection); - } - - private static void assertVisualCaretLocation(int visualLine, int visualColumn, boolean reversedDirection) { - assertEquals(1, myEditor.getCaretModel().getCaretCount()); - Caret caret = myEditor.getCaretModel().getPrimaryCaret(); - assertEquals(visualLine, caret.getVisualPosition().line); - assertEquals(visualColumn, caret.getVisualPosition().column); - assertEquals(reversedDirection, caret.isAtRtlLocation()); - } - - // logical position leaning backward - private static LogicalPosition lB(int column) { - return new LogicalPosition(0, column); - } - - // logical position leaning backward - private static LogicalPosition lB(int line, int column) { - return new LogicalPosition(line, column); - } - - // logical position leaning forward - private static LogicalPosition lF(int column) { - return new LogicalPosition(0, column, true); - } - - // logical position leaning forward - private static LogicalPosition lF(int line, int column) { - return new LogicalPosition(line, column, true); - } - - // visual position leaning to the left - private static VisualPosition vL(int column) { - return new VisualPosition(0, column); - } - - // visual position leaning to the left - private static VisualPosition vL(int line, int column) { - return new VisualPosition(line, column); - } - - // visual position leaning to the right - private static VisualPosition vR(int column) { - return new VisualPosition(0, column, true); - } - - // visual position leaning to the right - private static VisualPosition vR(int line, int column) { - return new VisualPosition(line, column, true); - } - - private static Point xy(int x) { - return new Point(x, 0); - } - - private static Point xy(int x, int y) { - return new Point(x, y); - } } diff --git a/xml/impl/src/com/intellij/openapi/editor/bidi/XmlBidiRegionsSeparator.java b/xml/impl/src/com/intellij/openapi/editor/bidi/XmlBidiRegionsSeparator.java index 6a330c785a37..6dbc53d4d47b 100644 --- a/xml/impl/src/com/intellij/openapi/editor/bidi/XmlBidiRegionsSeparator.java +++ b/xml/impl/src/com/intellij/openapi/editor/bidi/XmlBidiRegionsSeparator.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -21,6 +21,7 @@ import com.intellij.psi.xml.XmlTokenType; public class XmlBidiRegionsSeparator extends TokenSetBidiRegionsSeparator { public XmlBidiRegionsSeparator() { super(TokenSet.create(XmlTokenType.XML_DATA_CHARACTERS, - XmlTokenType.XML_REAL_WHITE_SPACE)); + XmlTokenType.XML_REAL_WHITE_SPACE, + XmlTokenType.XML_WHITE_SPACE)); } }