# HG changeset patch # User Mitchell # Date 1519447387 -39600 # Node ID 0a8a766722c0bc91e1e2ecd958927a504cea5cac # Parent 26e7749ba67ae8fc994e2f17c760a65c5d0ca479 Fix move-extends-selection mode for rectangular and line selections. diff -r 26e7749ba67a -r 0a8a766722c0 doc/ScintillaHistory.html --- a/doc/ScintillaHistory.html Thu Feb 22 08:13:37 2018 +1100 +++ b/doc/ScintillaHistory.html Sat Feb 24 15:43:07 2018 +1100 @@ -548,6 +548,9 @@ Bug #1993.
  • + Fix move-extends-selection mode for rectangular and line selections. +
  • +
  • SciTE on Windows can execute Python scripts directly by name when on path. Feature #1209.
  • diff -r 26e7749ba67a -r 0a8a766722c0 src/Editor.cxx --- a/src/Editor.cxx Thu Feb 22 08:13:37 2018 +1100 +++ b/src/Editor.cxx Sat Feb 24 15:43:07 2018 +1100 @@ -626,26 +626,31 @@ InvalidateSelection(sel.RangeMain(), true); } +/* For Line selection - the anchor and caret are always + at the beginning and end of the region lines. */ +SelectionRange Editor::LineSelectionRange(SelectionPosition currentPos_, SelectionPosition anchor_) const { + if (currentPos_ > anchor_) { + anchor_ = SelectionPosition(static_cast( + pdoc->LineStart(pdoc->LineFromPosition(anchor_.Position())))); + currentPos_ = SelectionPosition(static_cast( + pdoc->LineEnd(pdoc->LineFromPosition(currentPos_.Position())))); + } else { + currentPos_ = SelectionPosition(static_cast( + pdoc->LineStart(pdoc->LineFromPosition(currentPos_.Position())))); + anchor_ = SelectionPosition(static_cast( + pdoc->LineEnd(pdoc->LineFromPosition(anchor_.Position())))); + } + return SelectionRange(currentPos_, anchor_); +} + void Editor::SetSelection(SelectionPosition currentPos_, SelectionPosition anchor_) { currentPos_ = ClampPositionIntoDocument(currentPos_); anchor_ = ClampPositionIntoDocument(anchor_); Sci::Line currentLine = static_cast(pdoc->LineFromPosition(currentPos_.Position())); - /* For Line selection - ensure the anchor and caret are always - at the beginning and end of the region lines. */ + SelectionRange rangeNew(currentPos_, anchor_); if (sel.selType == Selection::selLines) { - if (currentPos_ > anchor_) { - anchor_ = SelectionPosition(static_cast( - pdoc->LineStart(pdoc->LineFromPosition(anchor_.Position())))); - currentPos_ = SelectionPosition(static_cast( - pdoc->LineEnd(pdoc->LineFromPosition(currentPos_.Position())))); - } else { - currentPos_ = SelectionPosition(static_cast( - pdoc->LineStart(pdoc->LineFromPosition(currentPos_.Position())))); - anchor_ = SelectionPosition(static_cast( - pdoc->LineEnd(pdoc->LineFromPosition(anchor_.Position())))); - } - } - SelectionRange rangeNew(currentPos_, anchor_); + rangeNew = LineSelectionRange(currentPos_, anchor_); + } if (sel.Count() > 1 || !(sel.RangeMain() == rangeNew)) { InvalidateSelection(rangeNew); } @@ -675,6 +680,8 @@ sel.Rectangular() = SelectionRange(SelectionPosition(currentPos_), sel.Rectangular().anchor); SetRectangularRange(); + } else if (sel.selType == Selection::selLines) { + sel.RangeMain() = LineSelectionRange(currentPos_, sel.RangeMain().anchor); } else { sel.RangeMain() = SelectionRange(SelectionPosition(currentPos_), sel.RangeMain().anchor); @@ -3120,7 +3127,7 @@ void Editor::CursorUpOrDown(int direction, Selection::selTypes selt) { if ((selt == Selection::noSel) && sel.MoveExtends()) { - selt = Selection::selStream; + selt = !sel.IsRectangular() ? Selection::selStream : Selection::selRectangle; } SelectionPosition caretToUse = sel.Range(sel.Main()).caret; if (sel.IsRectangular()) { @@ -3142,6 +3149,11 @@ sel.Rectangular() = SelectionRange(posNew, rangeBase.anchor); SetRectangularRange(); MovedCaret(posNew, caretToUse, true); + } else if (sel.selType == Selection::selLines && sel.MoveExtends()) { + // Calculate new caret position and call SetSelection(), which will ensure whole lines are selected. + const SelectionPosition posNew = MovePositionSoVisible( + PositionUpOrDown(caretToUse, direction, -1), direction); + SetSelection(posNew, sel.Range(sel.Main()).anchor); } else { InvalidateWholeSelection(); if (!additionalSelectionTyping || (sel.IsRectangular())) { @@ -3263,7 +3275,7 @@ } } -bool IsRectExtend(unsigned int iMessage) { +bool IsRectExtend(unsigned int iMessage, bool isRectMoveExtends) { switch (iMessage) { case SCI_CHARLEFTRECTEXTEND: case SCI_CHARRIGHTRECTEXTEND: @@ -3272,6 +3284,19 @@ case SCI_LINEENDRECTEXTEND: return true; default: + if (isRectMoveExtends) { + // Handle SCI_SETSELECTIONMODE(SC_SEL_RECTANGLE) and subsequent movements. + switch (iMessage) { + case SCI_CHARLEFTEXTEND: + case SCI_CHARRIGHTEXTEND: + case SCI_HOMEEXTEND: + case SCI_VCHOMEEXTEND: + case SCI_LINEENDEXTEND: + return true; + default: + return false; + } + } return false; } } @@ -3307,6 +3332,9 @@ } int Editor::HorizontalMove(unsigned int iMessage) { + if (sel.selType == Selection::selLines) { + return 0; // horizontal moves with line selection have no effect + } if (sel.MoveExtends()) { iMessage = WithExtends(iMessage); } @@ -3319,7 +3347,7 @@ // Invalidate each of the current selections InvalidateWholeSelection(); - if (IsRectExtend(iMessage)) { + if (IsRectExtend(iMessage, sel.IsRectangular() && sel.MoveExtends())) { const SelectionRange rangeBase = sel.IsRectangular() ? sel.Rectangular() : sel.RangeMain(); if (!sel.IsRectangular()) { sel.DropAdditionalRanges(); @@ -3328,6 +3356,7 @@ SelectionPosition spCaret = rangeBase.caret; switch (iMessage) { case SCI_CHARLEFTRECTEXTEND: + case SCI_CHARLEFTEXTEND: // only when sel.IsRectangular() && sel.MoveExtends() if (pdoc->IsLineEndPosition(spCaret.Position()) && spCaret.VirtualSpace()) { spCaret.SetVirtualSpace(spCaret.VirtualSpace() - 1); } else if ((virtualSpaceOptions & SCVS_NOWRAPLINESTART) == 0 || pdoc->GetColumn(spCaret.Position()) > 0) { @@ -3335,6 +3364,7 @@ } break; case SCI_CHARRIGHTRECTEXTEND: + case SCI_CHARRIGHTEXTEND: // only when sel.IsRectangular() && sel.MoveExtends() if ((virtualSpaceOptions & SCVS_RECTANGULARSELECTION) && pdoc->IsLineEndPosition(sel.MainCaret())) { spCaret.SetVirtualSpace(spCaret.VirtualSpace() + 1); } else { @@ -3342,13 +3372,16 @@ } break; case SCI_HOMERECTEXTEND: + case SCI_HOMEEXTEND: // only when sel.IsRectangular() && sel.MoveExtends() spCaret = SelectionPosition( static_cast(pdoc->LineStart(pdoc->LineFromPosition(spCaret.Position())))); break; case SCI_VCHOMERECTEXTEND: + case SCI_VCHOMEEXTEND: // only when sel.IsRectangular() && sel.MoveExtends() spCaret = SelectionPosition(pdoc->VCHomePosition(spCaret.Position())); break; case SCI_LINEENDRECTEXTEND: + case SCI_LINEENDEXTEND: // only when sel.IsRectangular() && sel.MoveExtends() spCaret = SelectionPosition(pdoc->LineEndPosition(spCaret.Position())); break; } @@ -7615,10 +7648,12 @@ case SC_SEL_RECTANGLE: sel.SetMoveExtends(!sel.MoveExtends() || (sel.selType != Selection::selRectangle)); sel.selType = Selection::selRectangle; + sel.Rectangular() = sel.RangeMain(); // adjust current selection break; case SC_SEL_LINES: sel.SetMoveExtends(!sel.MoveExtends() || (sel.selType != Selection::selLines)); sel.selType = Selection::selLines; + SetSelection(sel.RangeMain().caret, sel.RangeMain().anchor); // adjust current selection break; case SC_SEL_THIN: sel.SetMoveExtends(!sel.MoveExtends() || (sel.selType != Selection::selThin)); diff -r 26e7749ba67a -r 0a8a766722c0 src/Editor.h --- a/src/Editor.h Thu Feb 22 08:13:37 2018 +1100 +++ b/src/Editor.h Sat Feb 24 15:43:07 2018 +1100 @@ -312,6 +312,7 @@ void ThinRectangularRange(); void InvalidateSelection(SelectionRange newMain, bool invalidateWholeSelection=false); void InvalidateWholeSelection(); + SelectionRange LineSelectionRange(SelectionPosition currentPos_, SelectionPosition anchor_) const; void SetSelection(SelectionPosition currentPos_, SelectionPosition anchor_); void SetSelection(Sci::Position currentPos_, Sci::Position anchor_); void SetSelection(SelectionPosition currentPos_); diff -r 26e7749ba67a -r 0a8a766722c0 test/simpleTests.py --- a/test/simpleTests.py Thu Feb 22 08:13:37 2018 +1100 +++ b/test/simpleTests.py Sat Feb 24 15:43:07 2018 +1100 @@ -1466,6 +1466,88 @@ self.ed.DropSelectionN(0) self.assertEquals(self.ed.MainSelection, 2) +class TestModalSelection(unittest.TestCase): + + def setUp(self): + self.xite = Xite.xiteFrame + self.ed = self.xite.ed + self.ed.ClearAll() + self.ed.EmptyUndoBuffer() + # 3 lines of 3 characters + t = b"xxx\nxxx\nxxx" + self.ed.AddText(len(t), t) + + def testCharacterSelection(self): + self.ed.SetSelection(1, 1) + self.assertEquals(self.ed.Selections, 1) + self.assertEquals(self.ed.MainSelection, 0) + self.assertEquals(self.ed.GetSelectionNCaret(0), 1) + self.assertEquals(self.ed.GetSelectionNAnchor(0), 1) + self.ed.SelectionMode = self.ed.SC_SEL_STREAM + self.assertEquals(self.ed.Selections, 1) + self.assertEquals(self.ed.MainSelection, 0) + self.assertEquals(self.ed.GetSelectionNCaret(0), 1) + self.assertEquals(self.ed.GetSelectionNAnchor(0), 1) + self.ed.CharRight() + self.assertEquals(self.ed.Selections, 1) + self.assertEquals(self.ed.MainSelection, 0) + self.assertEquals(self.ed.GetSelectionNCaret(0), 2) + self.assertEquals(self.ed.GetSelectionNAnchor(0), 1) + self.ed.LineDown() + self.assertEquals(self.ed.Selections, 1) + self.assertEquals(self.ed.MainSelection, 0) + self.assertEquals(self.ed.GetSelectionNCaret(0), 6) + self.assertEquals(self.ed.GetSelectionNAnchor(0), 1) + self.ed.ClearSelections() + + def testRectangleSelection(self): + self.ed.SetSelection(1, 1) + self.assertEquals(self.ed.Selections, 1) + self.assertEquals(self.ed.MainSelection, 0) + self.assertEquals(self.ed.GetSelectionNCaret(0), 1) + self.assertEquals(self.ed.GetSelectionNAnchor(0), 1) + self.ed.SelectionMode = self.ed.SC_SEL_RECTANGLE + self.assertEquals(self.ed.Selections, 1) + self.assertEquals(self.ed.MainSelection, 0) + self.assertEquals(self.ed.GetSelectionNCaret(0), 1) + self.assertEquals(self.ed.GetSelectionNAnchor(0), 1) + self.ed.CharRight() + self.assertEquals(self.ed.Selections, 1) + self.assertEquals(self.ed.MainSelection, 0) + self.assertEquals(self.ed.GetSelectionNCaret(0), 2) + self.assertEquals(self.ed.GetSelectionNAnchor(0), 1) + self.ed.LineDown() + self.assertEquals(self.ed.Selections, 2) + self.assertEquals(self.ed.MainSelection, 1) + self.assertEquals(self.ed.GetSelectionNCaret(0), 2) + self.assertEquals(self.ed.GetSelectionNAnchor(0), 1) + self.assertEquals(self.ed.GetSelectionNCaret(1), 6) + self.assertEquals(self.ed.GetSelectionNAnchor(1), 5) + self.ed.ClearSelections() + + def testLinesSelection(self): + self.ed.SetSelection(1, 1) + self.assertEquals(self.ed.Selections, 1) + self.assertEquals(self.ed.MainSelection, 0) + self.assertEquals(self.ed.GetSelectionNCaret(0), 1) + self.assertEquals(self.ed.GetSelectionNAnchor(0), 1) + self.ed.SelectionMode = self.ed.SC_SEL_LINES + self.assertEquals(self.ed.Selections, 1) + self.assertEquals(self.ed.MainSelection, 0) + self.assertEquals(self.ed.GetSelectionNCaret(0), 0) + self.assertEquals(self.ed.GetSelectionNAnchor(0), 3) + self.ed.CharRight() + self.assertEquals(self.ed.Selections, 1) + self.assertEquals(self.ed.MainSelection, 0) + self.assertEquals(self.ed.GetSelectionNCaret(0), 0) + self.assertEquals(self.ed.GetSelectionNAnchor(0), 3) + self.ed.LineDown() + self.assertEquals(self.ed.Selections, 1) + self.assertEquals(self.ed.MainSelection, 0) + self.assertEquals(self.ed.GetSelectionNCaret(0), 7) + self.assertEquals(self.ed.GetSelectionNAnchor(0), 0) + self.ed.ClearSelections() + class TestStyleAttributes(unittest.TestCase): """ These tests are just to ensure that the calls set and retrieve values. They do not check the visual appearance of the style attributes.