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 | |
23 | using namespace Scintilla::Internal; |
24 | |
25 | void 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 | |
55 | bool 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 | |
62 | bool 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 | |
69 | bool 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 | |
76 | bool 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 | |
83 | Sci::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 | |
91 | void 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 | |
105 | bool 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 | |
112 | bool 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 | |
119 | bool 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 | |
126 | SelectionSegment 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 | |
143 | void SelectionRange::Swap() noexcept { |
144 | std::swap(caret, anchor); |
145 | } |
146 | |
147 | bool 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 |
183 | void 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 | |
193 | Selection::Selection() : mainRange(0), moveExtends(false), tentativeMain(false), selType(SelTypes::stream) { |
194 | AddSelection(SelectionRange(SelectionPosition(0))); |
195 | } |
196 | |
197 | bool Selection::IsRectangular() const noexcept { |
198 | return (selType == SelTypes::rectangle) || (selType == SelTypes::thin); |
199 | } |
200 | |
201 | Sci::Position Selection::MainCaret() const noexcept { |
202 | return ranges[mainRange].caret.Position(); |
203 | } |
204 | |
205 | Sci::Position Selection::MainAnchor() const noexcept { |
206 | return ranges[mainRange].anchor.Position(); |
207 | } |
208 | |
209 | SelectionRange &Selection::Rectangular() noexcept { |
210 | return rangeRectangular; |
211 | } |
212 | |
213 | SelectionSegment 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 | |
226 | SelectionSegment Selection::LimitsForRectangularElseMain() const { |
227 | if (IsRectangular()) { |
228 | return Limits(); |
229 | } else { |
230 | return SelectionSegment(ranges[mainRange].caret, ranges[mainRange].anchor); |
231 | } |
232 | } |
233 | |
234 | size_t Selection::Count() const noexcept { |
235 | return ranges.size(); |
236 | } |
237 | |
238 | size_t Selection::Main() const noexcept { |
239 | return mainRange; |
240 | } |
241 | |
242 | void Selection::SetMain(size_t r) noexcept { |
243 | PLATFORM_ASSERT(r < ranges.size()); |
244 | mainRange = r; |
245 | } |
246 | |
247 | SelectionRange &Selection::Range(size_t r) noexcept { |
248 | return ranges[r]; |
249 | } |
250 | |
251 | const SelectionRange &Selection::Range(size_t r) const noexcept { |
252 | return ranges[r]; |
253 | } |
254 | |
255 | SelectionRange &Selection::RangeMain() noexcept { |
256 | return ranges[mainRange]; |
257 | } |
258 | |
259 | const SelectionRange &Selection::RangeMain() const noexcept { |
260 | return ranges[mainRange]; |
261 | } |
262 | |
263 | SelectionPosition Selection::Start() const noexcept { |
264 | if (IsRectangular()) { |
265 | return rangeRectangular.Start(); |
266 | } else { |
267 | return ranges[mainRange].Start(); |
268 | } |
269 | } |
270 | |
271 | bool Selection::MoveExtends() const noexcept { |
272 | return moveExtends; |
273 | } |
274 | |
275 | void Selection::SetMoveExtends(bool moveExtends_) noexcept { |
276 | moveExtends = moveExtends_; |
277 | } |
278 | |
279 | bool Selection::Empty() const noexcept { |
280 | for (const SelectionRange &range : ranges) { |
281 | if (!range.Empty()) |
282 | return false; |
283 | } |
284 | return true; |
285 | } |
286 | |
287 | SelectionPosition 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 | |
298 | Sci::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 | |
306 | void 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 | |
315 | void 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 | |
331 | void 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 | |
339 | void Selection::SetSelection(SelectionRange range) { |
340 | ranges.clear(); |
341 | ranges.push_back(range); |
342 | mainRange = ranges.size() - 1; |
343 | } |
344 | |
345 | void Selection::AddSelection(SelectionRange range) { |
346 | TrimSelection(range); |
347 | ranges.push_back(range); |
348 | mainRange = ranges.size() - 1; |
349 | } |
350 | |
351 | void Selection::AddSelectionWithoutTrim(SelectionRange range) { |
352 | ranges.push_back(range); |
353 | mainRange = ranges.size() - 1; |
354 | } |
355 | |
356 | void 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 | |
371 | void Selection::DropAdditionalRanges() { |
372 | SetSelection(RangeMain()); |
373 | } |
374 | |
375 | void 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 | |
385 | void Selection::CommitTentative() noexcept { |
386 | rangesSaved.clear(); |
387 | tentativeMain = false; |
388 | } |
389 | |
390 | InSelection Selection::RangeType(size_t r) const noexcept { |
391 | return r == Main() ? InSelection::inMain : InSelection::inAdditional; |
392 | } |
393 | |
394 | InSelection 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 | |
402 | InSelection 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 | |
410 | 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 | |
421 | void 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 | |
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 | |
448 | void Selection::RotateMain() noexcept { |
449 | mainRange = (mainRange + 1) % ranges.size(); |
450 | } |
451 | |
452 | |