diff -r c09341366d82 src/Editor.cxx
--- a/src/Editor.cxx	Sat Jun 20 09:44:35 2015 +1000
+++ b/src/Editor.cxx	Mon Jun 22 12:01:25 2015 +1000
@@ -803,9 +803,37 @@
 	return pos;
 }
 
+void Editor::MovedCaret(SelectionPosition newPos, SelectionPosition previousPos, bool ensureVisible) {
+	const int currentLine = pdoc->LineFromPosition(newPos.Position());
+	if (ensureVisible) {
+		// In case in need of wrapping to ensure DisplayFromDoc works.
+		if (currentLine >= wrapPending.start)
+			WrapLines(wsAll);
+		XYScrollPosition newXY = XYScrollToMakeVisible(
+			SelectionRange(posDrag.IsValid() ? posDrag : newPos), xysDefault);
+		if (previousPos.IsValid() && (newXY.xOffset == xOffset)) {
+			// simple vertical scroll then invalidate
+			ScrollTo(newXY.topLine);
+			InvalidateSelection(SelectionRange(previousPos), true);
+		} else {
+			SetXYScroll(newXY);
+		}
+	}
+
+	ShowCaretAtCurrentPosition();
+
+	ClaimSelection();
+	SetHoverIndicatorPosition(sel.MainCaret());
+	QueueIdleWork(WorkNeeded::workUpdateUI);
+
+	if (marginView.highlightDelimiter.NeedsDrawing(currentLine)) {
+		RedrawSelMargin();
+	}
+}
+
 void Editor::MovePositionTo(SelectionPosition newPos, Selection::selTypes selt, bool ensureVisible) {
-	const bool simpleCaret = (sel.Count() == 1) && sel.Empty();
-	const SelectionPosition spCaret = sel.Last();
+	const SelectionPosition spCaret = ((sel.Count() == 1) && sel.Empty()) ?
+		sel.Last() : SelectionPosition(INVALID_POSITION);
 
 	int delta = newPos.Position() - sel.MainCaret();
 	newPos = ClampPositionIntoDocument(newPos);
@@ -830,27 +858,8 @@
 	} else {
 		SetEmptySelection(newPos);
 	}
