test and fix the editing editing functionality

Signed-off-by: Florian Thienel <florian@thienel.org>
diff --git a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/FakeCursor.java b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/FakeCursor.java
index 9060e9f..e1f574d 100644
--- a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/FakeCursor.java
+++ b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/FakeCursor.java
@@ -16,37 +16,43 @@
 	private final LinkedList<ICursorPositionListener> cursorPositionListeners = new LinkedList<ICursorPositionListener>();
 	private final Graphics graphics = new FakeGraphics();
 	private final ContentTopology contentTopology;
+	private final BalancingSelector selector;
 
-	private int offset;
-	private boolean hasSelection;
-	private int selectionStartOffset;
-	private int selectionEndOffset;
+	private IDocument document;
 
 	public FakeCursor(final IDocument document) {
+		this.document = document;
 		contentTopology = new ContentTopology() {
 			@Override
 			public int getLastOffset() {
-				return document.getEndOffset();
+				return FakeCursor.this.document.getEndOffset();
 			}
 		};
+		selector = new BalancingSelector();
+		selector.setDocument(document);
+	}
+
+	public void setDocument(final IDocument document) {
+		this.document = document;
+		selector.setDocument(document);
 	}
 
 	@Override
 	public int getOffset() {
-		return offset;
+		return selector.getCaretOffset();
 	}
 
 	@Override
 	public boolean hasSelection() {
-		return hasSelection;
+		return selector.isActive();
 	}
 
 	@Override
 	public ContentRange getSelectedRange() {
-		if (hasSelection) {
-			return new ContentRange(selectionStartOffset, selectionEndOffset);
+		if (selector.isActive()) {
+			return selector.getRange();
 		} else {
-			return new ContentRange(offset, offset);
+			return new ContentRange(selector.getCaretOffset(), selector.getCaretOffset());
 		}
 	}
 
@@ -75,21 +81,20 @@
 	@Override
 	public void move(final ICursorMove move) {
 		firePositionAboutToChange();
-		offset = move.calculateNewOffset(graphics, contentTopology, offset, null, null, 0);
-		hasSelection = false;
-		firePositionChanged(offset);
+		selector.setMark(move.calculateNewOffset(graphics, contentTopology, selector.getCaretOffset(), null, null, 0));
+		firePositionChanged(selector.getCaretOffset());
 	}
 
 	@Override
 	public void select(final ICursorMove move) {
 		firePositionAboutToChange();
-		if (!hasSelection) {
-			selectionStartOffset = offset;
+		final int newOffset = move.calculateNewOffset(graphics, contentTopology, selector.getCaretOffset(), null, null, 0);
+		if (move.isAbsolute()) {
+			selector.setEndAbsoluteTo(newOffset);
+		} else {
+			selector.moveEndTo(newOffset);
 		}
-		offset = move.calculateNewOffset(graphics, contentTopology, offset, null, null, 0);
-		hasSelection = true;
-		selectionEndOffset = offset;
-		firePositionChanged(offset);
+		firePositionChanged(selector.getCaretOffset());
 	}
 
 }
diff --git a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/L2SelectionTest.java b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/L2SelectionTest.java
index 7cb6e51..cb7f1d2 100644
--- a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/L2SelectionTest.java
+++ b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/L2SelectionTest.java
@@ -44,7 +44,7 @@
 
 		editor.selectContentOf(title);
 
-		assertEquals(title.getRange().resizeBy(1, -1), editor.getSelectedRange());
+		assertEquals(title.getRange().resizeBy(1, 0), editor.getSelectedRange());
 		assertTrue(editor.hasSelection());
 	}
 
@@ -65,7 +65,7 @@
 
 		editor.select(title);
 
-		assertEquals(title.getRange(), editor.getSelectedRange());
+		assertEquals(title.getRange().resizeBy(0, 1), editor.getSelectedRange());
 		assertTrue(editor.hasSelection());
 	}
 }
