provide text lines with their sub-ranges for a given range of text

Signed-off-by: Florian Thienel <florian@thienel.org>
diff --git a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/dom/ContentTest.java b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/dom/ContentTest.java
index 9a92e9d..bdb3982 100644
--- a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/dom/ContentTest.java
+++ b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/dom/ContentTest.java
@@ -17,6 +17,7 @@
 import org.eclipse.vex.core.provisional.dom.ContentRange;

 import org.eclipse.vex.core.provisional.dom.IContent;

 import org.eclipse.vex.core.provisional.dom.IPosition;

+import org.eclipse.vex.core.provisional.dom.MultilineText;

 import org.junit.Before;

 import org.junit.Test;

 

@@ -236,4 +237,41 @@
 		assertTrue("after", positionAfter.isValid());

 	}

 

+	@Test

+	public void givenTextWithLineBreaks_shouldProvideLinesWithRanges() throws Exception {

+		content.insertText(0, "line 1\nline 2\nline 3");

+

+		final MultilineText multilineText = content.getMultilineText(content.getRange());

+

+		assertEquals(3, multilineText.size());

+		assertEquals("line 1\n", multilineText.getText(0));

+		assertEquals(new ContentRange(0, 6), multilineText.getRange(0));

+		assertEquals("line 2\n", multilineText.getText(1));

+		assertEquals(new ContentRange(7, 13), multilineText.getRange(1));

+		assertEquals("line 3", multilineText.getText(2));

+		assertEquals(new ContentRange(14, 19), multilineText.getRange(2));

+	}

+

+	@Test

+	public void givenTextWithoutLineBreaks_shouldProvideSingleLineWithRange() throws Exception {

+		content.insertText(0, "line 1");

+

+		final MultilineText multilineText = content.getMultilineText(content.getRange());

+

+		assertEquals(1, multilineText.size());

+		assertEquals("line 1", multilineText.getText(0));

+		assertEquals(new ContentRange(0, 5), multilineText.getRange(0));

+	}

+

+	@Test

+	public void givenTextWithLineBreaks_whenTextEndsWithLineBreak_shouldNotProvideEmptyLineAsLastLine() throws Exception {

+		content.insertText(0, "line 1\nline 2\nline 3\n");

+

+		final MultilineText multilineText = content.getMultilineText(content.getRange());

+

+		assertEquals(3, multilineText.size());

+		assertEquals("line 3\n", multilineText.getText(2));

+		assertEquals(new ContentRange(14, 20), multilineText.getRange(2));

+	}

+

 }

diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/GapContent.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/GapContent.java
index 0b6b2c3..778f272 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/GapContent.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/GapContent.java
@@ -23,6 +23,7 @@
 import org.eclipse.vex.core.provisional.dom.ContentRange;
 import org.eclipse.vex.core.provisional.dom.IContent;
 import org.eclipse.vex.core.provisional.dom.IPosition;
+import org.eclipse.vex.core.provisional.dom.MultilineText;
 
 /**
  * Implementation of the <code>Content</code> interface that manages changes efficiently. Implements a buffer that keeps
@@ -37,6 +38,7 @@
 	private static final float GROWTH_RATE_SLOW = 1.1f;
 
 	private static final char TAG_MARKER = '\0';
+	private static final char LINE_BREAK = '\n';
 
 	private char[] content;
 	private int gapStart;
@@ -152,6 +154,18 @@
 		return c == TAG_MARKER;
 	}
 
+	public boolean isLineBreak(final int offset) {
+		if (offset < 0 || offset >= length()) {
+			return false;
+		}
+
+		return isLineBreak(content[getIndex(offset)]);
+	}
+
+	private boolean isLineBreak(final char c) {
+		return c == LINE_BREAK;
+	}
+
 	@Override
 	public void remove(final ContentRange range) {
 		assertOffset(range.getStartOffset(), 0, length() - range.length());
@@ -240,6 +254,35 @@
 	}
 
 	@Override
+	public MultilineText getMultilineText(final ContentRange range) {
+		Assert.isTrue(getRange().contains(range));
+		final MultilineText result = new MultilineText();
+
+		StringBuilder currentLine = new StringBuilder();
+		int lineStart = range.getStartOffset();
+		for (int i = range.getStartOffset(); i <= range.getEndOffset(); i += 1) {
+			final char c = charAt(i);
+			if (isTagMarker(c)) {
+				// ignore tag markers
+			} else if (isLineBreak(c)) {
+				currentLine.append(c);
+				final ContentRange lineRange = new ContentRange(lineStart, i);
+				result.appendLine(currentLine.toString(), lineRange);
+				currentLine = new StringBuilder();
+				lineStart = i + 1;
+			} else {
+				currentLine.append(c);
+			}
+		}
+
+		if (currentLine.length() > 0) {
+			result.appendLine(currentLine.toString(), new ContentRange(lineStart, range.getEndOffset()));
+		}
+
+		return result;
+	}
+
+	@Override
 	public void insertContent(final int offset, final IContent content) {
 		assertOffset(offset, 0, length());
 
@@ -294,11 +337,7 @@
 	 */
 	@Override
 	public char charAt(final int offset) {
-		if (offset < gapStart) {
-			return content[offset];
-		} else {
-			return content[offset - gapStart + gapEnd];
-		}
+		return content[getIndex(offset)];
 	}
 
 	/**
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/provisional/dom/IContent.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/provisional/dom/IContent.java
index aa396ae..18a0f38 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/provisional/dom/IContent.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/provisional/dom/IContent.java
@@ -77,6 +77,8 @@
 	 */
 	String getRawText();
 
+	MultilineText getMultilineText(final ContentRange range);
+
 	/**
 	 * Insert the given content into this content at the given offset.
 	 *
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/provisional/dom/MultilineText.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/provisional/dom/MultilineText.java
new file mode 100644
index 0000000..120e70e
--- /dev/null
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/provisional/dom/MultilineText.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Florian Thienel and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * 		Florian Thienel - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.vex.core.provisional.dom;
+
+import java.util.ArrayList;
+
+public class MultilineText {
+
+	private final ArrayList<Line> lines = new ArrayList<Line>();
+
+	public void appendLine(final String text, final ContentRange range) {
+		lines.add(new Line(text, range));
+	}
+
+	public int size() {
+		return lines.size();
+	}
+
+	public String getText(final int lineIndex) {
+		final Line line = lines.get(lineIndex);
+		return line.text;
+	}
+
+	public ContentRange getRange(final int lineIndex) {
+		final Line line = lines.get(lineIndex);
+		return line.range;
+	}
+
+	private static class Line {
+		public final String text;
+		public final ContentRange range;
+
+		public Line(final String text, final ContentRange range) {
+			this.text = text;
+			this.range = range;
+		}
+	}
+}