delete single characters

Signed-off-by: Florian Thienel <florian@thienel.org>
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 cd52547..08569da 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
@@ -13,12 +13,15 @@
 import static org.eclipse.vex.core.internal.widget.VexWidgetTest.createDocumentWithDTD;

 import static org.eclipse.vex.core.tests.TestResources.TEST_DTD;

 import static org.junit.Assert.assertEquals;

+import static org.junit.Assert.assertFalse;

+import static org.junit.Assert.assertNull;

 import static org.junit.Assert.assertSame;

 

 import org.eclipse.core.runtime.QualifiedName;

 import org.eclipse.vex.core.internal.css.StyleSheet;

 import org.eclipse.vex.core.internal.dom.Element;

 import org.junit.Before;

+import org.junit.Ignore;

 import org.junit.Test;

 

 /**

@@ -26,6 +29,9 @@
  */

 public class L2SimpleEditingTest {

 

+	private static final QualifiedName TITLE = new QualifiedName(null, "title");

+	private static final QualifiedName PARA = new QualifiedName(null, "para");

+

 	private VexWidgetImpl widget;

 	private Element rootElement;

 

@@ -44,13 +50,13 @@
 

 	@Test

 	public void shouldMoveCaretIntoInsertedElement() throws Exception {

-		final Element titleElement = widget.insertElement(new QualifiedName(null, "title"));

+		final Element titleElement = widget.insertElement(TITLE);

 		assertEquals(titleElement.getEndOffset(), widget.getCaretOffset());

 	}

 

 	@Test

 	public void shouldProvideInsertionElementAsCurrentElement() throws Exception {

-		final Element titleElement = widget.insertElement(new QualifiedName(null, "title"));

+		final Element titleElement = widget.insertElement(TITLE);

 		widget.moveBy(-1);

 		assertEquals(titleElement.getStartOffset(), widget.getCaretOffset());

 		assertSame(rootElement, widget.getCurrentElement());

@@ -58,7 +64,7 @@
 

 	@Test

 	public void givenAnElementWithText_whenHittingBackspace_shouldDeleteLastCharacter() throws Exception {

-		final Element titleElement = widget.insertElement(new QualifiedName(null, "title"));

+		final Element titleElement = widget.insertElement(TITLE);

 		widget.insertText("Hello");

 		widget.deletePreviousChar();

 		assertEquals("Hell", titleElement.getText());

@@ -67,11 +73,36 @@
 

 	@Test

 	public void givenAnElementWithText_whenHittingPos1AndDelete_shouldDeleteFirstCharacter() throws Exception {

-		final Element titleElement = widget.insertElement(new QualifiedName(null, "title"));

+		final Element titleElement = widget.insertElement(TITLE);

 		widget.insertText("Hello");

 		widget.moveBy(-5);

 		widget.deleteNextChar();

 		assertEquals("ello", titleElement.getText());

 		assertEquals(titleElement.getStartOffset() + 1, widget.getCaretOffset());

 	}

+

+	@Test

+	@Ignore("get moveTo working first")

+	public void givenAnEmptyElement_whenCaretBetweenStartAndEndTagAndHittingBackspace_shouldDeleteEmptyElement() throws Exception {

+		widget.insertElement(TITLE);

+		widget.moveBy(1);

+		final Element paraElement = widget.insertElement(PARA);

+		widget.deletePreviousChar();

+		assertEquals(1, rootElement.getChildCount());

+		assertNull(paraElement.getParent());

+		assertFalse(paraElement.isAssociated());

+	}

+

+	@Test

+	@Ignore("get moveTo working first")

+	public void givenAnEmptyElement_whenCaretBeforeStartOffsetAndHittingDelete_shouldDeleteEmptyElement() throws Exception {

+		widget.insertElement(TITLE);

+		widget.moveBy(1);

+		final Element paraElement = widget.insertElement(PARA);

+		widget.moveBy(-1);

+		widget.deleteNextChar();

+		assertEquals(1, rootElement.getChildCount());

+		assertNull(paraElement.getParent());

+		assertFalse(paraElement.isAssociated());

+	}

 }

diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/Document.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/Document.java
index fec52ff..97d7abd 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/Document.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/Document.java
@@ -328,13 +328,20 @@
 	}
 
 	public void delete(final Range range) throws DocumentValidationException {
-		final Parent surroundingParent = getParentAt(range.getStartOffset() - 1);
-		final Parent parentAtEndOffset = getParentAt(range.getEndOffset() + 1);
+		final Parent surroundingParent = getParentAt(range.getStartOffset());
+		final Parent parentAtEndOffset = getParentAt(range.getEndOffset());
 		if (surroundingParent != parentAtEndOffset) {
 			throw new IllegalArgumentException("Deletion in " + range + " is unbalanced");
 		}
 
-		final boolean deletionIsValid = surroundingParent.accept(new BaseNodeVisitorWithResult<Boolean>(true) {
+		final Parent parentForDeletion;
+		if (range.equals(surroundingParent.getRange())) {
+			parentForDeletion = surroundingParent.getParent();
+		} else {
+			parentForDeletion = surroundingParent;
+		}
+
+		final boolean deletionIsValid = parentForDeletion.accept(new BaseNodeVisitorWithResult<Boolean>(true) {
 			@Override
 			public Boolean visit(final Element element) {
 				final Validator validator = getValidator();
@@ -350,19 +357,20 @@
 			throw new DocumentValidationException("Unable to delete " + range);
 		}
 
-		fireBeforeContentDeleted(new DocumentEvent(this, surroundingParent, range.getStartOffset(), range.length(), null));
+		fireBeforeContentDeleted(new DocumentEvent(this, parentForDeletion, range.getStartOffset(), range.length(), null));
 
-		final Iterator<Node> iter = surroundingParent.getChildIterator();
+		final Iterator<Node> iter = parentForDeletion.getChildIterator();
 		while (iter.hasNext()) {
 			final Node child = iter.next();
 			if (child.isInRange(range)) {
-				surroundingParent.removeChild(child);
+				parentForDeletion.removeChild(child);
+				child.dissociate();
 			}
 		}
 
 		getContent().remove(range);
 
-		fireContentDeleted(new DocumentEvent(this, surroundingParent, range.getStartOffset(), range.length(), null));
+		fireContentDeleted(new DocumentEvent(this, parentForDeletion, range.getStartOffset(), range.length(), null));
 	}
 
 	/*
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/AbstractBlockBox.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/AbstractBlockBox.java
index 357acf9..da68ad6 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/AbstractBlockBox.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/AbstractBlockBox.java
@@ -376,7 +376,7 @@
 
 	@Override
 	public boolean hasContent() {
-		return true;
+		return getNode().isAssociated();
 	}
 
 	public void invalidate(final boolean direct) {
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/BlockElementBox.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/BlockElementBox.java
index b92f70c..57a3a0d 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/BlockElementBox.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/BlockElementBox.java
@@ -76,11 +76,6 @@
 	}
 
 	@Override
-	public boolean hasContent() {
-		return true;
-	}
-
-	@Override
 	public void paint(final LayoutContext context, final int x, final int y) {
 
 		super.paint(context, x, y);
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/CompositeInlineBox.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/CompositeInlineBox.java
index 0862e3c..63cb2c1 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/CompositeInlineBox.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/CompositeInlineBox.java
@@ -33,6 +33,10 @@
 	 */
 	@Override
 	public boolean hasContent() {
+		if (!getNode().isAssociated()) {
+			return false;
+		}
+
 		final Box[] children = getChildren();
 		for (final Box element : children) {
 			if (element.hasContent()) {
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/DocumentTextBox.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/DocumentTextBox.java
index 873a944..5d69168 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/DocumentTextBox.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/DocumentTextBox.java
@@ -104,7 +104,7 @@
 	 */
 	@Override
 	public boolean hasContent() {
-		return true;
+		return getNode().isAssociated();
 	}
 
 	/**
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/LineBox.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/LineBox.java
index d0befae..c361922 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/LineBox.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/LineBox.java
@@ -98,7 +98,7 @@
 	 */
 	@Override
 	public boolean hasContent() {
-		return firstContentChild != null;
+		return firstContentChild != null && firstContentChild.hasContent();
 	}
 
 	/**
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/ParagraphBox.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/ParagraphBox.java
index 7f13e7c..dad0308 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/ParagraphBox.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/ParagraphBox.java
@@ -273,7 +273,7 @@
 
 	@Override
 	public boolean hasContent() {
-		return firstContentLine != null;
+		return firstContentLine != null && firstContentLine.hasContent();
 	}
 
 	public IntRange layout(final LayoutContext context, final int top, final int bottom) {
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/VexWidgetImpl.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/VexWidgetImpl.java
index 2b3d768..82c7be3 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/VexWidgetImpl.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/VexWidgetImpl.java
@@ -321,18 +321,15 @@
 			} else if (element.isEmpty()) {
 				// deleting the right sentinel of an empty element
 				// so just delete the whole element an move on
-				this.moveTo(offset - 1, false);
-				this.moveTo(offset + 1, true);
+				this.moveTo(offset - 1, true);
 				deleteSelection();
 			} else if (doc.getElementForInsertionAt(offset + 1).isEmpty()) {
 				// deleting the left sentinel of an empty element
 				// so just delete the whole element an move on
-				this.moveTo(offset + 2, true);
-				deleteSelection();
-			} else if (!doc.isElementAt(offset)) {
-				this.moveTo(offset, false);
 				this.moveTo(offset + 1, true);
 				deleteSelection();
+			} else if (!doc.isElementAt(offset)) {
+				deleteOffset();
 			}
 		}
 	}
@@ -354,29 +351,37 @@
 			} else if (element.isEmpty()) {
 				// deleting the left sentinel of an empty element
 				// so just delete the whole element an move on
-				this.moveTo(offset - 1, false);
-				this.moveTo(offset + 1, true);
+				this.moveTo(offset - 1, true);
 				deleteSelection();
 			} else if (doc.getElementForInsertionAt(offset - 1).isEmpty()) {
 				// deleting the right sentinel of an empty element
 				// so just delete the whole element an move on
+				this.moveTo(offset - 1, false);
 				this.moveTo(offset - 2, true);
 				deleteSelection();
 			} else {
 				offset--;
 				if (!doc.isElementAt(offset)) {
-					this.moveTo(offset, false);
-					this.moveTo(offset + 1, true);
-					deleteSelection();
+					moveBy(-1);
+					deleteOffset();
 				}
 			}
 		}
 	}
 
+	private void deleteOffset() {
+		try {
+			applyEdit(new DeleteEdit(document, new Range(getCaretOffset(), getCaretOffset())), getCaretOffset());
+			this.moveTo(getSelectionStart());
+		} catch (final DocumentValidationException e) {
+			e.printStackTrace(); // This should never happen, because we constrain the selection
+		}
+	}
+
 	public void deleteSelection() {
 		try {
 			if (hasSelection()) {
-				applyEdit(new DeleteEdit(document, new Range(getSelectionStart(), getSelectionEnd() - 1)), getSelectionStart());
+				applyEdit(new DeleteEdit(document, new Range(getSelectionStart(), getSelectionEnd())), getSelectionStart());
 				this.moveTo(getSelectionStart());
 			}
 		} catch (final DocumentValidationException e) {