balanced selecting with the mouse must be handled differently

The mouse sets the end offset of the selection absolutely. Balancing
only depends on the mark and the end offset. In contrast using the
cursor keys moves the end offset of the selection relative to the caret
offset. Therefor we must take the direction of movement into account in
order to extend/reduce the balanced range correctly. 

Signed-off-by: Florian Thienel <florian@thienel.org>
diff --git a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/cursor/FakeSelector.java b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/cursor/FakeSelector.java
index 86a26c1..8fc3962 100644
--- a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/cursor/FakeSelector.java
+++ b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/cursor/FakeSelector.java
@@ -26,6 +26,10 @@
 	}
 
 	@Override
+	public void endAt(final int offset) {
+	}
+
+	@Override
 	public boolean isActive() {
 		return false;
 	}
diff --git a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/BalancedSelectorTest.java b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/BalancedSelectorTest.java
index 1d5e29b..2547b8d 100644
--- a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/BalancedSelectorTest.java
+++ b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/widget/BalancedSelectorTest.java
@@ -76,6 +76,17 @@
 		assertBalancedSelectionIs(section.getStartOffset(), section.getEndOffset() + 1, section.getStartOffset());
 	}
 
+	@Test
+	public void givenMarkInText_whenSelectingToEndOffsetOfNextParagraphMultipleTimes_shouldStayAtEndOfNextContainingSection() throws Exception {
+		final IElement section = document.getSection(0);
+		final IElement firstParagraph = document.getParagraphWithText(0);
+		final IElement secondParagraph = document.getEmptyParagraph(0);
+		select(firstParagraph.getStartOffset() + 4, secondParagraph.getEndOffset());
+		assertBalancedSelectionIs(firstParagraph.getStartOffset(), section.getEndOffset(), section.getEndOffset());
+		selector.endAt(secondParagraph.getEndOffset());
+		assertBalancedSelectionIs(firstParagraph.getStartOffset(), section.getEndOffset(), section.getEndOffset());
+	}
+
 	private void select(final int mark, final int caretPosition) {
 		selector.setMark(mark);
 		selector.moveTo(caretPosition);
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/Cursor.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/Cursor.java
index c77e676..43a6980 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/Cursor.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/Cursor.java
@@ -102,7 +102,11 @@
 		for (MoveWithSelection move = moves.poll(); move != null; move = moves.poll()) {
 			offset = move.move.calculateNewOffset(graphics, contentMap, offset, box, getHotArea(), preferredX);
 			if (move.select) {
-				selector.moveTo(offset);
+				if (move.move.isAbsolute()) {
+					selector.endAt(offset);
+				} else {
+					selector.moveTo(offset);
+				}
 				offset = selector.getCaretOffset();
 			} else {
 				selector.setMark(offset);
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/IContentSelector.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/IContentSelector.java
index ff4d337..5614c9e 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/IContentSelector.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/IContentSelector.java
@@ -21,6 +21,8 @@
 
 	void moveTo(int offset);
 
+	void endAt(int offset);
+
 	boolean isActive();
 
 	int getStartOffset();
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/ICursorMove.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/ICursorMove.java
index 6aa538d..0128868 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/ICursorMove.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/ICursorMove.java
@@ -22,4 +22,6 @@
 	int calculateNewOffset(final Graphics graphics, final ContentMap contentMap, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, int preferredX);
 
 	boolean preferX();
+
+	boolean isAbsolute();
 }
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveDown.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveDown.java
index a74fc09..20b3aca 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveDown.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveDown.java
@@ -32,6 +32,11 @@
 	}
 
 	@Override
+	public boolean isAbsolute() {
+		return false;
+	}
+
+	@Override
 	public int calculateNewOffset(final Graphics graphics, final ContentMap contentMap, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, final int preferredX) {
 		if (isAtStartOfEmptyBox(currentOffset, currentBox)) {
 			return currentBox.getEndOffset();
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveLeft.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveLeft.java
index 865b025..2e534be 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveLeft.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveLeft.java
@@ -20,7 +20,7 @@
 public class MoveLeft implements ICursorMove {
 
 	@Override
-	public int calculateNewOffset(final Graphics graphics, final ContentMap contentMap, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, int preferredX) {
+	public int calculateNewOffset(final Graphics graphics, final ContentMap contentMap, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, final int preferredX) {
 		return Math.max(0, currentOffset - 1);
 	}
 
@@ -29,4 +29,9 @@
 		return true;
 	}
 
+	@Override
+	public boolean isAbsolute() {
+		return false;
+	}
+
 }
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveRight.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveRight.java
index 2358195..d90af06 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveRight.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveRight.java
@@ -20,7 +20,7 @@
 public class MoveRight implements ICursorMove {
 
 	@Override
-	public int calculateNewOffset(final Graphics graphics, final ContentMap contentMap, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, int preferredX) {
+	public int calculateNewOffset(final Graphics graphics, final ContentMap contentMap, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, final int preferredX) {
 		return Math.min(currentOffset + 1, contentMap.getLastOffset());
 	}
 
@@ -29,4 +29,8 @@
 		return true;
 	}
 
+	@Override
+	public boolean isAbsolute() {
+		return false;
+	}
 }
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToAbsoluteCoordinates.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToAbsoluteCoordinates.java
index f8a4183..68484dd 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToAbsoluteCoordinates.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToAbsoluteCoordinates.java
@@ -38,6 +38,11 @@
 	}
 
 	@Override
+	public boolean isAbsolute() {
+		return true;
+	}
+
+	@Override
 	public int calculateNewOffset(final Graphics graphics, final ContentMap contentMap, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, final int preferredX) {
 		final IContentBox box = findClosestBoxOnLineByCoordinates(contentMap, x, y);
 		if (box.containsCoordinates(x, y)) {
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToOffset.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToOffset.java
index 00fb757..61fd9a3 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToOffset.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToOffset.java
@@ -26,7 +26,7 @@
 	}
 
 	@Override
-	public int calculateNewOffset(final Graphics graphics, final ContentMap contentMap, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, int preferredX) {
+	public int calculateNewOffset(final Graphics graphics, final ContentMap contentMap, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, final int preferredX) {
 		return offset;
 	}
 
@@ -35,4 +35,8 @@
 		return true;
 	}
 
+	@Override
+	public boolean isAbsolute() {
+		return true;
+	}
 }
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveUp.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveUp.java
index 5fcce9a..081c4bc 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveUp.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveUp.java
@@ -32,6 +32,11 @@
 	}
 
 	@Override
+	public boolean isAbsolute() {
+		return false;
+	}
+
+	@Override
 	public int calculateNewOffset(final Graphics graphics, final ContentMap contentMap, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, final int preferredX) {
 		if (isAtEndOfEmptyBox(currentOffset, currentBox)) {
 			return currentBox.getStartOffset();
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/BalancedSelector.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/BalancedSelector.java
index 4778db4..81e70bf 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/BalancedSelector.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/BalancedSelector.java
@@ -28,20 +28,27 @@
 		setMark(0);
 	}
 
+	@Override
 	public void moveTo(final int offset) {
 		if (document == null) {
 			return;
 		}
+		if (offset == getMark()) {
+			setMark(offset);
+		}
 
 		final boolean movingForward = offset > getCaretOffset();
 		final boolean movingBackward = offset < getCaretOffset();
-		final boolean movingTowardMark = movingForward && getMark() >= offset || movingBackward && getMark() <= offset;
-		final boolean movingAwayFromMark = !movingTowardMark;
+		final boolean beforeMark = offset < getMark();
+		final boolean afterMark = offset > getMark();
+		final boolean movingTowardMark = movingForward && beforeMark || movingBackward && afterMark;
+		final boolean movingAwayFromMark = movingForward && afterMark || movingBackward && beforeMark;
 
 		// expand or shrink the selection to make sure the selection is balanced
 		final int balancedStart = Math.min(getMark(), offset);
 		final int balancedEnd = Math.max(getMark(), offset);
 		final INode balancedNode = document.findCommonNode(balancedStart, balancedEnd);
+
 		if (movingForward && movingTowardMark) {
 			setStartOffset(balanceForward(balancedStart, balancedNode));
 			setEndOffset(balanceForward(balancedEnd, balancedNode));
@@ -61,6 +68,34 @@
 		}
 	}
 
+	@Override
+	public void endAt(final int offset) {
+		if (document == null) {
+			return;
+		}
+		if (offset == getMark()) {
+			setMark(offset);
+		}
+
+		final boolean beforeMark = offset < getMark();
+		final boolean afterMark = offset > getMark();
+
+		// expand or shrink the selection to make sure the selection is balanced
+		final int balancedStart = Math.min(getMark(), offset);
+		final int balancedEnd = Math.max(getMark(), offset);
+		final INode balancedNode = document.findCommonNode(balancedStart, balancedEnd);
+
+		if (beforeMark) {
+			setStartOffset(balanceBackward(balancedStart, balancedNode));
+			setEndOffset(balanceForward(balancedEnd, balancedNode));
+			setCaretOffset(getStartOffset());
+		} else if (afterMark) {
+			setStartOffset(balanceBackward(balancedStart, balancedNode));
+			setEndOffset(balanceForward(balancedEnd, balancedNode));
+			setCaretOffset(getEndOffset());
+		}
+	}
+
 	private int balanceForward(final int offset, final INode node) {
 		if (getParentForInsertionAt(offset) == node) {
 			return offset;
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/SimpleSelector.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/SimpleSelector.java
index ba914a9..aa03ae4 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/SimpleSelector.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/SimpleSelector.java
@@ -17,6 +17,11 @@
 
 	@Override
 	public void moveTo(final int offset) {
+		endAt(offset);
+	}
+
+	@Override
+	public void endAt(final int offset) {
 		final boolean movingForward = offset > getCaretOffset();
 		final boolean movingBackward = offset < getCaretOffset();
 		final boolean movingTowardMark = movingForward && getMark() >= offset || movingBackward && getMark() <= offset;