diff --git a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/L2SimpleEditingTest.java b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/L2SimpleEditingTest.java
index ff20a75..008c29c 100644
--- a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/L2SimpleEditingTest.java
+++ b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/L2SimpleEditingTest.java
@@ -35,6 +35,7 @@
 import org.eclipse.vex.core.internal.undo.CannotApplyException;
 import org.eclipse.vex.core.provisional.dom.DocumentValidationException;
 import org.eclipse.vex.core.provisional.dom.IComment;
+import org.eclipse.vex.core.provisional.dom.IDocument;
 import org.eclipse.vex.core.provisional.dom.IDocumentFragment;
 import org.eclipse.vex.core.provisional.dom.IElement;
 import org.eclipse.vex.core.provisional.dom.INode;
@@ -49,15 +50,22 @@
  */
 public class L2SimpleEditingTest {
 
+	private FakeCursor cursor;
 	private IDocumentEditor editor;
 	private IElement rootElement;
 
 	@Before
 	public void setUp() throws Exception {
-		editor = new BaseVexWidget(new MockHostComponent());
-		editor.setDocument(createDocumentWithDTD(TEST_DTD, "section"));
-		// TODO move dependency to whitespace policy into a BaseVexWidget specific set-up method
-		((BaseVexWidget) editor).setWhitespacePolicy(new CssWhitespacePolicy(readTestStyleSheet()));
+		final IDocument document = createDocumentWithDTD(TEST_DTD, "section");
+		cursor = new FakeCursor(document);
+		editor = new DocumentEditor(cursor, new CssWhitespacePolicy(readTestStyleSheet()));
+		editor.setDocument(document);
+		rootElement = editor.getDocument().getRootElement();
+	}
+
+	private void useDocument(final IDocument document) {
+		cursor.setDocument(document);
+		editor.setDocument(document);
 		rootElement = editor.getDocument().getRootElement();
 	}
 
@@ -77,7 +85,7 @@
 	public void shouldProvideInsertionElementAsCurrentElement() throws Exception {
 		final IElement titleElement = editor.insertElement(TITLE);
 		editor.moveBy(-1);
-		assertEquals(titleElement.getStartPosition(), editor.getCaretPosition());
+		assertEquals(titleElement.getStartPosition().getOffset(), editor.getCaretPosition().getOffset());
 		assertSame(rootElement, editor.getCurrentElement());
 	}
 
@@ -874,8 +882,7 @@
 
 	@Test
 	public void givenMultipleElementsOfSameKindSelected_whenStructureAfterJoinWouldBeInvalid_cannotJoin() throws Exception {
-		editor.setDocument(createDocumentWithDTD(TEST_DTD, "one-kind-of-child"));
-		rootElement = editor.getDocument().getRootElement();
+		useDocument(createDocumentWithDTD(TEST_DTD, "one-kind-of-child"));
 
 		final IElement firstSection = editor.insertElement(SECTION);
 		editor.insertElement(TITLE);
@@ -894,8 +901,7 @@
 
 	@Test(expected = CannotApplyException.class)
 	public void givenMultipleElementsOfSameKindSelected_whenStructureAfterJoinWouldBeInvalid_shouldJoin() throws Exception {
-		editor.setDocument(createDocumentWithDTD(TEST_DTD, "one-kind-of-child"));
-		rootElement = editor.getDocument().getRootElement();
+		useDocument(createDocumentWithDTD(TEST_DTD, "one-kind-of-child"));
 
 		final IElement firstSection = editor.insertElement(SECTION);
 		editor.insertElement(TITLE);
@@ -1093,7 +1099,7 @@
 		editor.insertText("Hello World");
 		editor.moveTo(title.getStartPosition().moveBy(1));
 		editor.moveBy(5, true);
-		final int expectedCaretPosition = editor.getSelectedRange().getEndOffset() + 1;
+		final int expectedCaretPosition = editor.getSelectedRange().getEndOffset();
 
 		editor.deleteSelection();
 		editor.undo();
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 84ffc04..dfc23f6 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
@@ -77,7 +77,7 @@
 
 	@Override
 	public void removePosition(final IPosition position) {
-		if (positions.contains(position)) {
+		if (position.isValid() && positions.contains(position)) {
 			/*
 			 * This cast is save: if the position can be removed, this instance must have created it, hence it is a
 			 * GapContentPosition.
@@ -505,4 +505,9 @@
 		}
 	}
 
+	@Override
+	public String toString() {
+		return getRawText();
+	}
+
 }
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/DocumentEditor.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/DocumentEditor.java
index c6c83f2..9850e2e 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/DocumentEditor.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/DocumentEditor.java
@@ -176,25 +176,29 @@
 	 */
 
 	@Override
-	public void doWork(final Runnable runnable) throws DocumentValidationException {
+	public void doWork(final Runnable runnable) throws CannotApplyException {
 		doWork(runnable, false);
 	}
 
 	@Override
-	public void doWork(final Runnable runnable, final boolean savePosition) throws DocumentValidationException {
+	public void doWork(final Runnable runnable, final boolean savePosition) throws CannotApplyException {
 		final IPosition position = document.createPosition(cursor.getOffset());
 		editStack.beginWork();
 		try {
 			runnable.run();
 			final IUndoableEdit work = editStack.commitWork();
 			cursor.move(toOffset(work.getOffsetAfter()));
-		} catch (final DocumentValidationException e) {
+		} catch (final CannotApplyException e) {
 			final IUndoableEdit work = editStack.rollbackWork();
-			cursor.move(toOffset(work.getOffsetBefore()));
+			if (work.getOffsetBefore() > 0) {
+				cursor.move(toOffset(work.getOffsetBefore()));
+			}
 			throw e;
 		} catch (final Throwable t) {
 			final IUndoableEdit work = editStack.rollbackWork();
-			cursor.move(toOffset(work.getOffsetBefore()));
+			if (work.getOffsetBefore() > 0) {
+				cursor.move(toOffset(work.getOffsetBefore()));
+			}
 			// TODO throw exception? at least log error?
 		} finally {
 			if (savePosition) {
@@ -335,8 +339,8 @@
 
 	@Override
 	public void selectAll() {
-		cursor.move(toOffset(document.getStartOffset()));
-		cursor.select(toOffset(document.getEndOffset()));
+		cursor.move(toOffset(document.getStartOffset() + 1));
+		cursor.select(toOffset(document.getEndOffset() - 1));
 	}
 
 	@Override
@@ -351,7 +355,7 @@
 			cursor.move(toOffset(node.getEndOffset()));
 		} else {
 			cursor.move(toOffset(node.getStartOffset() + 1));
-			cursor.select(toOffset(node.getEndOffset() - 1));
+			cursor.select(toOffset(node.getEndOffset()));
 		}
 	}
 
@@ -369,7 +373,7 @@
 		if (!hasSelection()) {
 			return false;
 		}
-		return document.canDelete(getSelectedRange());
+		return document.canDelete(getSelectedRange().resizeBy(0, -1));
 	}
 
 	@Override
@@ -381,7 +385,7 @@
 			return;
 		}
 
-		apply(new DeleteEdit(document, getSelectedRange(), cursor.getOffset()));
+		apply(new DeleteEdit(document, getSelectedRange().resizeBy(0, -1), cursor.getOffset()));
 	}
 
 	/*
@@ -688,7 +692,7 @@
 
 	@Override
 	public void deleteBackward() throws DocumentValidationException {
-		if (readOnly) {
+		if (isReadOnly()) {
 			throw new ReadOnlyException("Cannot delete, because the editor is read-only.");
 		}
 
@@ -706,7 +710,7 @@
 			final ContentRange range = document.getNodeForInsertionAt(offset).getRange();
 			edit = new DeleteEdit(document, range, offset);
 		} else if (document.getNodeForInsertionAt(offset - 1).isEmpty()) {
-			final ContentRange range = document.getNodeForInsertionAt(offset + 1).getRange();
+			final ContentRange range = document.getNodeForInsertionAt(offset - 1).getRange();
 			edit = new DeleteEdit(document, range, offset);
 		} else if (!document.isTagAt(offset - 1)) {
 			edit = new DeletePreviousCharEdit(document, offset);
@@ -1171,7 +1175,12 @@
 		}
 
 		final ContentRange elementRange = currentElement.getRange();
-		final IDocumentFragment elementContent = document.getFragment(elementRange.resizeBy(1, -1));
+		final IDocumentFragment elementContent;
+		if (currentElement.isEmpty()) {
+			elementContent = null;
+		} else {
+			elementContent = document.getFragment(elementRange.resizeBy(1, -1));
+		}
 
 		doWork(new Runnable() {
 			@Override
@@ -1216,7 +1225,12 @@
 		}
 
 		final ContentRange elementRange = currentElement.getRange();
-		final IDocumentFragment elementContent = document.getFragment(elementRange.resizeBy(1, -1));
+		final IDocumentFragment elementContent;
+		if (currentElement.isEmpty()) {
+			elementContent = null;
+		} else {
+			elementContent = document.getFragment(elementRange.resizeBy(1, -1));
+		}
 
 		doWork(new Runnable() {
 			@Override
@@ -1239,8 +1253,9 @@
 			return false;
 		}
 
-		final IElement parent = document.getElementForInsertionAt(cursor.getOffset());
-		final IAxis<? extends INode> selectedNodes = parent.children().in(getSelectedRange());
+		final ContentRange selectedRange = getSelectedRange();
+		final IElement parent = document.getElementForInsertionAt(selectedRange.getStartOffset());
+		final IAxis<? extends INode> selectedNodes = parent.children().in(selectedRange);
 		if (selectedNodes.isEmpty()) {
 			return false;
 		}
@@ -1288,8 +1303,8 @@
 			return;
 		}
 
-		final IElement parent = document.getElementForInsertionAt(cursor.getOffset());
 		final ContentRange selectedRange = getSelectedRange();
+		final IElement parent = document.getElementForInsertionAt(selectedRange.getStartOffset());
 		final IAxis<? extends INode> selectedNodes = parent.children().in(selectedRange);
 		if (selectedNodes.isEmpty()) {
 			return;
@@ -1313,8 +1328,11 @@
 		doWork(new Runnable() {
 			@Override
 			public void run() {
-				final DeleteEdit deletePreservedContent = editStack.apply(new DeleteEdit(document, new ContentRange(firstNode.getEndOffset() + 1, selectedRange.getEndOffset()), cursor.getOffset()));
-				editStack.apply(new DeleteEdit(document, firstNode.getRange().resizeBy(1, -1), deletePreservedContent.getOffsetAfter()));
+				final DeleteEdit deletePreservedContent = editStack
+						.apply(new DeleteEdit(document, new ContentRange(firstNode.getEndOffset() + 1, selectedRange.getEndOffset() - 1), cursor.getOffset()));
+				if (!firstNode.isEmpty()) {
+					editStack.apply(new DeleteEdit(document, firstNode.getRange().resizeBy(1, -1), deletePreservedContent.getOffsetAfter()));
+				}
 				for (final IDocumentFragment contentPart : contentToJoin) {
 					editStack.apply(new InsertFragmentEdit(document, firstNode.getEndOffset(), contentPart));
 				}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/IDocumentEditor.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/IDocumentEditor.java
index 0c21da1..e2a3eaa 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/IDocumentEditor.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/IDocumentEditor.java
@@ -99,7 +99,7 @@
 	 * @param runnable
 	 *            Runnable implementing the work to be done.
 	 */
-	void doWork(Runnable runnable) throws DocumentValidationException;
+	void doWork(Runnable runnable) throws CannotApplyException;
 
 	/**
 	 * Perform the runnable's run method within a transaction. All operations in the runnable are treated as a single
@@ -111,7 +111,7 @@
 	 * @param savePosition
 	 *            If true, the current caret position is saved and restored once the operation is complete.
 	 */
-	void doWork(Runnable runnable, boolean savePosition) throws DocumentValidationException;
+	void doWork(Runnable runnable, boolean savePosition) throws CannotApplyException;
 
 	/**
 	 * Execute a Runnable, restoring the caret position to its original position afterward.