# HG changeset patch
# User Teodor Petrov <fuscated@gmail.com>
# Date 1515864050 -7200
#      Sat Jan 13 19:20:50 2018 +0200
# Node ID aac9045e87459b7502814007da108c0d80073d38
# Parent  0f7f5a382dbb91c7e752cfeb3ee375d679096e7e
Try to make the rectangular selection to decay to multiple separate cursors when cursor is moved

* Add an option to enable/disable the decay to multiple cursors.
* Make the SCI_CANCEL command to cancel rectangular selections too, when
  we're in the new mode.
* Support for left, right, up, down keys in the new mode.
* Support for adding new lines with the enter key when in the new mode.
* Implement proper extend rect up or down commands for multiple cursors when
  in the new mode. Uses a pixel position based movement of the anchor
  similarly how it is done for the caret. This fixes problems when dealing
  with: multi-byte characters, virtual space and shorter lines below/above
  the current line.
* Implement proper support for extend rect left/right/home/end commands when
  dealing with multile cursors and we're in the new mode. This is implemented
  by remapping the commands to their non-rect equivalents.
* Document the commands for getting/setting the new option.
* Document changes to the behaviour of SCI_CANCEL.

diff -r 0f7f5a382dbb -r aac9045e8745 doc/ScintillaDoc.html
--- a/doc/ScintillaDoc.html	Thu Jan 11 19:23:33 2018 +1100
+++ b/doc/ScintillaDoc.html	Sat Jan 13 19:20:50 2018 +0200
@@ -1533,6 +1533,8 @@
      <a class="message" href="#SCI_GETMULTIPLESELECTION">SCI_GETMULTIPLESELECTION &rarr; bool</a><br />
      <a class="message" href="#SCI_SETADDITIONALSELECTIONTYPING">SCI_SETADDITIONALSELECTIONTYPING(bool additionalSelectionTyping)</a><br />
      <a class="message" href="#SCI_GETADDITIONALSELECTIONTYPING">SCI_GETADDITIONALSELECTIONTYPING &rarr; bool</a><br />
+     <a class="message" href="#SCI_SETSELECTIONRECTANGULARDECAYTOMULTIPLE">SCI_SETSELECTIONRECTANGULARDECAYTOMULTIPLE(bool selectionRectangularDecayToMultiple)</a><br />
+     <a class="message" href="#SCI_GETSELECTIONRECTANGULARDECAYTOMULTIPLE">SCI_GETSELECTIONRECTANGULARDECAYTOMULTIPLE &rarr; bool</a><br />
      <a class="message" href="#SCI_SETMULTIPASTE">SCI_SETMULTIPASTE(int multiPaste)</a><br />
      <a class="message" href="#SCI_GETMULTIPASTE">SCI_GETMULTIPASTE &rarr; int</a><br />
      <a class="message" href="#SCI_SETVIRTUALSPACEOPTIONS">SCI_SETVIRTUALSPACEOPTIONS(int virtualSpaceOptions)</a><br />
@@ -1635,6 +1637,14 @@
      Also allows selection and word and line deletion commands.</p>
 
     <p>
+    <b id="SCI_SETSELECTIONRECTANGULARDECAYTOMULTIPLE">SCI_SETSELECTIONRECTANGULARDECAYTOMULTIPLE(bool selectionRectangularDecayToMultiple)</b><br />
+    <b id="SCI_GETSELECTIONRECTANGULARDECAYTOMULTIPLE">SCI_GETSELECTIONRECTANGULARDECAYTOMULTIPLE &rarr; bool</b><br />
+     Whether new line, cursor left/right/up/down, home, and end convert a rectangular selection to a multiple cursors.
+     This makes it possible to move around and edit multiple lines simultaneously. This feature requires
+     <code>ADDITIONALSELECTIONTYPING</code> to also be enabled, otherwise random effects are possible.
+     .</p>
+
+    <p>
     <b id="SCI_SETMULTIPASTE">SCI_SETMULTIPASTE(int multiPaste)</b><br />
     <b id="SCI_GETMULTIPASTE">SCI_GETMULTIPASTE &rarr; int</b><br />
      When pasting into multiple selections, the pasted text can go into just the main selection with <code>SC_MULTIPASTE_ONCE</code>=0
@@ -5325,6 +5335,8 @@
 
     <p class="message" id="SCI_CANCEL">The <code>SCI_CANCEL</code> command cancels autocompletion and
     calltip display and drops any additional selections.
+    When <code>SCI_GETSELECTIONRECTANGULARDECAYTOMULTIPLE=true</code> this command drops rectangular
+    selections and turns them in stream selections (single cursor).
      </p>
 
     <h2 id="KeyBindings">Key bindings</h2>
diff -r 0f7f5a382dbb -r aac9045e8745 include/Scintilla.h
--- a/include/Scintilla.h	Thu Jan 11 19:23:33 2018 +1100
+++ b/include/Scintilla.h	Sat Jan 13 19:20:50 2018 +0200
@@ -888,6 +888,8 @@
 #define SCI_GETMULTIPLESELECTION 2564
 #define SCI_SETADDITIONALSELECTIONTYPING 2565
 #define SCI_GETADDITIONALSELECTIONTYPING 2566
