1 // Scintilla source code edit control
2 /** @file Selection.cxx
3 ** Classes maintaining the selection.
4 **/
5 // Copyright 2009 by Neil Hodgson <[email protected]>
6 // The License.txt file describes the conditions under which this software may be distributed.
7
8 #include <cstddef>
9 #include <cstdlib>
10
11 #include <stdexcept>
12 #include <string_view>
13 #include <vector>
14 #include <algorithm>
15 #include <memory>
16
17 #include "Platform.h"
18
19 #include "Scintilla.h"
20
21 #include "Position.h"
22 #include "Selection.h"
23
24 using namespace Scintilla;
25
MoveForInsertDelete(bool insertion,Sci::Position startChange,Sci::Position length,bool moveForEqual)26 void SelectionPosition::MoveForInsertDelete(bool insertion, Sci::Position startChange, Sci::Position length, bool moveForEqual) noexcept {
27 if (insertion) {
28 if (position == startChange) {
29 // Always consume virtual space
30 const Sci::Position virtualLengthRemove = std::min(length, virtualSpace);
31 virtualSpace -= virtualLengthRemove;
32 position += virtualLengthRemove;
33 if (moveForEqual) {
34 const Sci::Position lengthAfterVirtualRemove = length - virtualLengthRemove;
35 position += lengthAfterVirtualRemove;
36 }
37 } else if (position > startChange) {
38 position += length;
39 }
40 } else {
41 if (position == startChange) {
42 virtualSpace = 0;
43 }
44 if (position > startChange) {
45 const Sci::Position endDeletion = startChange + length;
46 if (position > endDeletion) {
47 position -= length;
48 } else {
49 position = startChange;
50 virtualSpace = 0;
51 }
52 }
53 }
54 }
55
operator <(const SelectionPosition & other) const56 bool SelectionPosition::operator <(const SelectionPosition &other) const noexcept {
57 if (position == other.position)
58 return virtualSpace < other.virtualSpace;
59 else
60 return position < other.position;
61 }
62
operator >(const SelectionPosition & other) const63 bool SelectionPosition::operator >(const SelectionPosition &other) const noexcept {
64 if (position == other.position)
65 return virtualSpace > other.virtualSpace;
66 else
67 return position > other.position;
68 }
69
operator <=(const SelectionPosition & other) const70 bool SelectionPosition::operator <=(const SelectionPosition &other) const noexcept {
71 if (position == other.position && virtualSpace == other.virtualSpace)
72 return true;
73 else
74 return other > *this;
75 }
76
operator >=(const SelectionPosition & other) const77 bool SelectionPosition::operator >=(const SelectionPosition &other) const noexcept {
78 if (position == other.position && virtualSpace == other.virtualSpace)
79 return true;
80 else
81 return *this > other;
82 }
83
Length() const84 Sci::Position SelectionRange::Length() const noexcept {
85 if (anchor > caret) {
86 return anchor.Position() - caret.Position();
87 } else {
88 return caret.Position() - anchor.Position();
89 }
90 }
91
MoveForInsertDelete(bool insertion,Sci::Position startChange,Sci::Position length)92 void SelectionRange::MoveForInsertDelete(bool insertion, Sci::Position startChange, Sci::Position length) noexcept {
93 // For insertions that occur at the start of the selection move both the start
94 // and end of the selection to preserve the selected length.
95 // The end will automatically move since it is after the insertion, so determine
96 // which position is the start and pass this into
97 // SelectionPosition::MoveForInsertDelete.
98 // There isn't any reason to move an empty selection so don't move it.
99 const bool caretStart = caret.Position() < anchor.Position();
100 const bool anchorStart = anchor.Position() < caret.Position();
101
102 caret.MoveForInsertDelete(insertion, startChange, length, caretStart);
103 anchor.MoveForInsertDelete(insertion, startChange, length, anchorStart);
104 }
105
Contains(Sci::Position pos) const106 bool SelectionRange::Contains(Sci::Position pos) const noexcept {
107 if (anchor > caret)
108 return (pos >= caret.Position()) && (pos <= anchor.Position());
109 else
110 return (pos >= anchor.Position()) && (pos <= caret.Position());
111 }
112
Contains(SelectionPosition sp) const113 bool SelectionRange::Contains(SelectionPosition sp) const noexcept {
114 if (anchor > caret)
115 return (sp >= caret) && (sp <= anchor);
116 else
117 return (sp >= anchor) && (sp <= caret);
118 }
119
ContainsCharacter(Sci::Position posCharacter) const120 bool SelectionRange::ContainsCharacter(Sci::Position posCharacter) const noexcept {
121 if (anchor > caret)
122 return (posCharacter >= caret.Position()) && (posCharacter < anchor.Position());
123 else
124 return (posCharacter >= anchor.Position()) && (posCharacter < caret.Position());
125 }
126
Intersect(SelectionSegment check) const127 SelectionSegment SelectionRange::Intersect(SelectionSegment check) const noexcept {
128 const SelectionSegment inOrder(caret, anchor);
129 if ((inOrder.start <= check.end) || (inOrder.end >= check.start)) {
130 SelectionSegment portion = check;
131 if (portion.start < inOrder.start)
132 portion.start = inOrder.start;
133 if (portion.end > inOrder.end)
134 portion.end = inOrder.end;
135 if (portion.start > portion.end)
136 return SelectionSegment();
137 else
138 return portion;
139 } else {
140 return SelectionSegment();
141 }
142 }
143
Swap()144 void SelectionRange::Swap() noexcept {
145 std::swap(caret, anchor);
146 }
147
Trim(SelectionRange range)148 bool SelectionRange::Trim(SelectionRange range) noexcept {
149 const SelectionPosition startRange = range.Start();
150 const SelectionPosition endRange = range.End();
151 SelectionPosition start = Start();
152 SelectionPosition end = End();
153 PLATFORM_ASSERT(start <= end);
154 PLATFORM_ASSERT(startRange <= endRange);
155 if ((startRange <= end) && (endRange >= start)) {
156 if ((start > startRange) && (end < endRange)) {
157 // Completely covered by range -> empty at start
158 end = start;
159 } else if ((start < startRange) && (end > endRange)) {
160 // Completely covers range -> empty at start
161 end = start;
162 } else if (start <= startRange) {
163 // Trim end
164 end = startRange;
165 } else { //
166 PLATFORM_ASSERT(end >= endRange);
167 // Trim start
168 start = endRange;
169 }
170 if (anchor > caret) {
171 caret = start;
172 anchor = end;
173 } else {
174 anchor = start;
175 caret = end;
176 }
177 return Empty();
178 } else {
179 return false;
180 }
181 }
182
183 // If range is all virtual collapse to start of virtual space
MinimizeVirtualSpace()184 void SelectionRange::MinimizeVirtualSpace() noexcept {
185 if (caret.Position() == anchor.Position()) {
186 Sci::Position virtualSpace = caret.VirtualSpace();
187 if (virtualSpace > anchor.VirtualSpace())
188 virtualSpace = anchor.VirtualSpace();
189 caret.SetVirtualSpace(virtualSpace);
190 anchor.SetVirtualSpace(virtualSpace);
191 }
192 }
193
Selection()194 Selection::Selection() : mainRange(0), moveExtends(false), tentativeMain(false), selType(selStream) {
195 AddSelection(SelectionRange(SelectionPosition(0)));
196 }
197
~Selection()198 Selection::~Selection() {
199 }
200
IsRectangular() const201 bool Selection::IsRectangular() const noexcept {
202 return (selType == selRectangle) || (selType == selThin);
203 }
204
MainCaret() const205 Sci::Position Selection::MainCaret() const noexcept {
206 return ranges[mainRange].caret.Position();
207 }
208
MainAnchor() const209 Sci::Position Selection::MainAnchor() const noexcept {
210 return ranges[mainRange].anchor.Position();
211 }
212
Rectangular()213 SelectionRange &Selection::Rectangular() noexcept {
214 return rangeRectangular;
215 }
216
Limits() const217 SelectionSegment Selection::Limits() const noexcept {
218 if (ranges.empty()) {
219 return SelectionSegment();
220 } else {
221 SelectionSegment sr(ranges[0].anchor, ranges[0].caret);
222 for (size_t i=1; i<ranges.size(); i++) {
223 sr.Extend(ranges[i].anchor);
224 sr.Extend(ranges[i].caret);
225 }
226 return sr;
227 }
228 }
229
LimitsForRectangularElseMain() const230 SelectionSegment Selection::LimitsForRectangularElseMain() const {
231 if (IsRectangular()) {
232 return Limits();
233 } else {
234 return SelectionSegment(ranges[mainRange].caret, ranges[mainRange].anchor);
235 }
236 }
237
Count() const238 size_t Selection::Count() const noexcept {
239 return ranges.size();
240 }
241
Main() const242 size_t Selection::Main() const noexcept {
243 return mainRange;
244 }
245
SetMain(size_t r)246 void Selection::SetMain(size_t r) noexcept {
247 PLATFORM_ASSERT(r < ranges.size());
248 mainRange = r;
249 }
250
Range(size_t r)251 SelectionRange &Selection::Range(size_t r) noexcept {
252 return ranges[r];
253 }
254
Range(size_t r) const255 const SelectionRange &Selection::Range(size_t r) const noexcept {
256 return ranges[r];
257 }
258
RangeMain()259 SelectionRange &Selection::RangeMain() noexcept {
260 return ranges[mainRange];
261 }
262
RangeMain() const263 const SelectionRange &Selection::RangeMain() const noexcept {
264 return ranges[mainRange];
265 }
266
Start() const267 SelectionPosition Selection::Start() const noexcept {
268 if (IsRectangular()) {
269 return rangeRectangular.Start();
270 } else {
271 return ranges[mainRange].Start();
272 }
273 }
274
MoveExtends() const275 bool Selection::MoveExtends() const noexcept {
276 return moveExtends;
277 }
278
SetMoveExtends(bool moveExtends_)279 void Selection::SetMoveExtends(bool moveExtends_) noexcept {
280 moveExtends = moveExtends_;
281 }
282
Empty() const283 bool Selection::Empty() const noexcept {
284 for (const SelectionRange &range : ranges) {
285 if (!range.Empty())
286 return false;
287 }
288 return true;
289 }
290
Last() const291 SelectionPosition Selection::Last() const noexcept {
292 SelectionPosition lastPosition;
293 for (const SelectionRange &range : ranges) {
294 if (lastPosition < range.caret)
295 lastPosition = range.caret;
296 if (lastPosition < range.anchor)
297 lastPosition = range.anchor;
298 }
299 return lastPosition;
300 }
301
Length() const302 Sci::Position Selection::Length() const noexcept {
303 Sci::Position len = 0;
304 for (const SelectionRange &range : ranges) {
305 len += range.Length();
306 }
307 return len;
308 }
309
MovePositions(bool insertion,Sci::Position startChange,Sci::Position length)310 void Selection::MovePositions(bool insertion, Sci::Position startChange, Sci::Position length) noexcept {
311 for (SelectionRange &range : ranges) {
312 range.MoveForInsertDelete(insertion, startChange, length);
313 }
314 if (selType == selRectangle) {
315 rangeRectangular.MoveForInsertDelete(insertion, startChange, length);
316 }
317 }
318
TrimSelection(SelectionRange range)319 void Selection::TrimSelection(SelectionRange range) noexcept {
320 for (size_t i=0; i<ranges.size();) {
321 if ((i != mainRange) && (ranges[i].Trim(range))) {
322 // Trimmed to empty so remove
323 for (size_t j=i; j<ranges.size()-1; j++) {
324 ranges[j] = ranges[j+1];
325 if (j == mainRange-1)
326 mainRange--;
327 }
328 ranges.pop_back();
329 } else {
330 i++;
331 }
332 }
333 }
334
TrimOtherSelections(size_t r,SelectionRange range)335 void Selection::TrimOtherSelections(size_t r, SelectionRange range) noexcept {
336 for (size_t i = 0; i<ranges.size(); ++i) {
337 if (i != r) {
338 ranges[i].Trim(range);
339 }
340 }
341 }
342
SetSelection(SelectionRange range)343 void Selection::SetSelection(SelectionRange range) {
344 ranges.clear();
345 ranges.push_back(range);
346 mainRange = ranges.size() - 1;
347 }
348
AddSelection(SelectionRange range)349 void Selection::AddSelection(SelectionRange range) {
350 TrimSelection(range);
351 ranges.push_back(range);
352 mainRange = ranges.size() - 1;
353 }
354
AddSelectionWithoutTrim(SelectionRange range)355 void Selection::AddSelectionWithoutTrim(SelectionRange range) {
356 ranges.push_back(range);
357 mainRange = ranges.size() - 1;
358 }
359
DropSelection(size_t r)360 void Selection::DropSelection(size_t r) {
361 if ((ranges.size() > 1) && (r < ranges.size())) {
362 size_t mainNew = mainRange;
363 if (mainNew >= r) {
364 if (mainNew == 0) {
365 mainNew = ranges.size() - 2;
366 } else {
367 mainNew--;
368 }
369 }
370 ranges.erase(ranges.begin() + r);
371 mainRange = mainNew;
372 }
373 }
374
DropAdditionalRanges()375 void Selection::DropAdditionalRanges() {
376 SetSelection(RangeMain());
377 }
378
TentativeSelection(SelectionRange range)379 void Selection::TentativeSelection(SelectionRange range) {
380 if (!tentativeMain) {
381 rangesSaved = ranges;
382 }
383 ranges = rangesSaved;
384 AddSelection(range);
385 TrimSelection(ranges[mainRange]);
386 tentativeMain = true;
387 }
388
CommitTentative()389 void Selection::CommitTentative() noexcept {
390 rangesSaved.clear();
391 tentativeMain = false;
392 }
393
CharacterInSelection(Sci::Position posCharacter) const394 int Selection::CharacterInSelection(Sci::Position posCharacter) const noexcept {
395 for (size_t i=0; i<ranges.size(); i++) {
396 if (ranges[i].ContainsCharacter(posCharacter))
397 return i == mainRange ? 1 : 2;
398 }
399 return 0;
400 }
401
InSelectionForEOL(Sci::Position pos) const402 int Selection::InSelectionForEOL(Sci::Position pos) const noexcept {
403 for (size_t i=0; i<ranges.size(); i++) {
404 if (!ranges[i].Empty() && (pos > ranges[i].Start().Position()) && (pos <= ranges[i].End().Position()))
405 return i == mainRange ? 1 : 2;
406 }
407 return 0;
408 }
409
VirtualSpaceFor(Sci::Position pos) const410 Sci::Position Selection::VirtualSpaceFor(Sci::Position pos) const noexcept {
411 Sci::Position virtualSpace = 0;
412 for (const SelectionRange &range : ranges) {
413 if ((range.caret.Position() == pos) && (virtualSpace < range.caret.VirtualSpace()))
414 virtualSpace = range.caret.VirtualSpace();
415 if ((range.anchor.Position() == pos) && (virtualSpace < range.anchor.VirtualSpace()))
416 virtualSpace = range.anchor.VirtualSpace();
417 }
418 return virtualSpace;
419 }
420
Clear()421 void Selection::Clear() {
422 ranges.clear();
423 ranges.emplace_back();
424 mainRange = ranges.size() - 1;
425 selType = selStream;
426 moveExtends = false;
427 ranges[mainRange].Reset();
428 rangeRectangular.Reset();
429 }
430
RemoveDuplicates()431 void Selection::RemoveDuplicates() {
432 for (size_t i=0; i<ranges.size()-1; i++) {
433 if (ranges[i].Empty()) {
434 size_t j=i+1;
435 while (j<ranges.size()) {
436 if (ranges[i] == ranges[j]) {
437 ranges.erase(ranges.begin() + j);
438 if (mainRange >= j)
439 mainRange--;
440 } else {
441 j++;
442 }
443 }
444 }
445 }
446 }
447
RotateMain()448 void Selection::RotateMain() noexcept {
449 mainRange = (mainRange + 1) % ranges.size();
450 }
451
452