From c7f77143dc5533340b07dcbc25ef666c39702efb Mon Sep 17 00:00:00 2001 From: Mike Smith Date: Mon, 9 Sep 2019 11:51:01 +0100 Subject: [PATCH] Fix WrappedLine calculateLineCount and modelToView when dealing with certain lines containing tabs. --- .../ui/rsyntaxtextarea/WrappedSyntaxView.java | 2 - ...xViewMonospacedCalculateLineCountTest.java | 127 ++++ ...edSyntaxViewMonospacedModelToViewTest.java | 619 ++++++++++++++++++ ...edSyntaxViewMonospacedViewToModelTest.java | 474 ++++++++++++++ 4 files changed, 1220 insertions(+), 2 deletions(-) create mode 100644 RSyntaxTextArea/src/test/java/org/fife/ui/rsyntaxtextarea/WrappedSyntaxViewMonospacedCalculateLineCountTest.java create mode 100644 RSyntaxTextArea/src/test/java/org/fife/ui/rsyntaxtextarea/WrappedSyntaxViewMonospacedModelToViewTest.java create mode 100644 RSyntaxTextArea/src/test/java/org/fife/ui/rsyntaxtextarea/WrappedSyntaxViewMonospacedViewToModelTest.java diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/WrappedSyntaxView.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/WrappedSyntaxView.java index a4c7ad265..ebae35ab2 100755 --- a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/WrappedSyntaxView.java +++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/WrappedSyntaxView.java @@ -1166,7 +1166,6 @@ final int calculateLineCount() { nlines += 1; TokenSubList subList = TokenUtils.getSubTokenList(tokenList, p0, WrappedSyntaxView.this, textArea, x0, lineCountTempToken); - x0 = subList!=null ? subList.x : x0; tokenList = subList!=null ? subList.tokenList : null; int p = calculateBreakPosition(p0, tokenList, x0); @@ -1273,7 +1272,6 @@ public Shape modelToView(int pos, Shape a, Position.Bias b) while (p0 < p1) { TokenSubList subList = TokenUtils.getSubTokenList(tokenList, p0, WrappedSyntaxView.this, textArea, x0, lineCountTempToken); - x0 = subList!=null ? subList.x : x0; tokenList = subList!=null ? subList.tokenList : null; int p = calculateBreakPosition(p0, tokenList, x0); if ((pos >= p0) && (testP data() + { + return asList(new Object[][] {{15}, {16}, {18}}); + } + + public WrappedSyntaxViewMonospacedCalculateLineCountTest(int fontSize) + { + this.fontSize = fontSize; + } + + @Before + public void setUp() + { + document = new RSyntaxDocument(SYNTAX_STYLE_NONE); + RSyntaxTextArea textArea = new RSyntaxTextArea(document); + + Font font = RTextAreaBase.getDefaultFont().deriveFont(Font.PLAIN, fontSize); + FontMetrics fontMetrics = new JPanel().getFontMetrics(font); + int charWidth = fontMetrics.charWidth(32); + textArea.setFont(font); + textArea.setLineWrap(true); + textArea.getSyntaxScheme().getStyle(TokenTypes.IDENTIFIER).font = font; + textArea.getSyntaxScheme().getStyle(TokenTypes.IDENTIFIER).fontMetrics = fontMetrics; + textArea.getSyntaxScheme().getStyle(TokenTypes.WHITESPACE).font = font; + textArea.getSyntaxScheme().getStyle(TokenTypes.WHITESPACE).fontMetrics = fontMetrics; + view = (WrappedSyntaxView) textArea.getUI().getRootView(textArea).getView(0); + view.setSize(charWidth * 8, 1000); + } + + @Test + public void givenALineWithOneTokenWithinViewWidth_thenCalculateLineCountReturns1() throws Exception { + document.replace(0, document.getLength(), "abcdefgh\n",null); + WrappedLine line = (WrappedLine) view.getView(0); + + assertEquals(1, line.calculateLineCount()); + } + + @Test + public void givenALineWithMultipleTokensWithinViewWidth_thenCalculateLineCountReturns1() throws Exception { + document.replace(0, document.getLength(), "abc de f\n",null); + WrappedLine line = (WrappedLine) view.getView(0); + + assertEquals(1, line.calculateLineCount()); + } + + @Test + public void givenALineWithMultipleTokensWithinViewWidthAndContainingTab_thenCalculateLineCountReturns1() throws Exception { + document.replace(0, document.getLength(), "abc\td f\n",null); + WrappedLine line = (WrappedLine) view.getView(0); + + assertEquals(1, line.calculateLineCount()); + } + + @Test + public void givenALineWithSingleTokenWrappingOverTwoPhysicalLines_thenCalculateLineCountReturns2() throws Exception { + document.replace(0, document.getLength(), "abcdefghijk",null); + WrappedLine line = (WrappedLine) view.getView(0); + + assertEquals(2, line.calculateLineCount()); + } + + @Test + public void givenALineWithMultipleTokensWrappingOverTwoPhysicalLinesAndContainingATabOnTheFirstLine_thenCalculateLineCountReturns2() throws Exception { + document.replace(0, document.getLength(), "abc\tde f",null); + WrappedLine line = (WrappedLine) view.getView(0); + + assertEquals(2, line.calculateLineCount()); + } + + @Test + public void givenALineWithMultipleTokensWrappingOverTwoPhysicalLinesAndContainingATabOnTheSecondLine_thenCalculateLineCountReturns2() throws Exception { + document.replace(0, document.getLength(), "abcdefghij\tkl",null); + WrappedLine line = (WrappedLine) view.getView(0); + + assertEquals(2, line.calculateLineCount()); + } + + @Test + public void givenALineWithMultipleTokensFittingExactlyInTwoPhysicalLinesAndContainingATabOnTheFirstLine_thenCalculateLineCountReturns2() throws Exception { + document.replace(0, document.getLength(), "abc\tde f ghijkl\n",null); + WrappedLine line = (WrappedLine) view.getView(0); + + assertEquals(2, line.calculateLineCount()); + } + + @Test + public void givenALineWithMultipleTokensFittingExactlyInTwoPhysicalLinesAndContainingATabOnTheSecondLine_thenCalculateLineCountReturns2() throws Exception { + document.replace(0, document.getLength(), "abcdefghij\tklm\n",null); + WrappedLine line = (WrappedLine) view.getView(0); + + assertEquals(2, line.calculateLineCount()); + } +} diff --git a/RSyntaxTextArea/src/test/java/org/fife/ui/rsyntaxtextarea/WrappedSyntaxViewMonospacedModelToViewTest.java b/RSyntaxTextArea/src/test/java/org/fife/ui/rsyntaxtextarea/WrappedSyntaxViewMonospacedModelToViewTest.java new file mode 100644 index 000000000..a0a5439fd --- /dev/null +++ b/RSyntaxTextArea/src/test/java/org/fife/ui/rsyntaxtextarea/WrappedSyntaxViewMonospacedModelToViewTest.java @@ -0,0 +1,619 @@ +package org.fife.ui.rsyntaxtextarea; + +import org.fife.ui.rtextarea.RTextAreaBase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import javax.swing.JPanel; +import javax.swing.text.BadLocationException; +import javax.swing.text.View; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Rectangle; +import java.util.Collection; + +import static java.util.Arrays.asList; +import static javax.swing.text.Position.Bias.Backward; +import static javax.swing.text.Position.Bias.Forward; +import static org.fife.ui.rsyntaxtextarea.SyntaxConstants.SYNTAX_STYLE_NONE; +import static org.junit.Assert.assertEquals; + + +/** + * Unit tests for the {@link WrappedSyntaxView} class. + * + * @author Mike Smith + * @version 1.0 + */ +@RunWith(Parameterized.class) +public class WrappedSyntaxViewMonospacedModelToViewTest +{ + private static final int VIEW_WIDTH = 1000; + private static final int VIEW_HEIGHT = 800; + + private final int fontSize; + + private RSyntaxDocument document; + private WrappedSyntaxView view; + private int lineHeight; + private int charWidth; + + @Parameterized.Parameters (name = "{0}") + public static Collection data() + { + return asList(new Object[][] {{15}, {16}, {18}}); + } + + public WrappedSyntaxViewMonospacedModelToViewTest(int fontSize) + { + this.fontSize = fontSize; + } + + @Before + public void setUp() + { + document = new RSyntaxDocument(SYNTAX_STYLE_NONE); + RSyntaxTextArea textArea = new RSyntaxTextArea(document); + + Font font = RTextAreaBase.getDefaultFont().deriveFont(Font.PLAIN, fontSize); + FontMetrics fontMetrics = new JPanel().getFontMetrics(font); + charWidth = fontMetrics.charWidth(32); + textArea.setFont(font); + textArea.setLineWrap(true); + textArea.getSyntaxScheme().getStyle(TokenTypes.IDENTIFIER).font = font; + textArea.getSyntaxScheme().getStyle(TokenTypes.IDENTIFIER).fontMetrics = fontMetrics; + textArea.getSyntaxScheme().getStyle(TokenTypes.WHITESPACE).font = font; + textArea.getSyntaxScheme().getStyle(TokenTypes.WHITESPACE).fontMetrics = fontMetrics; + lineHeight = textArea.getLineHeight(); + view = (WrappedSyntaxView) textArea.getUI().getRootView(textArea).getView(0); + view.setSize(VIEW_WIDTH, VIEW_HEIGHT); + } + + @Test + public void givenALineFittingWithinAllocationWidth_whenPositionIsAtTheFirstCharacter_andBiasForward_thenModelToViewReturnsThePositionOfTheFirstCharacter() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(0, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), Forward); + + assertEquals(0, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineFittingWithinAllocationWidth_whenPositionIsAtTheFistCharacter_andBiasBackward_thenModelToViewReturnsThePositionOfTheFirstCharacter() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(0, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), Backward); + + assertEquals(0, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineFittingWithinAllocationWidth_whenPositionIsAtACharacter_andBiasForward_thenModelToViewReturnsThePositionOfTheCharacter() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(7, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), Forward); + + assertEquals(charWidth * 7, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineFittingWithinAllocationWidth_whenPositionIsAtACharacter_andBiasBackward_thenModelToViewReturnsThePositionOfTheCharacter() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(7, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), Backward); + + assertEquals(charWidth * 7, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineFittingWithinAllocationWidthAndEndingWithANewline_whenPositionIsAtTheLastCharacterOfTheLine_andBiasForward_thenModelToViewReturnsThePositionOfTheLastCharacterOfTheLine() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(64, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), Forward); + + assertEquals(charWidth * 64, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineFittingWithinAllocationWidthAndEndingWithANewline_whenPositionIsAtTheLastCharacterOfTheLine_andBiasBackward_thenModelToViewReturnsThePositionOfTheLastCharacterOfTheLine() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(64, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), Backward); + + assertEquals(charWidth * 64, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineFittingWithinAllocationWidthAndEndingWithANewline_whenPositionIsAtTheNewlineCharacter_andBiasForward_thenModelToViewReturnsThePositionRightOfTheLastCharacterOfTheLine() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(65, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), Forward); + + assertEquals(charWidth * 65, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineFittingWithinAllocationWidthAndEndingWithANewline_whenPositionIsAtTheNewlineCharacter_andBiasBackward_thenModelToViewReturnsThePositionRightOfTheLastCharacterOfTheLine() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(65, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), Backward); + + assertEquals(charWidth * 65, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test(expected = BadLocationException.class) + public void givenALineFittingWithinAllocationWidthAndEndingWithANewline_whenPositionIsOnePlaceAfterTheNewlineCharacter_andBiasForward_thenModelToViewThrowsBadLocationException() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + line.modelToView(66, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), Forward); + } + + // TODO: should modelToView do this? Wouldn't it be better to throw an exception as in the forward bias case? + @Test + public void givenALineFittingWithinAllocationWidthAndEndingWithANewline_whenPositionIsOnePlaceAfterTheNewlineCharacter_andBiasBackward_thenModelToViewReturnsThePositionRightOfTheLastCharacterOfTheLine() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(66, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), Backward); + + assertEquals(charWidth * 65, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test(expected = BadLocationException.class) + public void givenALineFittingWithinAllocationWidthAndEndingWithANewline_whenPositionIsTwoPlacesAfterTheNewlineCharacter_andBiasForward_thenModelToViewThrowsBadLocationException() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + line.modelToView(67, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), Forward); + } + + @Test(expected = BadLocationException.class) + public void givenALineFittingWithinAllocationWidthAndEndingWithANewline_whenPositionIsTwoPlacesAfterTheNewlineCharacter_andBiasBackward_thenModelToViewThrowsBadLocationException() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + line.modelToView(67, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), Backward); + } + + @Test + public void givenALineFittingWithinAllocationWidthAndNotEndingWithANewline_whenPositionIsAtTheLastCharacterOfTheLine_andBiasForward_thenModelToViewReturnsThePositionOfTheLastCharacterOfTheLine() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(64, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), Forward); + + assertEquals(charWidth * 64, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineFittingWithinAllocationWidthAndNotEndingWithANewline_whenPositionIsAtTheLastCharacterOfTheLine_andBiasBackward_thenModelToViewReturnsThePositionOfTheLastCharacterOfTheLine() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(64, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), Backward); + + assertEquals(charWidth * 64, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineFittingWithinAllocationWidthAndNotEndingWithANewline_whenPositionIsOnePlaceAfterTheLastCharacterOfTheLine_andBiasForward_thenModelToViewReturnsThePositionRightOfTheLastCharacterOfTheLine() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(65, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), Forward); + + assertEquals(charWidth * 65, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineFittingWithinAllocationWidthAndNotEndingWithANewline_whenPositionIsOnePlaceAfterTheLastCharacterOfTheLine_andBiasBackward_thenModelToViewReturnsThePositionRightOfTheLastCharacterOfTheLine() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(65, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), Backward); + + assertEquals(charWidth * 65, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test(expected = BadLocationException.class) + public void givenALineFittingWithinAllocationWidthAndNotEndingWithANewline_whenPositionIsTwoPlacesAfterTheLastCharacterOfTheLine_andBiasForward_thenModelToViewThrowsBadLocationException() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.",null); + View line = view.getView(0); + + line.modelToView(66, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), Forward); + } + + @Test(expected = BadLocationException.class) + public void givenALineFittingWithinAllocationWidthAndNotEndingWithANewline_whenPositionIsTwoPlacesAfterTheLastCharacterOfTheLine_andBiasBackward_thenModelToViewThrowsBadLocationException() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.",null); + View line = view.getView(0); + + line.modelToView(66, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), Backward); + } + + @Test + public void givenALineExactlyTheViewWidthWithNoNewline_whenPositionIsAtTheLastCharacterOfTheLine_andBiasForward_thenModelToViewReturnsThePositionOfTheCharacter() throws Exception { + int viewWidth = charWidth * 26; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghijklmnopqrstuvwxyz",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(25, new Rectangle(0, 0, viewWidth, lineHeight), Forward); + + assertEquals(charWidth * 25, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineExactlyTheViewWidthWithNoNewline_whenPositionIsAtTheLastCharacterOfTheLine_andBiasBackward_thenModelToViewReturnsThePositionOfTheCharacter() throws Exception { + int viewWidth = charWidth * 26; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghijklmnopqrstuvwxyz",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(25, new Rectangle(0, 0, viewWidth, lineHeight), Backward); + + assertEquals(charWidth * 25, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineExactlyTheViewWidthWithNoNewline_whenPositionIsOnePlaceAfterTheLastCharacterOfTheLine_andBiasForward_thenModelToViewReturnsThePositionRightOfTheLastCharacter() throws Exception { + int viewWidth = charWidth * 26; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghijklmnopqrstuvwxyz",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(26, new Rectangle(0, 0, viewWidth, lineHeight), Forward); + + assertEquals(charWidth * 26, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineExactlyTheViewWidthWithNoNewline_whenPositionIsOnePlaceAfterTheLastCharacterOfTheLine_andBiasBackward_thenModelToViewReturnsThePositionRightOfTheLastCharacter() throws Exception { + int viewWidth = charWidth * 26; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghijklmnopqrstuvwxyz",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(26, new Rectangle(0, 0, viewWidth, lineHeight), Backward); + + assertEquals(charWidth * 26, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test(expected = BadLocationException.class) + public void givenALineExactlyTheViewWidthWithNoNewline_whenPositionIsOnePlaceAfterTheLastCharacterOfTheLine_andBiasForward_thenModelToViewThrowsBadLocationException() throws Exception { + int viewWidth = charWidth * 26; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghijklmnopqrstuvwxyz",null); + View line = view.getView(0); + + line.modelToView(27, new Rectangle(0, 0, viewWidth, lineHeight), Forward); + } + + @Test(expected = BadLocationException.class) + public void givenALineExactlyTheViewWidthWithNoNewline_whenPositionIsOnePlaceAfterTheLastCharacterOfTheLine_andBiasBackward_thenModelToViewThrowsBadLocationException() throws Exception { + int viewWidth = charWidth * 26; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghijklmnopqrstuvwxyz",null); + View line = view.getView(0); + + line.modelToView(27, new Rectangle(0, 0, viewWidth, lineHeight), Backward); + } + + @Test + public void givenALineExactlyTwoViewWidthsWithNoNewline_whenPositionIsAtTheLastCharacterOfTheSecondPhysicalLine_andBiasForward_thenModelToViewReturnsThePositionOfTheCharacter() throws Exception { + int viewWidth = charWidth * 26; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(52, new Rectangle(0, 0, viewWidth, lineHeight * 2), Forward); + + assertEquals(charWidth * 26, screenPosition.x); + assertEquals(lineHeight, screenPosition.y); + } + + @Test + public void givenATokenLongerThanOnePhysicalLine_whenPositionIsAtACharacterOnTheSecondPhysicalLine_andBiasForward_thenModelToViewReturnsThePositionOfTheCharacter() throws Exception { + int viewWidth = charWidth * 26; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(38, new Rectangle(0, 0, viewWidth, lineHeight * 2), Forward); + + assertEquals(charWidth * 12, screenPosition.x); + assertEquals(lineHeight, screenPosition.y); + } + + @Test + public void givenALineFittingWithinAllocationWidth_whenPositionIsAtACharacter_andTheAllocatedRegionIsOffsetInTheYAxis_thenModelToViewReturnsThePositionOfTheCharacterOffsetInTheYAxisByTheSameAmount() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(7, new Rectangle(0, 120, VIEW_WIDTH, lineHeight), Backward); + + assertEquals(charWidth * 7, screenPosition.x); + assertEquals(120, screenPosition.y); + } + + @Test + public void givenALineWrappedOverSeveralPhysicalLines_whenPositionIsAtACharacterOfTheSecondPhysicalLine_thenModelToViewReturnsThePositionOfTheCharacter() throws Exception { + int viewWidth = charWidth * 25; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "This is a line of text which will be wrapped over several lines due to its excessive length.\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(37, new Rectangle(0, 0, viewWidth, lineHeight * 4), Forward); + + assertEquals(charWidth * 14, screenPosition.x); + assertEquals(lineHeight, screenPosition.y); + } + + @Test + public void givenALineWrappedOverSeveralPhysicalLinesBySpaces_whenPositionIsAtTheSpaceBreakingTheFirstPhysicalLine_andBiasForward_thenModelToViewReturnsThePositionOfTheSpace() throws Exception { + int viewWidth = charWidth * 25; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "xxxx xx x xxxx xx xxxxxxxxxxx xxxxxx xxxx.\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(17, new Rectangle(0, 0, viewWidth, lineHeight * 2), Forward); + + assertEquals(charWidth * 17, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineWrappedOverSeveralPhysicalLinesBySpaces_whenPositionIsAtTheSpaceBreakingTheFirstPhysicalLine_andBiasBackward_thenModelToViewReturnsThePositionOfTheSpace() throws Exception { + int viewWidth = charWidth * 25; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "xxxx xx x xxxx xx xxxxxxxxxxx xxxxxx xxxx.\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(17, new Rectangle(0, 0, viewWidth, lineHeight * 2), Backward); + + assertEquals(charWidth * 17, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineWrappedOverSeveralPhysicalLinesBySpaces_whenPositionIsAtTheFirstCharacterOfTheSecondPhysicalLine_andBiasForward_thenModelToViewReturnsThePositionOfTheCharacter() throws Exception { + int viewWidth = charWidth * 25; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "xxxx xx x xxxx xx xxxxxxxxxxx xxxxxx xxxx.\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(18, new Rectangle(0, 0, viewWidth, lineHeight * 2), Forward); + + assertEquals(0, screenPosition.x); + assertEquals(lineHeight, screenPosition.y); + } + + @Test + public void givenALineWrappedOverSeveralPhysicalLinesBySpaces_whenPositionIsAtTheFirstCharacterOfTheSecondPhysicalLine_andBiasBackward_thenModelToViewReturnsThePositionRightOfTheSpaceBreakingTheFirstLine() throws Exception { + int viewWidth = charWidth * 25; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "xxxx xx x xxxx xx xxxxxxxxxxx xxxxxx xxxx.\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(18, new Rectangle(0, 0, viewWidth, lineHeight * 2), Backward); + + assertEquals(charWidth * 18, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineWrappedOverSeveralPhysicalLinesBySpaces_whenPositionIsAtTheSecondCharacterOfTheSecondPhysicalLine_andBiasForward_thenModelToViewReturnsThePositionOfTheCharacter() throws Exception { + int viewWidth = charWidth * 25; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "xxxx xx x xxxx xx xxxxxxxxxxx xxxxxx xxxx.\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(19, new Rectangle(0, 0, viewWidth, lineHeight * 2), Forward); + + assertEquals(charWidth, screenPosition.x); + assertEquals(lineHeight, screenPosition.y); + } + + @Test + public void givenALineWrappedOverSeveralPhysicalLinesBySpaces_whenPositionIsAtTheSecondCharacterOfTheSecondPhysicalLine_andBiasBackward_thenModelToViewReturnsThePositionOfTheCharacter() throws Exception { + int viewWidth = charWidth * 25; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "xxxx xx x xxxx xx xxxxxxxxxxx xxxxxx xxxx.\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(19, new Rectangle(0, 0, viewWidth, lineHeight * 2), Backward); + + assertEquals(charWidth, screenPosition.x); + assertEquals(lineHeight, screenPosition.y); + } + + @Test + public void givenALineWrappedOverSeveralPhysicalLinesWithoutSpaces_whenPositionIsAtTheFirstCharacterOfTheSecondPhysicalLine_andBiasForward_thenModelToViewReturnsThePositionOfTheCharacter() throws Exception { + int viewWidth = charWidth * 26; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghijklmnopqrstuvwxyzabc\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(26, new Rectangle(0, 0, viewWidth, lineHeight * 2), Forward); + + assertEquals(0, screenPosition.x); + assertEquals(lineHeight, screenPosition.y); + } + + @Test + public void givenALineWrappedOverSeveralPhysicalLinesWithoutSpaces_whenPositionIsAtTheFirstCharacterOfTheSecondPhysicalLine_andBiasBackward_thenModelToViewReturnsThePositionRightOfTheLastCharacterOfTheFirstLine() throws Exception { + int viewWidth = charWidth * 26; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghijklmnopqrstuvwxyzabc\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(26, new Rectangle(0, 0, viewWidth, lineHeight * 2), Backward); + + assertEquals(charWidth * 26, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineWrappedOverSeveralPhysicalLinesWithoutSpaces_whenPositionIsAtTheSecondCharacterOfTheSecondPhysicalLine_andBiasForward_thenModelToViewReturnsThePositionOfTheCharacter() throws Exception { + int viewWidth = charWidth * 26; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghijklmnopqrstuvwxyzabc\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(27, new Rectangle(0, 0, viewWidth, lineHeight * 2), Forward); + + assertEquals(charWidth, screenPosition.x); + assertEquals(lineHeight, screenPosition.y); + } + + @Test + public void givenALineWrappedOverSeveralPhysicalLinesWithoutSpaces_whenPositionIsAtTheSecondCharacterOfTheSecondPhysicalLine_andBiasBackward_thenModelToViewReturnsThePositionOfTheCharacter() throws Exception { + int viewWidth = charWidth * 26; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghijklmnopqrstuvwxyzabc\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(27, new Rectangle(0, 0, viewWidth, lineHeight * 2), Backward); + + assertEquals(charWidth, screenPosition.x); + assertEquals(lineHeight, screenPosition.y); + } + + @Test + public void givenALineWithATabAtTheBeginningFittingWithinAllocationWidth_whenPositionIsAtTheTab_thenModelToViewReturnsThePositionOfTheTab() throws Exception { + document.replace(0, document.getLength(), "\tword.\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(0, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), Forward); + + assertEquals(0, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineWithATabAtTheBeginningFittingWithinAllocationWidth_whenPositionIsOnePlaceAfterTheTab_thenModelToViewReturnsThePositionOfTheCharacterAfterTheTab() throws Exception { + document.replace(0, document.getLength(), "\tword.\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(1, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), Forward); + + assertEquals(charWidth * 5, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineWithATabNotAtATabStopFittingWithinAllocationWidth_whenPositionIsAtTheTab_thenModelToViewReturnsThePositionOfTheTab() throws Exception { + document.replace(0, document.getLength(), "wo\trd.\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(2, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), Forward); + + assertEquals(charWidth * 2, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineWithATabNotAtATabStopFittingWithinAllocationWidth_whenPositionIsImmediatelyAfterTheTab_thenModelToViewReturnsThePositionOfTheCharacterAfterTheTab() throws Exception { + document.replace(0, document.getLength(), "wo\trd.\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(3, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), Forward); + + assertEquals(charWidth * 5, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineFittingExactlyWithinAllocationWidthWithATabAtTheEnd_whenPositionIsImmediatelyAfterTheTab_andBiasForward_thenModelToViewReturnsThePositionOfTheNewlineCharacter() throws Exception { + int viewWidth = charWidth * 10; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdef\t\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(7, new Rectangle(0, 0, viewWidth, lineHeight), Forward); + + assertEquals(charWidth * 10, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineFittingExactlyWithinAllocationWidthWithATabAtTheEnd_whenPositionIsImmediatelyAfterTheTab_andBiasBackward_thenModelToViewReturnsThePositionOfTheNewlineCharacter() throws Exception { + int viewWidth = charWidth * 10; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdef\t\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(7, new Rectangle(0, 0, viewWidth, lineHeight), Backward); + + assertEquals(charWidth * 10, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineFittingExactlyWithinAllocationWidthAndContainingATab_whenPositionIsImmediatelyAfterTheTab_thenModelToViewReturnsThePosition() throws Exception { + int viewWidth = charWidth * 8; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abc\tdef\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(4, new Rectangle(0, 0, viewWidth, lineHeight), Forward); + + assertEquals(charWidth * 5, screenPosition.x); + assertEquals(0, screenPosition.y); + } + + @Test + public void givenALineFittingExactlyInTwoPhysicalLinesAndContainingATabOnTheFirstLine_whenPositionIsOnTheSecondLine_thenModelToViewReturnsThePosition() throws Exception { + int viewWidth = charWidth * 8; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abc\tde f ghijkl\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(10, new Rectangle(0, 0, viewWidth, lineHeight * 2), Forward); + + assertEquals(charWidth * 3, screenPosition.x); + assertEquals(lineHeight, screenPosition.y); + } + + @Test + public void givenALineFittingExactlyInTwoPhysicalLinesAndContainingATabOnTheSecondLine_whenPositionIsAfterTheTabOnTheSecondLine_thenModelToViewReturnsThePosition() throws Exception { + int viewWidth = charWidth * 8; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghij\tklm\n",null); + View line = view.getView(0); + + Rectangle screenPosition = (Rectangle) line.modelToView(12, new Rectangle(0, 0, viewWidth, lineHeight * 2), Forward); + + System.err.println("(" + screenPosition.x + ", " + screenPosition.y + ")"); + assertEquals(lineHeight, screenPosition.y); + assertEquals(charWidth * 6, screenPosition.x); + } +} diff --git a/RSyntaxTextArea/src/test/java/org/fife/ui/rsyntaxtextarea/WrappedSyntaxViewMonospacedViewToModelTest.java b/RSyntaxTextArea/src/test/java/org/fife/ui/rsyntaxtextarea/WrappedSyntaxViewMonospacedViewToModelTest.java new file mode 100644 index 000000000..efe3d13e7 --- /dev/null +++ b/RSyntaxTextArea/src/test/java/org/fife/ui/rsyntaxtextarea/WrappedSyntaxViewMonospacedViewToModelTest.java @@ -0,0 +1,474 @@ +package org.fife.ui.rsyntaxtextarea; + +import org.fife.ui.rtextarea.RTextAreaBase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import javax.swing.JPanel; +import javax.swing.text.Position; +import javax.swing.text.View; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Rectangle; +import java.util.Collection; + +import static java.util.Arrays.asList; +import static org.fife.ui.rsyntaxtextarea.SyntaxConstants.SYNTAX_STYLE_NONE; +import static org.junit.Assert.assertEquals; + + +/** + * Unit tests for the {@link WrappedSyntaxView} class. + * + * @author Mike Smith + * @version 1.0 + */ +@RunWith(Parameterized.class) +public class WrappedSyntaxViewMonospacedViewToModelTest +{ + private static final int VIEW_WIDTH = 1000; + private static final int VIEW_HEIGHT = 800; + + private final int fontSize; + + private RSyntaxDocument document; + private WrappedSyntaxView view; + private int lineHeight; + private int charWidth; + + @Parameterized.Parameters (name = "{0}") + public static Collection data() + { + return asList(new Object[][] {{15}, {16}, {18}}); + } + + public WrappedSyntaxViewMonospacedViewToModelTest(int fontSize) + { + this.fontSize = fontSize; + } + + @Before + public void setUp() + { + document = new RSyntaxDocument(SYNTAX_STYLE_NONE); + RSyntaxTextArea textArea = new RSyntaxTextArea(document); + + Font font = RTextAreaBase.getDefaultFont().deriveFont(Font.PLAIN, fontSize); + FontMetrics fontMetrics = new JPanel().getFontMetrics(font); + charWidth = fontMetrics.charWidth(32); + textArea.setFont(font); + textArea.setLineWrap(true); + textArea.getSyntaxScheme().getStyle(TokenTypes.IDENTIFIER).font = font; + textArea.getSyntaxScheme().getStyle(TokenTypes.IDENTIFIER).fontMetrics = fontMetrics; + textArea.getSyntaxScheme().getStyle(TokenTypes.WHITESPACE).font = font; + textArea.getSyntaxScheme().getStyle(TokenTypes.WHITESPACE).fontMetrics = fontMetrics; + lineHeight = textArea.getLineHeight(); + view = (WrappedSyntaxView) textArea.getUI().getRootView(textArea).getView(0); + view.setSize(VIEW_WIDTH, VIEW_HEIGHT); + } + + @Test + public void givenALineFittingWithinAllocationWidth_whenPositionIsAtTheStartOfTheLine_thenViewToModelReturnsThePositionOfTheFirstCharacter() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(0, lineHeight / 2f, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), new Position.Bias[1]); + + assertEquals(0, offset); + } + + @Test + public void givenALineFittingWithinAllocationWidth_whenPositionIsLeftOfTheCentreOfTheFirstCharacter_thenViewToModelReturnsThePositionOfTheFirstCharacter() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth / 2f - 1f, lineHeight / 2f, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), new Position.Bias[1]); + + assertEquals(0, offset); + } + + @Test + public void givenALineFittingWithinAllocationWidth_whenPositionIsRightOfTheCentreOfTheFirstCharacter_thenViewToModelReturnsThePositionOfTheSecondCharacter() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth / 2f + 1f, lineHeight / 2f, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), new Position.Bias[1]); + + assertEquals(1, offset); + } + + @Test + public void givenALineFittingWithinAllocationWidth_whenPositionIsAtTheStartOfACharacter_thenViewToModelReturnsThePositionOfTheCharacter() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth * 7f, lineHeight / 2f, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), new Position.Bias[1]); + + assertEquals(7, offset); + } + + @Test + public void givenALineFittingWithinAllocationWidth_whenPositionIsLeftOfTheCentreOfACharacter_thenViewToModelReturnsThePositionOfTheCharacter() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth * 7.5f - 1f, lineHeight / 2f, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), new Position.Bias[1]); + + assertEquals(7, offset); + } + + @Test + public void givenALineFittingWithinAllocationWidth_whenPositionIsRightOfTheCentreOfACharacter_thenViewToModelReturnsThePositionOfTheNextCharacter() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth * 7.5f + 1f, lineHeight / 2f, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), new Position.Bias[1]); + + assertEquals(8, offset); + } + + @Test + public void givenALineFittingWithinAllocationWidth_whenPositionIsAfterTheLastCharacter_thenViewToModelReturnsThePositionOfTheLastCharacter() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth * 70f, lineHeight / 2f, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), new Position.Bias[1]); + + assertEquals(65, offset); + } + + @Test + public void givenALineFittingWithinAllocationWidth_whenPositionIsAfterTheViewWidth_thenViewToModelReturnsThePositionOfTheLastCharacter() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(VIEW_WIDTH + 100, lineHeight / 2f, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), new Position.Bias[1]); + + assertEquals(65, offset); + } + + @Test + public void givenTwoLinesFittingWithinAllocationWidth_whenPositionIsAfterTheLastCharacterOfTheSecondLine_thenViewToModelReturnsThePositionOfTheLastCharacter() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\nShort line.\n",null); + View line = view.getView(1); + + int offset = line.viewToModel(charWidth * 20f, lineHeight * 1.5f, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), new Position.Bias[1]); + + assertEquals(77, offset); + } + + @Test + public void givenALineFittingWithinAllocationWidth_whenPositionAndAllocAreOffsetInYAxis_andPositionIsLeftOfTheCentreOfTheCharacter_thenViewToModelReturnsThePositionOfTheCharacter() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth * 10f + 2f, 120.0f + lineHeight / 2f, new Rectangle(0, 120, VIEW_WIDTH, lineHeight), new Position.Bias[1]); + + assertEquals(10, offset); + } + + @Test + public void givenALineFittingWithinAllocationWidth_whenPositionAndAllocAreOffsetInYAxis_andPositionIsAfterTheViewWidth_thenViewToModelReturnsThePositionOfTheLastCharacter() throws Exception { + document.replace(0, document.getLength(), "This is a line of text. There are a number of words on this line.\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(VIEW_WIDTH + 100, 120.0f + lineHeight / 2f, new Rectangle(0, 120, VIEW_WIDTH, lineHeight), new Position.Bias[1]); + + assertEquals(65, offset); + } + + @Test + public void givenALineWithWhitespaceFittingExactlyWithinAllocationWidth_whenPositionIsWithinACharacterOfTheLine_thenViewToModelReturnsThePositionOfTheCharacter() throws Exception { + int viewWidth = charWidth * 23; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "This is a line of text.\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth * 14f + 2f, lineHeight / 2f, new Rectangle(0, 0, viewWidth, lineHeight), new Position.Bias[1]); + + assertEquals(14, offset); + } + + @Test + public void givenALineWithNoWhitespaceFittingExactlyWithinAllocationWidth_whenPositionIsWithinACharacterOfTheLine_thenViewToModelReturnsThePositionOfTheCharacter() throws Exception { + int viewWidth = charWidth * 52; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth * 14f + 2f, lineHeight / 2f, new Rectangle(0, 0, viewWidth, lineHeight), new Position.Bias[1]); + + assertEquals(14, offset); + } + + @Test + public void givenALineWithWhitespaceFittingExactlyWithinAllocationWidth_whenPositionIsAfterTheEndOfTheLine_thenViewToModelReturnsThePositionAfterTheLastCharacter() throws Exception { + int viewWidth = charWidth * 23; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "This is a line of text.\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(viewWidth + 100, lineHeight / 2f, new Rectangle(0, 0, viewWidth, lineHeight), new Position.Bias[1]); + + assertEquals(23, offset); + } + + @Test + public void givenALineWithNoWhitespaceFittingExactlyWithinAllocationWidth_whenPositionIsAfterTheEndOfTheLine_thenViewToModelReturnsThePositionAfterTheLastCharacter() throws Exception { + int viewWidth = charWidth * 52; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(viewWidth + 100, lineHeight / 2f, new Rectangle(0, 0, viewWidth, lineHeight), new Position.Bias[1]); + + assertEquals(52, offset); + } + + @Test + public void givenALineWithWhitespaceWrappedOverSeveralPhysicalLines_whenPositionIsWithinACharacterOfTheSecondPhysicalLine_thenViewToModelReturnsThePositionOfTheCharacter() throws Exception { + int viewWidth = charWidth * 25; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "This is a line of text which will be wrapped over several lines due to its excessive length.\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth * 14f + 2f, lineHeight * 1.5f, new Rectangle(0, 0, viewWidth, lineHeight * 4), new Position.Bias[1]); + + assertEquals(37, offset); + } + + @Test + public void givenALineWithNoWhitespaceWrappedOverSeveralPhysicalLines_whenPositionIsWithinACharacterOfTheSecondPhysicalLine_thenViewToModelReturnsThePositionOfTheCharacter() throws Exception { + int viewWidth = charWidth * 20; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth * 14f + 2f, lineHeight * 1.5f, new Rectangle(0, 0, viewWidth, lineHeight * 4), new Position.Bias[1]); + + assertEquals(34, offset); + } + + @Test + public void givenALineWithNoWhitespaceWrappedExactlyOverSeveralPhysicalLines_whenPositionIsWithinACharacterOfTheSecondPhysicalLine_thenViewToModelReturnsThePositionOfTheCharacter() throws Exception { + int viewWidth = charWidth * 26; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth * 14f + 2f, lineHeight * 1.5f, new Rectangle(0, 0, viewWidth, lineHeight * 4), new Position.Bias[1]); + + assertEquals(40, offset); + } + + @Test + public void givenALineWithWhitespaceWrappedOverSeveralPhysicalLines_whenPositionIsAfterTheEndOfTheFirstLine_thenViewToModelReturnsThePositionOfTheFirstCharacterOfTheSecondLine() throws Exception { + int viewWidth = charWidth * 25; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "xxxx xx x xxxx xx xxxxxxxxxxx xxxxxx xxxx.\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth * 20, lineHeight / 2f, new Rectangle(0, 0, viewWidth, lineHeight * 2), new Position.Bias[1]); + + assertEquals(17, offset); + } + + @Test + public void givenALineWithNoWhitespaceWrappedOverSeveralPhysicalLines_whenPositionIsAfterTheEndOfTheSecondLine_thenViewToModelReturnsThePositionAfterTheLastCharacterOfTheSecondLine() throws Exception { + int viewWidth = charWidth * 35; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth * 22, lineHeight * 1.5f, new Rectangle(0, 0, viewWidth, lineHeight * 2), new Position.Bias[1]); + + assertEquals(52, offset); + } + + @Test + public void givenALineWithWhitespaceWrappedOverSeveralPhysicalLines_whenPositionIsAfterTheViewWidthOnTheFirstLine_thenViewToModelReturnsThePositionOfTheLastCharacterOnTheLine() throws Exception { + int viewWidth = charWidth * 25; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "xxxx xx x xxxx xx xxxxxxxxxxx xxxxxx xxxx.\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(VIEW_WIDTH + 100, lineHeight / 2f, new Rectangle(0, 0, viewWidth, lineHeight * 2), new Position.Bias[1]); + + assertEquals(17, offset); + } + + @Test + public void givenALineWithWhitespaceWrappedOverSeveralPhysicalLines_whenPositionIsAfterTheViewWidthOnTheSecondLine_thenViewToModelReturnsThePositionOfTheLastCharacterOnTheLine() throws Exception { + int viewWidth = charWidth * 25; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "xxxx xx x xxxx xx xxxxxxxxxxx xxxxxx xxxx.\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(VIEW_WIDTH + 100, lineHeight * 1.5f, new Rectangle(0, 0, viewWidth, lineHeight * 2), new Position.Bias[1]); + + assertEquals(42, offset); + } + + @Test + public void givenALineWithNoWhitespaceWrappedOverSeveralPhysicalLines_whenPositionIsAfterTheViewWidthOnTheFirstLine_thenViewToModelReturnsThePositionOfTheNextToLastCharacterOnTheLine() throws Exception { + int viewWidth = charWidth * 20; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(VIEW_WIDTH + 100, lineHeight / 2f, new Rectangle(0, 0, viewWidth, lineHeight * 2), new Position.Bias[1]); + + assertEquals(19, offset); + } + + @Test + public void givenALineWithNoWhitespaceWrappedOverSeveralPhysicalLines_whenPositionIsAfterTheViewWidthOnTheSecondLine_thenViewToModelReturnsThePositionOfTheNextToLastCharacterOnTheLine() throws Exception { + int viewWidth = charWidth * 20; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(VIEW_WIDTH + 100, lineHeight * 1.5f, new Rectangle(0, 0, viewWidth, lineHeight * 2), new Position.Bias[1]); + + assertEquals(39, offset); + } + + @Test + public void givenALineWithNoWhitespaceWrappedExactlyOverSeveralPhysicalLines_whenPositionIsAfterTheViewWidthOnTheSecondLine_thenViewToModelReturnsThePositionOfTheLastCharacterOnTheLine() throws Exception { + int viewWidth = charWidth * 26; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(VIEW_WIDTH + 100, lineHeight * 1.5f, new Rectangle(0, 0, viewWidth, lineHeight * 2), new Position.Bias[1]); + + assertEquals(52, offset); + } + + @Test + public void givenALineWrappedExactlyOverSeveralPhysicalLines_whenPositionIsWithinAWrappedLine_thenViewToModelReturnsThePositionOfTheCharacter() throws Exception { + int viewWidth = charWidth * 26; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth * 10, lineHeight * 1.5f, new Rectangle(0, 0, viewWidth, lineHeight * 2), new Position.Bias[1]); + + assertEquals(36, offset); + } + + @Test + public void givenALineWithATabAtTheBeginningFittingWithinAllocationWidth_whenPositionIsAtTheEndOfTheTab_thenViewToModelReturnsThePositionOfTheLetterAfterTheTab() throws Exception { + document.replace(0, document.getLength(), "\tword.\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth * 5, lineHeight / 2f, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), new Position.Bias[1]); + + assertEquals(1, offset); + } + + @Test + public void givenALineWithATabNotAtATabStopFittingWithinAllocationWidth_whenPositionIsAfterFirstCharOfTheTab_thenViewToModelReturnsThePositionOfTheTab() throws Exception { + document.replace(0, document.getLength(), "wo\trd.\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth * 3, lineHeight / 2f, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), new Position.Bias[1]); + + assertEquals(2, offset); + } + + @Test + public void givenALineWithATabNotAtATabStopFittingWithinAllocationWidth_whenPositionIsAfterSecondCharOfTheTab_thenViewToModelReturnsThePositionOfTheLetterAfterTheTab() throws Exception { + document.replace(0, document.getLength(), "wo\trd.\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth * 4, lineHeight / 2f, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), new Position.Bias[1]); + + assertEquals(3, offset); + } + + @Test + public void givenALineWithATabNotAtATabStopFittingWithinAllocationWidth_whenPositionIsAtTheEndOfTheTab_thenViewToModelReturnsThePositionOfTheLetterAfterTheTab() throws Exception { + document.replace(0, document.getLength(), "wo\trd.\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth * 5, lineHeight / 2f, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), new Position.Bias[1]); + + assertEquals(3, offset); + } + + @Test + public void givenALineFittingExactlyWithinAllocationWidthWithATabAtTheEnd_whenPositionIsAfterTheTab_thenViewToModelReturnsThePositionAfterTheTab() throws Exception { + int viewWidth = charWidth * 10; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdef\t\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth * 10, lineHeight / 2f, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), new Position.Bias[1]); + + assertEquals(7, offset); + } + + @Test + public void givenALineFittingExactlyWithinAllocationWidthAndContainingATab_whenPositionIsAfterTheTab_thenViewToModelReturnsThePosition() throws Exception { + int viewWidth = charWidth * 8; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abc\tdef\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth * 6, lineHeight / 2f, new Rectangle(0, 0, VIEW_WIDTH, lineHeight), new Position.Bias[1]); + + assertEquals(5, offset); + } + + @Test + public void givenALineFittingExactlyInTwoPhysicalLinesAndContainingATabOnTheFirstLine_whenPositionIsOnTheSecondLine_thenViewToModelReturnsThePosition() throws Exception { + int viewWidth = charWidth * 8; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abc\tde f ghijkl\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth * 3, lineHeight * 1.5f, new Rectangle(0, 0, viewWidth, lineHeight * 2), new Position.Bias[1]); + + assertEquals(10, offset); + } + + @Test + public void givenALineFittingExactlyInTwoPhysicalLinesAndContainingATabOnTheSecondLine_whenPositionIsOnTheSecondLine_thenViewToModelReturnsThePosition() throws Exception { + int viewWidth = charWidth * 8; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefghij\tklm\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth * 6, lineHeight * 1.5f, new Rectangle(0, 0, viewWidth, lineHeight * 2), new Position.Bias[1]); + + assertEquals(12, offset); + } + + @Test + public void givenALineWithMultipleTokensFittingExactlyInTwoPhysicalLinesAndContainingATabOnTheSecondLine_whenPositionIsOnTheSecondLine_thenViewToModelReturnsThePosition() throws Exception { + int viewWidth = charWidth * 8; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abc def gh\tijk\n",null); + View line = view.getView(0); + + int offset = line.viewToModel(charWidth * 6, lineHeight * 1.5f, new Rectangle(0, 0, viewWidth, lineHeight * 2), new Position.Bias[1]); + + assertEquals(12, offset); + } + + @Test + public void givenTwoLinesWhereTheSecondHasASingleTokenOverFourPhysicalLines_whenASpaceIsInsertedInTheSecondLogicalLine_andPositionIsAfterWhereTheSpaceWasInserted_thenViewToModelReturnsThePosition() throws Exception { + int viewWidth = charWidth * 10; + view.setSize(viewWidth, VIEW_HEIGHT); + document.replace(0, document.getLength(), "abcdefg\nabcdefghij1234567890abcdefghij1234567890\n",null); + document.insertString(27, " ", null); + View line = view.getView(1); + + int offset = line.viewToModel(charWidth * 8, lineHeight * 3.5f, new Rectangle(0, 0, viewWidth, lineHeight * 4), new Position.Bias[1]); + + assertEquals(46, offset); + } +}