From 26b60d88b6d848f3ba55ca046852e079be5fe3c6 Mon Sep 17 00:00:00 2001
From: Neil
Date: Sun, 5 Nov 2023 22:11:26 +1100
Subject: Add SCI_SELECTIONFROMPOINT for modifying multiple selections.
---
call/ScintillaCall.cxx | 4 ++++
doc/ScintillaDoc.html | 6 ++++++
doc/ScintillaHistory.html | 3 +++
include/Scintilla.h | 1 +
include/Scintilla.iface | 3 +++
include/ScintillaCall.h | 1 +
include/ScintillaMessages.h | 1 +
src/Editor.cxx | 25 +++++++++++++++++++++++++
src/Editor.h | 1 +
src/Selection.cxx | 7 +++++++
src/Selection.h | 1 +
test/simpleTests.py | 39 ++++++++++++++++++++++++++++++++++++---
12 files changed, 89 insertions(+), 3 deletions(-)
diff --git a/call/ScintillaCall.cxx b/call/ScintillaCall.cxx
index 6935eacbd..99e400e28 100644
--- a/call/ScintillaCall.cxx
+++ b/call/ScintillaCall.cxx
@@ -2887,6 +2887,10 @@ void ScintillaCall::AddSelection(Position caret, Position anchor) {
Call(Message::AddSelection, caret, anchor);
}
+int ScintillaCall::SelectionFromPoint(int x, int y) {
+ return static_cast(Call(Message::SelectionFromPoint, x, y));
+}
+
void ScintillaCall::DropSelectionN(int selection) {
Call(Message::DropSelectionN, selection);
}
diff --git a/doc/ScintillaDoc.html b/doc/ScintillaDoc.html
index dcabdd9ee..27a542e90 100644
--- a/doc/ScintillaDoc.html
+++ b/doc/ScintillaDoc.html
@@ -1199,6 +1199,7 @@ struct Sci_TextRangeFull {
SCI_CLEARSELECTIONS
SCI_SETSELECTION(position caret, position anchor)
SCI_ADDSELECTION(position caret, position anchor)
+ SCI_SELECTIONFROMPOINT(int x, int y) → int
SCI_DROPSELECTIONN(int selection)
SCI_SETMAINSELECTION(int selection)
SCI_GETMAINSELECTION → int
@@ -1343,6 +1344,11 @@ struct Sci_TextRangeFull {
Since there is always at least one selection, to set a list of selections, the first selection should be
added with SCI_SETSELECTION and later selections added with SCI_ADDSELECTION
+
+ SCI_SELECTIONFROMPOINT(int x, int y) → int
+ Return the index of the selection at the point. If there is no selection at the point, return -1.
+ This can be used to drop a selection or make it the main selection.
+
SCI_DROPSELECTIONN(int selection)
If there are multiple selections, remove the indicated selection.
diff --git a/doc/ScintillaHistory.html b/doc/ScintillaHistory.html
index b808bd4b8..2e26d8900 100644
--- a/doc/ScintillaHistory.html
+++ b/doc/ScintillaHistory.html
@@ -591,6 +591,9 @@
Released 18 November 2023.
+ Add SCI_SELECTIONFROMPOINT for modifying multiple selections.
+
+
Add SCI_SETMOVEEXTENDSSELECTION and SCI_CHANGESELECTIONMODE to
simplify selection mode manipulation.
diff --git a/include/Scintilla.h b/include/Scintilla.h
index 64238a5a3..8c555352c 100644
--- a/include/Scintilla.h
+++ b/include/Scintilla.h
@@ -1022,6 +1022,7 @@ typedef sptr_t (*SciFnDirectStatus)(sptr_t ptr, unsigned int iMessage, uptr_t wP
#define SCI_CLEARSELECTIONS 2571
#define SCI_SETSELECTION 2572
#define SCI_ADDSELECTION 2573
+#define SCI_SELECTIONFROMPOINT 2474
#define SCI_DROPSELECTIONN 2671
#define SCI_SETMAINSELECTION 2574
#define SCI_GETMAINSELECTION 2575
diff --git a/include/Scintilla.iface b/include/Scintilla.iface
index c393d6179..918121596 100644
--- a/include/Scintilla.iface
+++ b/include/Scintilla.iface
@@ -2797,6 +2797,9 @@ fun void SetSelection=2572(position caret, position anchor)
# Add a selection
fun void AddSelection=2573(position caret, position anchor)
+# Find the selection index for a point. -1 when not at a selection.
+fun int SelectionFromPoint=2474(int x, int y)
+
# Drop one selection
fun void DropSelectionN=2671(int selection,)
diff --git a/include/ScintillaCall.h b/include/ScintillaCall.h
index 7f8088748..1a1a1e0f2 100644
--- a/include/ScintillaCall.h
+++ b/include/ScintillaCall.h
@@ -765,6 +765,7 @@ public:
void ClearSelections();
void SetSelection(Position caret, Position anchor);
void AddSelection(Position caret, Position anchor);
+ int SelectionFromPoint(int x, int y);
void DropSelectionN(int selection);
void SetMainSelection(int selection);
int MainSelection();
diff --git a/include/ScintillaMessages.h b/include/ScintillaMessages.h
index eaf162ae5..4b9421242 100644
--- a/include/ScintillaMessages.h
+++ b/include/ScintillaMessages.h
@@ -678,6 +678,7 @@ enum class Message {
ClearSelections = 2571,
SetSelection = 2572,
AddSelection = 2573,
+ SelectionFromPoint = 2474,
DropSelectionN = 2671,
SetMainSelection = 2574,
GetMainSelection = 2575,
diff --git a/src/Editor.cxx b/src/Editor.cxx
index 627179876..527c9b47c 100644
--- a/src/Editor.cxx
+++ b/src/Editor.cxx
@@ -4511,6 +4511,28 @@ bool Editor::PointInSelection(Point pt) {
return false;
}
+ptrdiff_t Editor::SelectionFromPoint(Point pt) {
+ // Prioritize checking inside non-empty selections since each character will be inside only 1
+ const SelectionPosition posChar = SPositionFromLocation(pt, true, true);
+ for (size_t r = 0; r < sel.Count(); r++) {
+ if (sel.Range(r).ContainsCharacter(posChar)) {
+ return r;
+ }
+ }
+
+ // Then check if near empty selections as may be near more than 1
+ const SelectionPosition pos = SPositionFromLocation(pt, true, false);
+ for (size_t r = 0; r < sel.Count(); r++) {
+ const SelectionRange &range = sel.Range(r);
+ if ((range.Empty()) && (pos == range.caret)) {
+ return r;
+ }
+ }
+
+ // No selection at point
+ return -1;
+}
+
bool Editor::PointInSelMargin(Point pt) const {
// Really means: "Point in a margin"
if (vs.fixedColumnWidth > 0) { // There is a margin
@@ -8676,6 +8698,9 @@ sptr_t Editor::WndProc(Message iMessage, uptr_t wParam, sptr_t lParam) {
Redraw();
break;
+ case Message::SelectionFromPoint:
+ return SelectionFromPoint(PointFromParameters(wParam, lParam));
+
case Message::DropSelectionN:
sel.DropSelection(wParam);
ContainerNeedsUpdate(Update::Selection);
diff --git a/src/Editor.h b/src/Editor.h
index e7e198aec..19b0c730c 100644
--- a/src/Editor.h
+++ b/src/Editor.h
@@ -527,6 +527,7 @@ protected: // ScintillaBase subclass needs access to much of Editor
/** PositionInSelection returns true if position in selection. */
bool PositionInSelection(Sci::Position pos);
bool PointInSelection(Point pt);
+ ptrdiff_t SelectionFromPoint(Point pt);
bool PointInSelMargin(Point pt) const;
Window::Cursor GetMarginCursor(Point pt) const noexcept;
void TrimAndSetSelection(Sci::Position currentPos_, Sci::Position anchor_);
diff --git a/src/Selection.cxx b/src/Selection.cxx
index fbdc474eb..53559e329 100644
--- a/src/Selection.cxx
+++ b/src/Selection.cxx
@@ -123,6 +123,13 @@ bool SelectionRange::ContainsCharacter(Sci::Position posCharacter) const noexcep
return (posCharacter >= anchor.Position()) && (posCharacter < caret.Position());
}
+bool SelectionRange::ContainsCharacter(SelectionPosition spCharacter) const noexcept {
+ if (anchor > caret)
+ return (spCharacter >= caret) && (spCharacter < anchor);
+ else
+ return (spCharacter >= anchor) && (spCharacter < caret);
+}
+
SelectionSegment SelectionRange::Intersect(SelectionSegment check) const noexcept {
const SelectionSegment inOrder(caret, anchor);
if ((inOrder.start <= check.end) || (inOrder.end >= check.start)) {
diff --git a/src/Selection.h b/src/Selection.h
index 30e1e27ad..03ce5083c 100644
--- a/src/Selection.h
+++ b/src/Selection.h
@@ -129,6 +129,7 @@ struct SelectionRange {
bool Contains(Sci::Position pos) const noexcept;
bool Contains(SelectionPosition sp) const noexcept;
bool ContainsCharacter(Sci::Position posCharacter) const noexcept;
+ bool ContainsCharacter(SelectionPosition spCharacter) const noexcept;
SelectionSegment Intersect(SelectionSegment check) const noexcept;
SelectionPosition Start() const noexcept {
return (anchor < caret) ? anchor : caret;
diff --git a/test/simpleTests.py b/test/simpleTests.py
index fd1f7423a..4263ad036 100644
--- a/test/simpleTests.py
+++ b/test/simpleTests.py
@@ -809,11 +809,44 @@ class TestSimple(unittest.TestCase):
self.assertEqual(self.ed.TargetEndVirtualSpace, 0)
def testPointsAndPositions(self):
- self.ed.AddText(1, b"x")
+ self.ed.SetContents(b"xyz")
+
+ # Inter-character positions
# Start of text
- self.assertEqual(self.ed.PositionFromPoint(0,0), 0)
+ self.assertEqual(self.ed.PositionFromPoint(1,1), 0)
# End of text
- self.assertEqual(self.ed.PositionFromPoint(0,100), 1)
+ self.assertEqual(self.ed.PositionFromPoint(100, 1), 3)
+ self.assertEqual(self.ed.PositionFromPointClose(100, 1), -1)
+
+ # Character positions
+ # Start of text
+ self.assertEqual(self.ed.CharPositionFromPoint(0,0), 0)
+ # End of text
+ self.assertEqual(self.ed.CharPositionFromPoint(100, 0), 3)
+ self.assertEqual(self.ed.CharPositionFromPointClose(100, 0), -1)
+
+ def testSelectionFromPoint(self):
+ self.ed.SetContents(b"xxxxxx")
+ self.ed.SetSelection(3, 2)
+ self.ed.AddSelection(0, 1)
+ self.ed.AddSelection(5, 5) # Empty
+ xStart = self.ed.PointXFromPosition(0, 0)
+ xEnd = self.ed.PointXFromPosition(0, 5)
+ width = xEnd-xStart
+ charWidth = width // 5
+ widthMid = charWidth // 2
+
+ posMid1 = xStart+widthMid
+ self.assertEqual(self.ed.SelectionFromPoint(posMid1, 1), 1)
+ posMid2 = xStart+charWidth+widthMid
+ self.assertEqual(self.ed.SelectionFromPoint(posMid2, 1), -1)
+ posMid3 = xStart+charWidth*2+widthMid
+ self.assertEqual(self.ed.SelectionFromPoint(posMid3, 1), 0)
+ # Empty selection at 5. Exact and then a few pixels either side
+ self.assertEqual(self.ed.SelectionFromPoint(xEnd, 1), 2)
+ self.assertEqual(self.ed.SelectionFromPoint(xEnd-2, 1), 2)
+ self.assertEqual(self.ed.SelectionFromPoint(xEnd+2, 1), 2)
+ self.assertEqual(self.ed.SelectionFromPoint(100, 0), -1)
def testLinePositions(self):
text = b"ab\ncd\nef"
--
cgit v1.2.3