aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorNeil <nyamatongwe@gmail.com>2019-11-24 08:04:57 +1100
committerNeil <nyamatongwe@gmail.com>2019-11-24 08:04:57 +1100
commitcf034c5958bd83d4a0f71ff79a47e4f76534d5d6 (patch)
tree709910a1d33720c95fa732e5cd7772e4befed213
parentdf312dd97f04aaed14f528f2f822302042d756c5 (diff)
downloadscintilla-mirror-cf034c5958bd83d4a0f71ff79a47e4f76534d5d6.tar.gz
Bug [#2140]. Move rather than grow selection when insertion at start.
-rw-r--r--doc/ScintillaHistory.html4
-rw-r--r--src/Selection.cxx21
-rw-r--r--src/Selection.h2
-rw-r--r--test/simpleTests.py92
4 files changed, 114 insertions, 5 deletions
diff --git a/doc/ScintillaHistory.html b/doc/ScintillaHistory.html
index 8109018c1..05e03640f 100644
--- a/doc/ScintillaHistory.html
+++ b/doc/ScintillaHistory.html
@@ -567,6 +567,10 @@
Released 24 October 2019.
</li>
<li>
+ Move rather than grow selection when insertion at start.
+ <a href="https://sourceforge.net/p/scintilla/bugs/2140/">Bug #2140</a>.
+ </li>
+ <li>
Allow target to have virtual space.
Add methods for finding the virtual space at start and end of multiple selections.
<a href="https://sourceforge.net/p/scintilla/feature-requests/1316/">Feature #1316</a>.
diff --git a/src/Selection.cxx b/src/Selection.cxx
index 9b08ea001..5667bf234 100644
--- a/src/Selection.cxx
+++ b/src/Selection.cxx
@@ -23,12 +23,16 @@
using namespace Scintilla;
-void SelectionPosition::MoveForInsertDelete(bool insertion, Sci::Position startChange, Sci::Position length) noexcept {
+void SelectionPosition::MoveForInsertDelete(bool insertion, Sci::Position startChange, Sci::Position length, bool moveForEqual) noexcept {
if (insertion) {
if (position == startChange) {
+ // Always consume virtual space
const Sci::Position virtualLengthRemove = std::min(length, virtualSpace);
virtualSpace -= virtualLengthRemove;
- position += virtualLengthRemove;
+ if (moveForEqual) {
+ const Sci::Position lengthAfterVirtualRemove = length - virtualLengthRemove;
+ position += lengthAfterVirtualRemove;
+ }
} else if (position > startChange) {
position += length;
}
@@ -85,8 +89,17 @@ Sci::Position SelectionRange::Length() const noexcept {
}
void SelectionRange::MoveForInsertDelete(bool insertion, Sci::Position startChange, Sci::Position length) noexcept {
- caret.MoveForInsertDelete(insertion, startChange, length);
- anchor.MoveForInsertDelete(insertion, startChange, length);
+ // For insertions that occur at the start of the selection move both the start
+ // and end of the selection to preserve the selected length.
+ // The end will automatically move since it is after the insertion, so determine
+ // which position is the start and pass this into
+ // SelectionPosition::MoveForInsertDelete.
+ // There isn't any reason to move an empty selection so don't move it.
+ const bool caretStart = caret < anchor;
+ const bool anchorStart = anchor < caret;
+
+ caret.MoveForInsertDelete(insertion, startChange, length, caretStart);
+ anchor.MoveForInsertDelete(insertion, startChange, length, anchorStart);
}
bool SelectionRange::Contains(Sci::Position pos) const noexcept {
diff --git a/src/Selection.h b/src/Selection.h
index 11a6f097a..741b75277 100644
--- a/src/Selection.h
+++ b/src/Selection.h
@@ -23,7 +23,7 @@ public:
position = 0;
virtualSpace = 0;
}
- void MoveForInsertDelete(bool insertion, Sci::Position startChange, Sci::Position length) noexcept;
+ void MoveForInsertDelete(bool insertion, Sci::Position startChange, Sci::Position length, bool moveForEqual) noexcept;
bool operator ==(const SelectionPosition &other) const noexcept {
return position == other.position && virtualSpace == other.virtualSpace;
}
diff --git a/test/simpleTests.py b/test/simpleTests.py
index f16d4268c..6cacf1045 100644
--- a/test/simpleTests.py
+++ b/test/simpleTests.py
@@ -1387,6 +1387,11 @@ class TestMultiSelection(unittest.TestCase):
# 3 lines of 3 characters
t = b"xxx\nxxx\nxxx"
self.ed.AddText(len(t), t)
+
+ def textOfSelection(self, n):
+ self.ed.TargetStart = self.ed.GetSelectionNStart(n)
+ self.ed.TargetEnd = self.ed.GetSelectionNEnd(n)
+ return bytes(self.ed.GetTargetText())
def testSelectionCleared(self):
self.ed.ClearSelections()
@@ -1542,6 +1547,93 @@ class TestMultiSelection(unittest.TestCase):
self.ed.DropSelectionN(0)
self.assertEquals(self.ed.MainSelection, 2)
+ def partFromSelection(self, n):
+ # Return a tuple (order, text) from a selection part
+ # order is a boolean whether the caret is before the anchor
+ self.ed.TargetStart = self.ed.GetSelectionNStart(n)
+ self.ed.TargetEnd = self.ed.GetSelectionNEnd(n)
+ return (self.ed.GetSelectionNCaret(n) < self.ed.GetSelectionNAnchor(n), self.textOfSelection(n))
+
+ def replacePart(self, n, part):
+ startSelection = self.ed.GetSelectionNStart(n)
+ self.ed.TargetStart = startSelection
+ self.ed.TargetEnd = self.ed.GetSelectionNEnd(n)
+ direction, text = part
+ length = len(text)
+ self.ed.ReplaceTarget(len(text), text)
+ if direction:
+ self.ed.SetSelectionNCaret(n, startSelection)
+ self.ed.SetSelectionNAnchor(n, startSelection + length)
+ else:
+ self.ed.SetSelectionNAnchor(n, startSelection)
+ self.ed.SetSelectionNCaret(n, startSelection + length)
+
+ def swapSelections(self):
+ # Swap the selections
+ part0 = self.partFromSelection(0)
+ part1 = self.partFromSelection(1)
+
+ self.replacePart(1, part0)
+ self.replacePart(0, part1)
+
+ def checkAdjacentSelections(self, selections, invert):
+ # Only called from testAdjacentSelections to try one permutation
+ self.ed.ClearAll()
+ self.ed.EmptyUndoBuffer()
+ t = b"ab"
+ texts = (b'b', b'a') if invert else (b'a', b'b')
+ self.ed.AddText(len(t), t)
+ sel0, sel1 = selections
+ self.ed.SetSelection(sel0[0], sel0[1])
+ self.ed.AddSelection(sel1[0], sel1[1])
+ self.assertEquals(self.ed.Selections, 2)
+ self.assertEquals(self.textOfSelection(0), texts[0])
+ self.assertEquals(self.textOfSelection(1), texts[1])
+ self.swapSelections()
+ self.assertEquals(self.ed.Contents(), b'ba')
+ self.assertEquals(self.ed.Selections, 2)
+ self.assertEquals(self.textOfSelection(0), texts[1])
+ self.assertEquals(self.textOfSelection(1), texts[0])
+
+ def testAdjacentSelections(self):
+ # For various permutations of selections, try swapping the text and ensure that the
+ # selections remain distinct
+ self.checkAdjacentSelections(((1, 0),(1, 2)), False)
+ self.checkAdjacentSelections(((0, 1),(1, 2)), False)
+ self.checkAdjacentSelections(((1, 0),(2, 1)), False)
+ self.checkAdjacentSelections(((0, 1),(2, 1)), False)
+
+ # Reverse order, first selection is after second
+ self.checkAdjacentSelections(((1, 2),(1, 0)), True)
+ self.checkAdjacentSelections(((1, 2),(0, 1)), True)
+ self.checkAdjacentSelections(((2, 1),(1, 0)), True)
+ self.checkAdjacentSelections(((2, 1),(0, 1)), True)
+
+ def testInsertBefore(self):
+ self.ed.ClearAll()
+ t = b"a"
+ self.ed.AddText(len(t), t)
+ self.ed.SetSelection(0, 1)
+ self.assertEquals(self.textOfSelection(0), b'a')
+
+ self.ed.SetTargetRange(0, 0)
+ self.ed.ReplaceTarget(1, b'1')
+ self.assertEquals(self.ed.Contents(), b'1a')
+ self.assertEquals(self.textOfSelection(0), b'a')
+
+ def testInsertAfter(self):
+ self.ed.ClearAll()
+ t = b"a"
+ self.ed.AddText(len(t), t)
+ self.ed.SetSelection(0, 1)
+ self.assertEquals(self.textOfSelection(0), b'a')
+
+ self.ed.SetTargetRange(1, 1)
+ self.ed.ReplaceTarget(1, b'9')
+ self.assertEquals(self.ed.Contents(), b'a9')
+ self.assertEquals(self.textOfSelection(0), b'a')
+
+
class TestModalSelection(unittest.TestCase):
def setUp(self):