+#define SCI_SETSELECTIONRECTANGULARDECAYTOMULTIPLE 2710
+#define SCI_GETSELECTIONRECTANGULARDECAYTOMULTIPLE 2711
 #define SCI_SETADDITIONALCARETSBLINK 2567
 #define SCI_GETADDITIONALCARETSBLINK 2568
 #define SCI_SETADDITIONALCARETSVISIBLE 2608
diff -r 0f7f5a382dbb -r aac9045e8745 include/Scintilla.iface
--- a/include/Scintilla.iface	Thu Jan 11 19:23:33 2018 +1100
+++ b/include/Scintilla.iface	Sat Jan 13 19:20:50 2018 +0200
@@ -2344,6 +2344,12 @@
 # Whether typing can be performed into multiple selections
 get bool GetAdditionalSelectionTyping=2566(,)
 
+# Set whether cursor movements turn rectangular selection into multiple selections
+set void SetSelectionRectangularDecayToMultiple=2710(bool selectionRectangularDecayToMultiple,)
+
+# Whether cursor movements turn rectangular selection into multiple selections
+get bool GetSelectionRectangularDecayToMultiple=2711(,)
+
 # Set whether additional carets will blink
 set void SetAdditionalCaretsBlink=2567(bool additionalCaretsBlink,)
 
diff -r 0f7f5a382dbb -r aac9045e8745 src/Editor.cxx
--- a/src/Editor.cxx	Thu Jan 11 19:23:33 2018 +1100
+++ b/src/Editor.cxx	Sat Jan 13 19:20:50 2018 +0200
@@ -157,6 +157,7 @@
 	mouseSelectionRectangularSwitch = false;
 	multipleSelection = false;
 	additionalSelectionTyping = false;
+	selectionRectangularDecayToMultiple = false;
 	multiPasteMode = SC_MULTIPASTE_ONCE;
 	virtualSpaceOptions = SCVS_NONE;
 
@@ -3022,7 +3023,7 @@
 
 void Editor::NewLine() {
 	InvalidateWholeSelection();
-	if (sel.IsRectangular() || !additionalSelectionTyping) {
+	if ((sel.IsRectangular() && !selectionRectangularDecayToMultiple) || !additionalSelectionTyping) {
 		// Remove non-main ranges
 		sel.DropAdditionalRanges();
 	}
@@ -3063,6 +3064,11 @@
 		}
 	}
 
+	// We've added several new lines, so we no longer have a valid rectangular
+	// selection, thus we are switching it to a stream selection.
+	if (selectionRectangularDecayToMultiple && sel.IsRectangular() && countInsertions > 1)
+		sel.selType = Selection::selStream;
+
 	SetLastXChosen();
 	SetScrollBars();
 	EnsureCaretVisible();