-	ShowCaretAtCurrentPosition();
-
-	const int currentLine = pdoc->LineFromPosition(newPos.Position());
-	if (ensureVisible) {
-		// In case in need of wrapping to ensure DisplayFromDoc works.
-		if (currentLine >= wrapPending.start)
-			WrapLines(wsAll);
-		XYScrollPosition newXY = XYScrollToMakeVisible(
-			SelectionRange(posDrag.IsValid() ? posDrag : sel.RangeMain().caret), xysDefault);
-		if (simpleCaret && (newXY.xOffset == xOffset)) {
-			// simple vertical scroll then invalidate
-			ScrollTo(newXY.topLine);
-			InvalidateSelection(SelectionRange(spCaret), true);
-		} else {
-			SetXYScroll(newXY);
-		}
-	}
-
-	if (marginView.highlightDelimiter.NeedsDrawing(currentLine)) {
-		RedrawSelMargin();
-	}
+
+	MovedCaret(newPos, spCaret, ensureVisible);
 }
 
 void Editor::MovePositionTo(int newPos, Selection::selTypes selt, bool ensureVisible) {
@@ -2952,30 +2961,36 @@
 }
 
 void Editor::NewLine() {
-	// Remove non-main ranges
 	InvalidateWholeSelection();
-	sel.DropAdditionalRanges();
-	sel.RangeMain().ClearVirtualSpace();
-
-	// Clear main range and insert line end
-	bool needGroupUndo = !sel.Empty();
-	if (needGroupUndo)
-		pdoc->BeginUndoAction();
-
-	if (!sel.Empty())
+	if (sel.IsRectangular() || !additionalSelectionTyping) {
+		// Remove non-main ranges
+		sel.DropAdditionalRanges();
+	}
+
+	UndoGroup ug(pdoc, !sel.Empty() || (sel.Count() > 1));
+
+	// Clear each range
+	if (!sel.Empty()) {
 		ClearSelection();
-	const char *eol = "\n";
-	if (pdoc->eolMode == SC_EOL_CRLF) {
-		eol = "\r\n";
-	} else if (pdoc->eolMode == SC_EOL_CR) {
-		eol = "\r";
-	} // else SC_EOL_LF -> "\n" already set
-	const int insertLength = pdoc->InsertString(sel.MainCaret(), eol, istrlen(eol));
-	// Want to end undo group before NotifyChar as applications often modify text here
-	if (needGroupUndo)
-		pdoc->EndUndoAction();
-	if (insertLength > 0) {
-		SetEmptySelection(sel.MainCaret() + insertLength);
+	}
+
+	// Insert each line end
+	size_t countInsertions = 0;
+	for (size_t r = 0; r < sel.Count(); r++) {
+		sel.Range(r).ClearVirtualSpace();
+		const char *eol = StringFromEOLMode(pdoc->eolMode);
+		const int positionInsert = sel.Range(r).caret.Position();
+		const int insertLength = pdoc->InsertString(positionInsert, eol, istrlen(eol));
+		if (insertLength > 0) {
+			sel.Range(r) = SelectionRange(positionInsert + insertLength);
+			countInsertions++;
+		}
+	}
+
+	// Perform notifications after all the changes as the application may change the
+	// selections in response to the characters.
+	for (size_t i = 0; i < countInsertions; i++) {
+		const char *eol = StringFromEOLMode(pdoc->eolMode);
 		while (*eol) {
 			NotifyChar(*eol);
 			if (recordingMacro) {
@@ -2987,6 +3002,7 @@
 			eol++;
 		}
 	}
+
 	SetLastXChosen();
 	SetScrollBars();
 	EnsureCaretVisible();
@@ -2994,26 +3010,17 @@
 	ShowCaretAtCurrentPosition();
 }
 
-void Editor::CursorUpOrDown(int direction, Selection::selTypes selt) {
-	SelectionPosition caretToUse = sel.Range(sel.Main()).caret;
-	if (sel.IsRectangular()) {
-		if (selt ==  Selection::noSel) {
-			caretToUse = (direction > 0) ? sel.Limits().end : sel.Limits().start;
-		} else {
-			caretToUse = sel.Rectangular().caret;
-		}
-	}
-
-	Point pt = LocationFromPosition(caretToUse);
+SelectionPosition Editor::PositionUpOrDown(SelectionPosition spStart, int direction, int lastX) {
+	const Point pt = LocationFromPosition(spStart);
 	int skipLines = 0;
 
 	if (vs.annotationVisible) {
-		int lineDoc = pdoc->LineFromPosition(caretToUse.Position());
-		Point ptStartLine = LocationFromPosition(pdoc->LineStart(lineDoc));
-		int subLine = static_cast<int>(pt.y - ptStartLine.y) / vs.lineHeight;
+		const int lineDoc = pdoc->LineFromPosition(spStart.Position());
+		const Point ptStartLine = LocationFromPosition(pdoc->LineStart(lineDoc));
+		const int subLine = static_cast<int>(pt.y - ptStartLine.y) / vs.lineHeight;
 
 		if (direction < 0 && subLine == 0) {
-			int lineDisplay = cs.DisplayFromDoc(lineDoc);
+			const int lineDisplay = cs.DisplayFromDoc(lineDoc);
 			if (lineDisplay > 0) {
 				skipLines = pdoc->AnnotationLines(cs.DocFromDisplay(lineDisplay - 1));
 			}
@@ -3022,9 +3029,12 @@
 		}
 	}
 
-	int newY = static_cast<int>(pt.y) + (1 + skipLines) * direction * vs.lineHeight;
+	const int newY = static_cast<int>(pt.y) + (1 + skipLines) * direction * vs.lineHeight;
+	if (lastX < 0) {
+		lastX = static_cast<int>(pt.x) + xOffset;
+	}
 	SelectionPosition posNew = SPositionFromLocation(
-	            Point::FromInts(lastXChosen - xOffset, newY), false, false, UserVirtualSpace());
+		Point::FromInts(lastX - xOffset, newY), false, false, UserVirtualSpace());
 
 	if (direction < 0) {
 		// Line wrapping may lead to a location on the same line, so
@@ -3039,14 +3049,53 @@
 		// There is an equivalent case when moving down which skips
 		// over a line.
 		Point ptNew = LocationFromPosition(posNew.Position());
-		while ((posNew.Position() > caretToUse.Position()) && (ptNew.y > newY)) {
+		while ((posNew.Position() > spStart.Position()) && (ptNew.y > newY)) {
 			posNew.Add(-1);
 			posNew.SetVirtualSpace(0);
 			ptNew = LocationFromPosition(posNew.Position());
 		}
 	}
-
-	MovePositionTo(MovePositionSoVisible(posNew, direction), selt);
+	return posNew;
+}
+
+void Editor::CursorUpOrDown(int direction, Selection::selTypes selt) {
+	SelectionPosition caretToUse = sel.Range(sel.Main()).caret;
+	if (sel.IsRectangular()) {
+		if (selt ==  Selection::noSel) {
+			caretToUse = (direction > 0) ? sel.Limits().end : sel.Limits().start;
+		} else {
+			caretToUse = sel.Rectangular().caret;
+		}
+	}
+	if (selt == Selection::selRectangle) {
+		const SelectionRange rangeBase = sel.IsRectangular() ? sel.Rectangular() : sel.RangeMain();
+		if (!sel.IsRectangular()) {
+			InvalidateWholeSelection();
+			sel.DropAdditionalRanges();
+		}
+		const SelectionPosition posNew = MovePositionSoVisible(
+			PositionUpOrDown(caretToUse, direction, lastXChosen), direction);
+		sel.selType = Selection::selRectangle;
+		sel.Rectangular() = SelectionRange(posNew, rangeBase.anchor);
+		SetRectangularRange();
+		MovedCaret(posNew, caretToUse, true);
+	} else {
+		if (!additionalSelectionTyping || (sel.IsRectangular())) {
+			InvalidateWholeSelection();
+			sel.DropAdditionalRanges();
+		}
+		sel.selType = Selection::selStream;
+		for (size_t r = 0; r < sel.Count(); r++) {
+			const int lastX = (r == sel.Main()) ? lastXChosen : -1;
+			const SelectionPosition spCaretNow = sel.Range(r).caret;
+			const SelectionPosition posNew = MovePositionSoVisible(
+				PositionUpOrDown(spCaretNow, direction, lastX), direction);
+			sel.Range(r) = selt == Selection::selStream ? 
+				SelectionRange(posNew, sel.Range(r).anchor) : SelectionRange(posNew);
+		}
+		sel.RemoveDuplicates();
+		MovedCaret(sel.RangeMain().caret, caretToUse, true);
+	}
 }
 
 void Editor::ParaUpOrDown(int direction, Selection::selTypes selt) {
@@ -3076,6 +3125,402 @@
 	}
 }
 
+namespace {
+
+unsigned int WithExtends(unsigned int iMessage) {
+	switch (iMessage) {
+	case SCI_CHARLEFT: return SCI_CHARLEFTEXTEND;
+	case SCI_CHARRIGHT: return SCI_CHARRIGHTEXTEND;
+
+	case SCI_WORDLEFT: return SCI_WORDLEFTEXTEND;
+	case SCI_WORDRIGHT: return SCI_WORDRIGHTEXTEND;
+	case SCI_WORDLEFTEND: return SCI_WORDLEFTENDEXTEND;
+	case SCI_WORDRIGHTEND: return SCI_WORDRIGHTENDEXTEND;
+	case SCI_WORDPARTLEFT: return SCI_WORDPARTLEFTEXTEND;
+	case SCI_WORDPARTRIGHT: return SCI_WORDPARTRIGHTEXTEND;
+
+	case SCI_HOME: return SCI_HOMEEXTEND;
+	case SCI_HOMEDISPLAY: return SCI_HOMEDISPLAYEXTEND;
+	case SCI_HOMEWRAP: return SCI_HOMEWRAPEXTEND;
+	case SCI_VCHOME: return SCI_VCHOMEEXTEND;
+	case SCI_VCHOMEDISPLAY: return SCI_VCHOMEDISPLAYEXTEND;
+	case SCI_VCHOMEWRAP: return SCI_VCHOMEWRAPEXTEND;
+
+	case SCI_LINEEND: return SCI_LINEENDEXTEND;
+	case SCI_LINEENDDISPLAY: return SCI_LINEENDDISPLAYEXTEND;
+	case SCI_LINEENDWRAP: return SCI_LINEENDWRAPEXTEND;
+
+	default:	return iMessage;
+	}
+}
+
+int NaturalDirection(unsigned int iMessage) {
+	switch (iMessage) {
+	case SCI_CHARLEFT:
+	case SCI_CHARLEFTEXTEND:
+	case SCI_CHARLEFTRECTEXTEND:
+	case SCI_WORDLEFT:
+	case SCI_WORDLEFTEXTEND:
+	case SCI_WORDLEFTEND:
+	case SCI_WORDLEFTENDEXTEND:
+	case SCI_WORDPARTLEFT:
+	case SCI_WORDPARTLEFTEXTEND:
+	case SCI_HOME:
+	case SCI_HOMEEXTEND:
+	case SCI_HOMEDISPLAY:
+	case SCI_HOMEDISPLAYEXTEND:
+	case SCI_HOMEWRAP:
+	case SCI_HOMEWRAPEXTEND:
+		// VC_HOME* mostly goes back
+	case SCI_VCHOME:
+	case SCI_VCHOMEEXTEND:
+	case SCI_VCHOMEDISPLAY:
+	case SCI_VCHOMEDISPLAYEXTEND:
+	case SCI_VCHOMEWRAP:
+	case SCI_VCHOMEWRAPEXTEND:
+		return -1;
+
+	default:
+		return 1;
+	}
+}
+
+bool IsRectExtend(unsigned int iMessage) {
+	switch (iMessage) {
+	case SCI_CHARLEFTRECTEXTEND:
+	case SCI_CHARRIGHTRECTEXTEND:
+	case SCI_HOMERECTEXTEND:
+	case SCI_VCHOMERECTEXTEND:
+	case SCI_LINEENDRECTEXTEND:
+		return true;
+	default:
+		return false;
+	}
+}
+
+}
+
+int Editor::VCHomeDisplayPosition(int position) {
+	const int homePos = pdoc->VCHomePosition(position);
+	const int viewLineStart = StartEndDisplayLine(position, true);
+	if (viewLineStart > homePos)
+		return viewLineStart;
+	else
+		return homePos;
+}
+
+int Editor::VCHomeWrapPosition(int position) {
+	const int homePos = pdoc->VCHomePosition(position);
+	const int viewLineStart = StartEndDisplayLine(position, true);
+	if ((viewLineStart < position) && (viewLineStart > homePos))
+		return viewLineStart;
+	else
+		return homePos;
+}
+
+int Editor::LineEndWrapPosition(int position) {
+	const int endPos = StartEndDisplayLine(position, false);
+	const int realEndPos = pdoc->LineEndPosition(position);
+	if (endPos > realEndPos      // if moved past visible EOLs
+		|| position >= endPos) // if at end of display line already
+		return realEndPos;
+	else
+		return endPos;
+}
+
+int Editor::HorizontalMove(unsigned int iMessage) {
+	if (sel.MoveExtends()) {
+		iMessage = WithExtends(iMessage);
+	}
+
+	if (!multipleSelection && !sel.IsRectangular()) {
+		// Simplify selection down to 1
+		sel.SetSelection(sel.RangeMain());
+	}
+
+	// Invalidate each of the current selections
+	InvalidateWholeSelection();
+
+	if (IsRectExtend(iMessage)) {
+		const SelectionRange rangeBase = sel.IsRectangular() ? sel.Rectangular() : sel.RangeMain();
+		if (!sel.IsRectangular()) {
+			sel.DropAdditionalRanges();
+		}
+		// Will change to rectangular if not currently rectangular
+		SelectionPosition spCaret = rangeBase.caret;
+		switch (iMessage) {
+		case SCI_CHARLEFTRECTEXTEND:
+			if (pdoc->IsLineEndPosition(spCaret.Position()) && spCaret.VirtualSpace()) {
+				spCaret.SetVirtualSpace(spCaret.VirtualSpace() - 1);
+			} else {
+				spCaret = SelectionPosition(spCaret.Position() - 1);
+			}
+			break;
+		case SCI_CHARRIGHTRECTEXTEND:
+			if ((virtualSpaceOptions & SCVS_RECTANGULARSELECTION) && pdoc->IsLineEndPosition(sel.MainCaret())) {
+				spCaret.SetVirtualSpace(spCaret.VirtualSpace() + 1);
+			} else {
+				spCaret = SelectionPosition(spCaret.Position() + 1);
+			}
+			break;
+		case SCI_HOMERECTEXTEND:
+			spCaret = SelectionPosition(pdoc->LineStart(pdoc->LineFromPosition(spCaret.Position())));
+			break;
+		case SCI_VCHOMERECTEXTEND:
+			spCaret = SelectionPosition(pdoc->VCHomePosition(spCaret.Position()));
+			break;
+		case SCI_LINEENDRECTEXTEND:
+			spCaret = SelectionPosition(pdoc->LineEndPosition(spCaret.Position()));
+			break;
+		}
+		const int directionMove = (spCaret < rangeBase.caret) ? -1 : 1;
+		spCaret = MovePositionSoVisible(spCaret, directionMove);
+		sel.selType = Selection::selRectangle;
+		sel.Rectangular() = SelectionRange(spCaret, rangeBase.anchor);
+		SetRectangularRange();
+	} else {
+		if (sel.IsRectangular()) {
+			// Not a rectangular extension so switch to stream.
+			SelectionPosition selAtLimit = (NaturalDirection(iMessage) > 0) ? sel.Limits().end : sel.Limits().start;
+			sel.selType = Selection::selStream;
+			sel.SetSelection(SelectionRange(selAtLimit));
+		}
+		if (!additionalSelectionTyping) {
+			InvalidateWholeSelection();
+			sel.DropAdditionalRanges();
+		}
+		for (size_t r = 0; r < sel.Count(); r++) {
+			const SelectionPosition spCaretNow = sel.Range(r).caret;
+			SelectionPosition spCaret = spCaretNow;
+			switch (iMessage) {
+			case SCI_CHARLEFT:
+			case SCI_CHARLEFTEXTEND:
+				if (spCaret.VirtualSpace()) {
+					spCaret.SetVirtualSpace(spCaret.VirtualSpace() - 1);
+				} else {
+					spCaret = SelectionPosition(spCaret.Position() - 1);
+				}
+				break;
+			case SCI_CHARRIGHT:
+			case SCI_CHARRIGHTEXTEND:
+				if ((virtualSpaceOptions & SCVS_USERACCESSIBLE) && pdoc->IsLineEndPosition(spCaret.Position())) {
+					spCaret.SetVirtualSpace(spCaret.VirtualSpace() + 1);
+				} else {
+					spCaret = SelectionPosition(spCaret.Position() + 1);
+				}
+				break;
+			case SCI_WORDLEFT:
+			case SCI_WORDLEFTEXTEND:
+				spCaret = SelectionPosition(pdoc->NextWordStart(spCaret.Position(), -1));
+				break;
+			case SCI_WORDRIGHT:
+			case SCI_WORDRIGHTEXTEND:
+				spCaret = SelectionPosition(pdoc->NextWordStart(spCaret.Position(), 1));
+				break;
+			case SCI_WORDLEFTEND:
+			case SCI_WORDLEFTENDEXTEND:
+				spCaret = SelectionPosition(pdoc->NextWordEnd(spCaret.Position(), -1));
+				break;
+			case SCI_WORDRIGHTEND:
+			case SCI_WORDRIGHTENDEXTEND:
+				spCaret = SelectionPosition(pdoc->NextWordEnd(spCaret.Position(), 1));
+				break;
+			case SCI_WORDPARTLEFT:
+			case SCI_WORDPARTLEFTEXTEND:
+				spCaret = SelectionPosition(pdoc->WordPartLeft(spCaret.Position()));
+				break;
+			case SCI_WORDPARTRIGHT:
+			case SCI_WORDPARTRIGHTEXTEND:
+				spCaret = SelectionPosition(pdoc->WordPartRight(spCaret.Position()));
+				break;
+			case SCI_HOME:
+			case SCI_HOMEEXTEND:
+				spCaret = SelectionPosition(pdoc->LineStart(pdoc->LineFromPosition(spCaret.Position())));
+				break;
+			case SCI_HOMEDISPLAY:
+			case SCI_HOMEDISPLAYEXTEND:
+				spCaret = SelectionPosition(StartEndDisplayLine(spCaret.Position(), true));
+				break;
+			case SCI_HOMEWRAP:
+			case SCI_HOMEWRAPEXTEND:
+				spCaret = MovePositionSoVisible(StartEndDisplayLine(spCaret.Position(), true), -1);
+				if (spCaretNow <= spCaret)
+					spCaret = SelectionPosition(pdoc->LineStart(pdoc->LineFromPosition(spCaret.Position())));
+				break;
+			case SCI_VCHOME:
+			case SCI_VCHOMEEXTEND:
+				// VCHome alternates between beginning of line and beginning of text so may move back or forwards
+				spCaret = SelectionPosition(pdoc->VCHomePosition(spCaret.Position()));
+				break;
+			case SCI_VCHOMEDISPLAY:
+			case SCI_VCHOMEDISPLAYEXTEND:
+				spCaret = SelectionPosition(VCHomeDisplayPosition(spCaret.Position()));
+				break;
+			case SCI_VCHOMEWRAP:
+			case SCI_VCHOMEWRAPEXTEND:
+				spCaret = SelectionPosition(VCHomeWrapPosition(spCaret.Position()));
+				break;
+			case SCI_LINEEND:
+			case SCI_LINEENDEXTEND:
+				spCaret = SelectionPosition(pdoc->LineEndPosition(spCaret.Position()));
+				break;
+			case SCI_LINEENDDISPLAY:
+			case SCI_LINEENDDISPLAYEXTEND:
+				spCaret = SelectionPosition(StartEndDisplayLine(spCaret.Position(), false));
+				break;
+			case SCI_LINEENDWRAP:
+			case SCI_LINEENDWRAPEXTEND:
+				spCaret = SelectionPosition(LineEndWrapPosition(spCaret.Position()));
+				break;
+
+			default:
+				PLATFORM_ASSERT(false);
+			}
+
+			const int directionMove = (spCaret < spCaretNow) ? -1 : 1;
+			spCaret = MovePositionSoVisible(spCaret, directionMove);
+
+			// Handle move versus extend, and special behaviour for non-emoty left/right
+			switch (iMessage) {
+			case SCI_CHARLEFT:
+			case SCI_CHARRIGHT:
+				if (sel.Range(r).Empty()) {
+					sel.Range(r) = SelectionRange(spCaret);
+				} else {
+					sel.Range(r) = SelectionRange(
+						(iMessage == SCI_CHARLEFT) ? sel.Range(r).Start() : sel.Range(r).End());
+				}
+				break;
+
+			case SCI_WORDLEFT:
+			case SCI_WORDRIGHT:
+			case SCI_WORDLEFTEND:
+			case SCI_WORDRIGHTEND:
+			case SCI_WORDPARTLEFT:
+			case SCI_WORDPARTRIGHT:
+			case SCI_HOME:
+			case SCI_HOMEDISPLAY:
+			case SCI_HOMEWRAP:
+			case SCI_VCHOME:
+			case SCI_VCHOMEDISPLAY:
+			case SCI_VCHOMEWRAP:
+			case SCI_LINEEND:
+			case SCI_LINEENDDISPLAY:
+			case SCI_LINEENDWRAP:
+				sel.Range(r) = SelectionRange(spCaret);
+				break;
+
+			case SCI_CHARLEFTEXTEND:
+			case SCI_CHARRIGHTEXTEND:
+			case SCI_WORDLEFTEXTEND:
+			case SCI_WORDRIGHTEXTEND:
+			case SCI_WORDLEFTENDEXTEND:
+			case SCI_WORDRIGHTENDEXTEND:
+			case SCI_WORDPARTLEFTEXTEND:
+			case SCI_WORDPARTRIGHTEXTEND:
+			case SCI_HOMEEXTEND:
+			case SCI_HOMEDISPLAYEXTEND:
+			case SCI_HOMEWRAPEXTEND:
+			case SCI_VCHOMEEXTEND:
+			case SCI_VCHOMEDISPLAYEXTEND:
+			case SCI_VCHOMEWRAPEXTEND:
+			case SCI_LINEENDEXTEND:
+			case SCI_LINEENDDISPLAYEXTEND:
+			case SCI_LINEENDWRAPEXTEND: {
+				SelectionRange rangeNew = SelectionRange(spCaret, sel.Range(r).anchor);
+				sel.TrimOtherSelections(r, SelectionRange(rangeNew));
+				sel.Range(r) = rangeNew;
+				}
+				break;
+
+			default:
+				PLATFORM_ASSERT(false);
+			}
+		}
+	}
+
+	sel.RemoveDuplicates();
+
+	MovedCaret(sel.RangeMain().caret, SelectionPosition(INVALID_POSITION), true);
+
+	// Invalidate the new state of the selection
+	InvalidateWholeSelection();
+
+	SetLastXChosen();
+	// Need the line moving and so forth from MovePositionTo
+	return 0;
+}
+
+int Editor::DelWordOrLine(unsigned int iMessage) {
+	// Virtual space may be realised for SCI_DELWORDRIGHT or SCI_DELWORDRIGHTEND
+	// which means 2 actions so wrap in an undo group.
+
+	// Rightwards and leftwards deletions differ in treatment of virtual space.
+	// Clear virtual space for leftwards, realise for rightwards. 
+	const bool leftwards = (iMessage == SCI_DELWORDLEFT) || (iMessage == SCI_DELLINELEFT);
+
+	if (!additionalSelectionTyping) {
+		InvalidateWholeSelection();
+		sel.DropAdditionalRanges();
+	}
+
+	UndoGroup ug0(pdoc, (sel.Count() > 1) || !leftwards);
+
+	for (size_t r = 0; r < sel.Count(); r++) {
+		if (leftwards) {
+			// Delete to the left so first clear the virtual space.
+			sel.Range(r).ClearVirtualSpace();
+		} else {
+			// Delete to the right so first realise the virtual space.
+			sel.Range(r) = SelectionRange(
+				InsertSpace(sel.Range(r).caret.Position(), sel.Range(r).caret.VirtualSpace()));
+		}
+		
+		Range rangeDelete;
+		switch (iMessage) {
+		case SCI_DELWORDLEFT:
+			rangeDelete = Range(
+				pdoc->NextWordStart(sel.Range(r).caret.Position(), -1),
+				sel.Range(r).caret.Position());
+			break;
+		case SCI_DELWORDRIGHT:
+			rangeDelete = Range(
+				sel.Range(r).caret.Position(),
+				pdoc->NextWordStart(sel.Range(r).caret.Position(), 1));
+			break;
+		case SCI_DELWORDRIGHTEND:
+			rangeDelete = Range(
+				sel.Range(r).caret.Position(),
+				pdoc->NextWordEnd(sel.Range(r).caret.Position(), 1));
+			break;
+		case SCI_DELLINELEFT:
+			rangeDelete = Range(
+				pdoc->LineStart(pdoc->LineFromPosition(sel.Range(r).caret.Position())),
+				sel.Range(r).caret.Position());
+			break;
+		case SCI_DELLINERIGHT:
+			rangeDelete = Range(
+				sel.Range(r).caret.Position(),
+				pdoc->LineEnd(pdoc->LineFromPosition(sel.Range(r).caret.Position())));
+			break;
+		}
+		if (!RangeContainsProtected(rangeDelete.start, rangeDelete.end)) {
+			pdoc->DeleteChars(rangeDelete.start, rangeDelete.end - rangeDelete.start);
+		}
+	}
+
+	// May need something stronger here: can selections overlap at this point?
+	sel.RemoveDuplicates();
+
+	MovedCaret(sel.RangeMain().caret, SelectionPosition(INVALID_POSITION), true);
+
+	// Invalidate the new state of the selection
+	InvalidateWholeSelection();
+
+	SetLastXChosen();
+	return 0;
+}
+
 int Editor::KeyCommand(unsigned int iMessage) {
 	switch (iMessage) {
 	case SCI_LINEDOWN:
@@ -3116,174 +3561,48 @@
 		ScrollTo(topLine - 1);
 		MoveCaretInsideView(false);
 		break;
+
 	case SCI_CHARLEFT:
-		if (SelectionEmpty() || sel.MoveExtends()) {
-			if ((sel.Count() == 1) && pdoc->IsLineEndPosition(sel.MainCaret()) && sel.RangeMain().caret.VirtualSpace()) {
-				SelectionPosition spCaret = sel.RangeMain().caret;
-				spCaret.SetVirtualSpace(spCaret.VirtualSpace() - 1);
-				MovePositionTo(spCaret);
-			} else if (sel.MoveExtends() && sel.selType == Selection::selStream) {
-				MovePositionTo(MovePositionSoVisible(SelectionPosition(sel.MainCaret() - 1), -1));
-			} else {
-				MovePositionTo(MovePositionSoVisible(
-					SelectionPosition((sel.LimitsForRectangularElseMain().start).Position() - 1), -1));
-			}
-		} else {
-			MovePositionTo(sel.LimitsForRectangularElseMain().start);
-		}
-		SetLastXChosen();
-		break;
 	case SCI_CHARLEFTEXTEND:
-		if (pdoc->IsLineEndPosition(sel.MainCaret()) && sel.RangeMain().caret.VirtualSpace()) {
-			SelectionPosition spCaret = sel.RangeMain().caret;
-			spCaret.SetVirtualSpace(spCaret.VirtualSpace() - 1);
-			MovePositionTo(spCaret, Selection::selStream);
-		} else {
-			MovePositionTo(MovePositionSoVisible(SelectionPosition(sel.MainCaret() - 1), -1), Selection::selStream);
-		}
-		SetLastXChosen();
-		break;
 	case SCI_CHARLEFTRECTEXTEND:
-		if (pdoc->IsLineEndPosition(sel.MainCaret()) && sel.RangeMain().caret.VirtualSpace()) {
-			SelectionPosition spCaret = sel.RangeMain().caret;
-			spCaret.SetVirtualSpace(spCaret.VirtualSpace() - 1);
-			MovePositionTo(spCaret, Selection::selRectangle);
-		} else {
-			MovePositionTo(MovePositionSoVisible(SelectionPosition(sel.MainCaret() - 1), -1), Selection::selRectangle);
-		}
-		SetLastXChosen();
-		break;
 	case SCI_CHARRIGHT:
-		if (SelectionEmpty() || sel.MoveExtends()) {
-			if ((virtualSpaceOptions & SCVS_USERACCESSIBLE) && pdoc->IsLineEndPosition(sel.MainCaret())) {
-				SelectionPosition spCaret = sel.RangeMain().caret;
-				spCaret.SetVirtualSpace(spCaret.VirtualSpace() + 1);
-				MovePositionTo(spCaret);
-			} else if (sel.MoveExtends() && sel.selType == Selection::selStream) {
-				MovePositionTo(MovePositionSoVisible(SelectionPosition(sel.MainCaret() + 1), 1));
-			} else {
-				MovePositionTo(MovePositionSoVisible(
-					SelectionPosition((sel.LimitsForRectangularElseMain().end).Position() + 1), 1));
-			}
-		} else {
-			MovePositionTo(sel.LimitsForRectangularElseMain().end);
-		}
-		SetLastXChosen();
-		break;
 	case SCI_CHARRIGHTEXTEND:
-		if ((virtualSpaceOptions & SCVS_USERACCESSIBLE) && pdoc->IsLineEndPosition(sel.MainCaret())) {
-			SelectionPosition spCaret = sel.RangeMain().caret;
-			spCaret.SetVirtualSpace(spCaret.VirtualSpace() + 1);
-			MovePositionTo(spCaret, Selection::selStream);
-		} else {
-			MovePositionTo(MovePositionSoVisible(SelectionPosition(sel.MainCaret() + 1), 1), Selection::selStream);
-		}
-		SetLastXChosen();
-		break;
 	case SCI_CHARRIGHTRECTEXTEND:
-		if ((virtualSpaceOptions & SCVS_RECTANGULARSELECTION) && pdoc->IsLineEndPosition(sel.MainCaret())) {
-			SelectionPosition spCaret = sel.RangeMain().caret;
-			spCaret.SetVirtualSpace(spCaret.VirtualSpace() + 1);
-			MovePositionTo(spCaret, Selection::selRectangle);
-		} else {
-			MovePositionTo(MovePositionSoVisible(SelectionPosition(sel.MainCaret() + 1), 1), Selection::selRectangle);
-		}
-		SetLastXChosen();
-		break;
 	case SCI_WORDLEFT:
-		MovePositionTo(MovePositionSoVisible(pdoc->NextWordStart(sel.MainCaret(), -1), -1));
-		SetLastXChosen();
-		break;
 	case SCI_WORDLEFTEXTEND:
-		MovePositionTo(MovePositionSoVisible(pdoc->NextWordStart(sel.MainCaret(), -1), -1), Selection::selStream);
-		SetLastXChosen();
-		break;
 	case SCI_WORDRIGHT:
-		MovePositionTo(MovePositionSoVisible(pdoc->NextWordStart(sel.MainCaret(), 1), 1));
-		SetLastXChosen();
-		break;
 	case SCI_WORDRIGHTEXTEND:
-		MovePositionTo(MovePositionSoVisible(pdoc->NextWordStart(sel.MainCaret(), 1), 1), Selection::selStream);
-		SetLastXChosen();
-		break;
-
 	case SCI_WORDLEFTEND:
-		MovePositionTo(MovePositionSoVisible(pdoc->NextWordEnd(sel.MainCaret(), -1), -1));
-		SetLastXChosen();
-		break;
 	case SCI_WORDLEFTENDEXTEND:
-		MovePositionTo(MovePositionSoVisible(pdoc->NextWordEnd(sel.MainCaret(), -1), -1), Selection::selStream);
-		SetLastXChosen();
-		break;
 	case SCI_WORDRIGHTEND:
-		MovePositionTo(MovePositionSoVisible(pdoc->NextWordEnd(sel.MainCaret(), 1), 1));
-		SetLastXChosen();
-		break;
 	case SCI_WORDRIGHTENDEXTEND:
-		MovePositionTo(MovePositionSoVisible(pdoc->NextWordEnd(sel.MainCaret(), 1), 1), Selection::selStream);
-		SetLastXChosen();
-		break;
-
+	case SCI_WORDPARTLEFT:
+	case SCI_WORDPARTLEFTEXTEND:
+	case SCI_WORDPARTRIGHT:
+	case SCI_WORDPARTRIGHTEXTEND:
 	case SCI_HOME:
-		MovePositionTo(pdoc->LineStart(pdoc->LineFromPosition(sel.MainCaret())));
-		SetLastXChosen();
-		break;
 	case SCI_HOMEEXTEND:
-		MovePositionTo(pdoc->LineStart(pdoc->LineFromPosition(sel.MainCaret())), Selection::selStream);
-		SetLastXChosen();
-		break;
 	case SCI_HOMERECTEXTEND:
-		MovePositionTo(pdoc->LineStart(pdoc->LineFromPosition(sel.MainCaret())), Selection::selRectangle);
-		SetLastXChosen();
-		break;
+	case SCI_HOMEDISPLAY:
+	case SCI_HOMEDISPLAYEXTEND:
+	case SCI_HOMEWRAP:
+	case SCI_HOMEWRAPEXTEND:
+	case SCI_VCHOME:
+	case SCI_VCHOMEEXTEND:
+	case SCI_VCHOMERECTEXTEND:
+	case SCI_VCHOMEDISPLAY:
+	case SCI_VCHOMEDISPLAYEXTEND:
+	case SCI_VCHOMEWRAP:
+	case SCI_VCHOMEWRAPEXTEND:
 	case SCI_LINEEND:
-		MovePositionTo(pdoc->LineEndPosition(sel.MainCaret()));
-		SetLastXChosen();
-		break;
 	case SCI_LINEENDEXTEND:
-		MovePositionTo(pdoc->LineEndPosition(sel.MainCaret()), Selection::selStream);
-		SetLastXChosen();
-		break;
 	case SCI_LINEENDRECTEXTEND:
-		MovePositionTo(pdoc->LineEndPosition(sel.MainCaret()), Selection::selRectangle);
-		SetLastXChosen();
-		break;
-	case SCI_HOMEWRAP: {
-			SelectionPosition homePos = MovePositionSoVisible(StartEndDisplayLine(sel.MainCaret(), true), -1);
-			if (sel.RangeMain().caret <= homePos)
-				homePos = SelectionPosition(pdoc->LineStart(pdoc->LineFromPosition(sel.MainCaret())));
-			MovePositionTo(homePos);
-			SetLastXChosen();
-		}
-		break;
-	case SCI_HOMEWRAPEXTEND: {
-			SelectionPosition homePos = MovePositionSoVisible(StartEndDisplayLine(sel.MainCaret(), true), -1);
-			if (sel.RangeMain().caret <= homePos)
-				homePos = SelectionPosition(pdoc->LineStart(pdoc->LineFromPosition(sel.MainCaret())));
-			MovePositionTo(homePos, Selection::selStream);
-			SetLastXChosen();
-		}
-		break;
-	case SCI_LINEENDWRAP: {
-			SelectionPosition endPos = MovePositionSoVisible(StartEndDisplayLine(sel.MainCaret(), false), 1);
-			SelectionPosition realEndPos = SelectionPosition(pdoc->LineEndPosition(sel.MainCaret()));
-			if (endPos > realEndPos      // if moved past visible EOLs
-			        || sel.RangeMain().caret >= endPos) // if at end of display line already
-				endPos = realEndPos;
-			MovePositionTo(endPos);
-			SetLastXChosen();
-		}
-		break;
-	case SCI_LINEENDWRAPEXTEND: {
-			SelectionPosition endPos = MovePositionSoVisible(StartEndDisplayLine(sel.MainCaret(), false), 1);
-			SelectionPosition realEndPos = SelectionPosition(pdoc->LineEndPosition(sel.MainCaret()));
-			if (endPos > realEndPos      // if moved past visible EOLs
-			        || sel.RangeMain().caret >= endPos) // if at end of display line already
-				endPos = realEndPos;
-			MovePositionTo(endPos, Selection::selStream);
-			SetLastXChosen();
-		}
-		break;
+	case SCI_LINEENDDISPLAY:
+	case SCI_LINEENDDISPLAYEXTEND:
+	case SCI_LINEENDWRAP:
+	case SCI_LINEENDWRAPEXTEND:
+		return HorizontalMove(iMessage);
+
 	case SCI_DOCUMENTSTART:
 		MovePositionTo(0);
 		SetLastXChosen();
@@ -3381,38 +3700,6 @@
 	case SCI_FORMFEED:
 		AddChar('\f');
 		break;
-	case SCI_VCHOME:
-		MovePositionTo(pdoc->VCHomePosition(sel.MainCaret()));
-		SetLastXChosen();
-		break;
-	case SCI_VCHOMEEXTEND:
-		MovePositionTo(pdoc->VCHomePosition(sel.MainCaret()), Selection::selStream);
-		SetLastXChosen();
-		break;
-	case SCI_VCHOMERECTEXTEND:
-		MovePositionTo(pdoc->VCHomePosition(sel.MainCaret()), Selection::selRectangle);
-		SetLastXChosen();
-		break;
-	case SCI_VCHOMEWRAP: {
-			SelectionPosition homePos = SelectionPosition(pdoc->VCHomePosition(sel.MainCaret()));
-			SelectionPosition viewLineStart = MovePositionSoVisible(StartEndDisplayLine(sel.MainCaret(), true), -1);
-			if ((viewLineStart < sel.RangeMain().caret) && (viewLineStart > homePos))
-				homePos = viewLineStart;
-
-			MovePositionTo(homePos);
-			SetLastXChosen();
-		}
-		break;
-	case SCI_VCHOMEWRAPEXTEND: {
-			SelectionPosition homePos = SelectionPosition(pdoc->VCHomePosition(sel.MainCaret()));
-			SelectionPosition viewLineStart = MovePositionSoVisible(StartEndDisplayLine(sel.MainCaret(), true), -1);
-			if ((viewLineStart < sel.RangeMain().caret) && (viewLineStart > homePos))
-				homePos = viewLineStart;
-
-			MovePositionTo(homePos, Selection::selStream);
-			SetLastXChosen();
-		}
-		break;
 	case SCI_ZOOMIN:
 		if (vs.zoomLevel < 20) {
 			vs.zoomLevel++;
@@ -3427,46 +3714,14 @@
 			NotifyZoom();
 		}
 		break;
-	case SCI_DELWORDLEFT: {
-			int startWord = pdoc->NextWordStart(sel.MainCaret(), -1);
-			pdoc->DeleteChars(startWord, sel.MainCaret() - startWord);
-			sel.RangeMain().ClearVirtualSpace();
-			SetLastXChosen();
-		}
-		break;
-	case SCI_DELWORDRIGHT: {
-			UndoGroup ug(pdoc);
-			InvalidateWholeSelection();
-			sel.RangeMain().caret = SelectionPosition(
-				InsertSpace(sel.RangeMain().caret.Position(), sel.RangeMain().caret.VirtualSpace()));
-			sel.RangeMain().anchor = sel.RangeMain().caret;
-			int endWord = pdoc->NextWordStart(sel.MainCaret(), 1);
-			pdoc->DeleteChars(sel.MainCaret(), endWord - sel.MainCaret());
-		}
-		break;
-	case SCI_DELWORDRIGHTEND: {
-			UndoGroup ug(pdoc);
-			InvalidateWholeSelection();
-			sel.RangeMain().caret = SelectionPosition(
-				InsertSpace(sel.RangeMain().caret.Position(), sel.RangeMain().caret.VirtualSpace()));
-			int endWord = pdoc->NextWordEnd(sel.MainCaret(), 1);
-			pdoc->DeleteChars(sel.MainCaret(), endWord - sel.MainCaret());
-		}
-		break;
-	case SCI_DELLINELEFT: {
-			int line = pdoc->LineFromPosition(sel.MainCaret());
-			int start = pdoc->LineStart(line);
-			pdoc->DeleteChars(start, sel.MainCaret() - start);
-			sel.RangeMain().ClearVirtualSpace();
-			SetLastXChosen();
-		}
-		break;
-	case SCI_DELLINERIGHT: {
-			int line = pdoc->LineFromPosition(sel.MainCaret());
-			int end = pdoc->LineEnd(line);
-			pdoc->DeleteChars(sel.MainCaret(), end - sel.MainCaret());
-		}
-		break;
+
+	case SCI_DELWORDLEFT:
+	case SCI_DELWORDRIGHT:
+	case SCI_DELWORDRIGHTEND:
+	case SCI_DELLINELEFT:
+	case SCI_DELLINERIGHT:
+		return DelWordOrLine(iMessage);
+
 	case SCI_LINECOPY: {
 			int lineStart = pdoc->LineFromPosition(SelectionStart().Position());
 			int lineEnd = pdoc->LineFromPosition(SelectionEnd().Position());
@@ -3506,62 +3761,6 @@
 	case SCI_UPPERCASE:
 		ChangeCaseOfSelection(cmUpper);
 		break;
-	case SCI_WORDPARTLEFT:
-		MovePositionTo(MovePositionSoVisible(pdoc->WordPartLeft(sel.MainCaret()), -1));
-		SetLastXChosen();
-		break;
-	case SCI_WORDPARTLEFTEXTEND:
-		MovePositionTo(MovePositionSoVisible(pdoc->WordPartLeft(sel.MainCaret()), -1), Selection::selStream);
-		SetLastXChosen();
-		break;
-	case SCI_WORDPARTRIGHT:
-		MovePositionTo(MovePositionSoVisible(pdoc->WordPartRight(sel.MainCaret()), 1));
-		SetLastXChosen();
-		break;
-	case SCI_WORDPARTRIGHTEXTEND:
-		MovePositionTo(MovePositionSoVisible(pdoc->WordPartRight(sel.MainCaret()), 1), Selection::selStream);
-		SetLastXChosen();
-		break;
-	case SCI_HOMEDISPLAY:
-		MovePositionTo(MovePositionSoVisible(
-		            StartEndDisplayLine(sel.MainCaret(), true), -1));
-		SetLastXChosen();
-		break;
-	case SCI_VCHOMEDISPLAY: {
-			SelectionPosition homePos = SelectionPosition(pdoc->VCHomePosition(sel.MainCaret()));
-			SelectionPosition viewLineStart = MovePositionSoVisible(StartEndDisplayLine(sel.MainCaret(), true), -1);
-			if (viewLineStart > homePos)
-				homePos = viewLineStart;
-
-			MovePositionTo(homePos);
-			SetLastXChosen();
-		}
-		break;
-	case SCI_HOMEDISPLAYEXTEND:
-		MovePositionTo(MovePositionSoVisible(
-		            StartEndDisplayLine(sel.MainCaret(), true), -1), Selection::selStream);
-		SetLastXChosen();
-		break;
-	case SCI_VCHOMEDISPLAYEXTEND: {
-			SelectionPosition homePos = SelectionPosition(pdoc->VCHomePosition(sel.MainCaret()));
-			SelectionPosition viewLineStart = MovePositionSoVisible(StartEndDisplayLine(sel.MainCaret(), true), -1);
-			if (viewLineStart > homePos)
-				homePos = viewLineStart;
-
-			MovePositionTo(homePos, Selection::selStream);
-			SetLastXChosen();
-		}
-		break;
-	case SCI_LINEENDDISPLAY:
-		MovePositionTo(MovePositionSoVisible(
-		            StartEndDisplayLine(sel.MainCaret(), false), 1));
-		SetLastXChosen();
-		break;
-	case SCI_LINEENDDISPLAYEXTEND:
-		MovePositionTo(MovePositionSoVisible(
-		            StartEndDisplayLine(sel.MainCaret(), false), 1), Selection::selStream);
-		SetLastXChosen();
-		break;
 	case SCI_SCROLLTOSTART:
 		ScrollTo(0);
 		break;
diff -r c09341366d82 src/Editor.h
--- a/src/Editor.h	Sat Jun 20 09:44:35 2015 +1000
+++ b/src/Editor.h	Mon Jun 22 12:01:25 2015 +1000
@@ -326,6 +326,7 @@
 	bool SelectionContainsProtected();
 	int MovePositionOutsideChar(int pos, int moveDir, bool checkLineEnd=true) const;
 	SelectionPosition MovePositionOutsideChar(SelectionPosition pos, int moveDir, bool checkLineEnd=true) const;
+	void MovedCaret(SelectionPosition newPos, SelectionPosition previousPos, bool ensureVisible);
 	void MovePositionTo(SelectionPosition newPos, Selection::selTypes selt=Selection::noSel, bool ensureVisible=true);
 	void MovePositionTo(int newPos, Selection::selTypes selt=Selection::noSel, bool ensureVisible=true);
 	SelectionPosition MovePositionSoVisible(SelectionPosition pos, int moveDir);
@@ -458,9 +459,15 @@
 	void Duplicate(bool forLine);
 	virtual void CancelModes();
 	void NewLine();
+	SelectionPosition PositionUpOrDown(SelectionPosition spStart, int direction, int lastX);
 	void CursorUpOrDown(int direction, Selection::selTypes selt);
 	void ParaUpOrDown(int direction, Selection::selTypes selt);
 	int StartEndDisplayLine(int pos, bool start);
+	int VCHomeDisplayPosition(int position);
+	int VCHomeWrapPosition(int position);
+	int LineEndWrapPosition(int position);
+	int HorizontalMove(unsigned int iMessage);
+	int DelWordOrLine(unsigned int iMessage);
 	virtual int KeyCommand(unsigned int iMessage);
 	virtual int KeyDefault(int /* key */, int /*modifiers*/);
 	int KeyDownWithModifiers(int key, int modifiers, bool *consumed);
diff -r c09341366d82 src/Selection.cxx
--- a/src/Selection.cxx	Sat Jun 20 09:44:35 2015 +1000
+++ b/src/Selection.cxx	Mon Jun 22 12:01:25 2015 +1000
@@ -311,6 +311,14 @@
 	}
 }
 
+void Selection::TrimOtherSelections(size_t r, SelectionRange range) {
+	for (size_t i = 0; i<ranges.size(); ++i) {
+		if (i != r) {
+			ranges[i].Trim(range);
+		}
+	}
+}
+
 void Selection::SetSelection(SelectionRange range) {
 	ranges.clear();
 	ranges.push_back(range);
diff -r c09341366d82 src/Selection.h
--- a/src/Selection.h	Sat Jun 20 09:44:35 2015 +1000
+++ b/src/Selection.h	Mon Jun 22 12:01:25 2015 +1000
@@ -168,6 +168,7 @@
 	int Length() const;
 	void MovePositions(bool insertion, int startChange, int length);
 	void TrimSelection(SelectionRange range);
+	void TrimOtherSelections(size_t r, SelectionRange range);
 	void SetSelection(SelectionRange range);
 	void AddSelection(SelectionRange range);
 	void AddSelectionWithoutTrim(SelectionRange range);
