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