@@ -3132,19 +3138,31 @@
 	}
 	if (selt == Selection::selRectangle) {
 		const SelectionRange rangeBase = sel.IsRectangular() ? sel.Rectangular() : sel.RangeMain();
-		if (!sel.IsRectangular()) {
+		if (!sel.IsRectangular() && !selectionRectangularDecayToMultiple) {
 			InvalidateWholeSelection();
 			sel.DropAdditionalRanges();
 		}
 		const SelectionPosition posNew = MovePositionSoVisible(
 			PositionUpOrDown(caretToUse, direction, lastXChosen), direction);
-		sel.selType = Selection::selRectangle;
-		sel.Rectangular() = SelectionRange(posNew, rangeBase.anchor);
-		SetRectangularRange();
+
+		// Expand or start a rect selection if possible, else create multiple selections.
+		if (sel.IsRectangular() || sel.Count() <= 1 || !selectionRectangularDecayToMultiple) {
+			sel.selType = Selection::selRectangle;
+			sel.Rectangular() = SelectionRange(posNew, rangeBase.anchor);
+			SetRectangularRange();
+		} else {
+			sel.selType = Selection::selStream;
+			// Find the position of the anchor if we move it up/down and then add a new selection.
+			const SelectionPosition posNewAnchor = MovePositionSoVisible(
+				PositionUpOrDown(rangeBase.anchor, direction, -1), direction);
+			sel.AddSelection(SelectionRange(posNew, posNewAnchor));
+			sel.RemoveDuplicates();
+		}
+
 		MovedCaret(posNew, caretToUse, true);
 	} else {
 		InvalidateWholeSelection();
-		if (!additionalSelectionTyping || (sel.IsRectangular())) {
+		if (!additionalSelectionTyping || (!selectionRectangularDecayToMultiple && sel.IsRectangular())) {
 			sel.DropAdditionalRanges();
 		}
 		sel.selType = Selection::selStream;
@@ -3276,6 +3294,23 @@
 	}
 }
 
+unsigned int RemapRectExtendMessages(unsigned int iMessage) {
+	switch (iMessage) {
+	case SCI_CHARLEFTRECTEXTEND:
+		return SCI_CHARLEFTEXTEND;
+	case SCI_CHARRIGHTRECTEXTEND:
+		return SCI_CHARRIGHTEXTEND;
+	case SCI_HOMERECTEXTEND:
+		return SCI_HOMEEXTEND;
+	case SCI_VCHOMERECTEXTEND:
+		return SCI_VCHOMEEXTEND;
+	case SCI_LINEENDRECTEXTEND:
+		return SCI_LINEENDEXTEND;
+	default:
+		return iMessage;
+	}
+}
+
 }
 
 Sci::Position Editor::VCHomeDisplayPosition(Sci::Position position) {
@@ -3319,7 +3354,10 @@
 	// Invalidate each of the current selections
 	InvalidateWholeSelection();
 
-	if (IsRectExtend(iMessage)) {
+	const bool isRectExtend=IsRectExtend(iMessage);
+	const bool looksLikeRectSelection = (sel.Count() <= 1 || (sel.Count() > 1 && sel.IsRectangular()));
+
+	if (isRectExtend && (!selectionRectangularDecayToMultiple || looksLikeRectSelection)) {
 		const SelectionRange rangeBase = sel.IsRectangular() ? sel.Rectangular() : sel.RangeMain();
 		if (!sel.IsRectangular()) {
 			sel.DropAdditionalRanges();
@@ -3357,9 +3395,9 @@
 		sel.selType = Selection::selRectangle;
 		sel.Rectangular() = SelectionRange(spCaret, rangeBase.anchor);
 		SetRectangularRange();
-	} else if (sel.IsRectangular()) {
+	} else if (sel.IsRectangular() && !selectionRectangularDecayToMultiple) {
 		// Not a rectangular extension so switch to stream.
-		const SelectionPosition selAtLimit = 
+		const SelectionPosition selAtLimit =
 			(NaturalDirection(iMessage) > 0) ? sel.Limits().end : sel.Limits().start;
 		sel.selType = Selection::selStream;
 		sel.SetSelection(SelectionRange(selAtLimit));
@@ -3367,7 +3405,14 @@
 		if (!additionalSelectionTyping) {
 			InvalidateWholeSelection();
 			sel.DropAdditionalRanges();
-		}
+		} else if (selectionRectangularDecayToMultiple && sel.IsRectangular()) {
+			sel.selType = Selection::selStream;
+		}
+
+		if (isRectExtend && selectionRectangularDecayToMultiple) {
+			iMessage = RemapRectExtendMessages(iMessage);
+		}
+
 		for (size_t r = 0; r < sel.Count(); r++) {
 			const SelectionPosition spCaretNow = sel.Range(r).caret;
 			SelectionPosition spCaret = spCaretNow;
@@ -3738,10 +3783,13 @@
 	case SCI_CANCEL:            	// Cancel any modes - handled in subclass
 		// Also unselect text
 		CancelModes();
-		if ((sel.Count() > 1) && !sel.IsRectangular()) {
+		if ((sel.Count() > 1) && (!sel.IsRectangular() || selectionRectangularDecayToMultiple)) {
 			// Drop additional selections
 			InvalidateWholeSelection();
 			sel.DropAdditionalRanges();
+			// Make sure the selection is not rectangular, because this breaks extending
+			// the selection when handling SCI_LINEDOWNRECTEXTEND and SCI_LINEUPRECTEXTEND.
+			sel.selType = Selection::selStream;
 		}
 		break;
 	case SCI_DELETEBACK:
@@ -7537,7 +7585,7 @@
 		std::vector<EdgeProperties>().swap(vs.theMultiEdge); // Free vector and memory, C++03 compatible
 		InvalidateStyleRedraw();
 		break;
-	
+
 	case SCI_GETACCESSIBILITY:
 		return SC_ACCESSIBILITY_DISABLED;
 
@@ -7935,6 +7983,14 @@
 	case SCI_GETADDITIONALSELECTIONTYPING:
 		return additionalSelectionTyping;
 
+	case SCI_SETSELECTIONRECTANGULARDECAYTOMULTIPLE:
+		selectionRectangularDecayToMultiple = wParam != 0;
+		InvalidateCaret();
+		break;
+
+	case SCI_GETSELECTIONRECTANGULARDECAYTOMULTIPLE:
+		return selectionRectangularDecayToMultiple;
+
 	case SCI_SETMULTIPASTE:
 		multiPasteMode = static_cast<int>(wParam);
 		break;
diff -r 0f7f5a382dbb -r aac9045e8745 src/Editor.h
--- a/src/Editor.h	Thu Jan 11 19:23:33 2018 +1100
+++ b/src/Editor.h	Sat Jan 13 19:20:50 2018 +0200
@@ -182,6 +182,7 @@
 	bool mouseSelectionRectangularSwitch;
 	bool multipleSelection;
 	bool additionalSelectionTyping;
+	bool selectionRectangularDecayToMultiple;
 	int multiPasteMode;
 
 	int virtualSpaceOptions;
