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 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 56 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 63 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 70 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 77 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 84 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 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 106 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 113 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 120 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 127 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 144 void SelectionRange::Swap() noexcept { 145 std::swap(caret, anchor); 146 } 147 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 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 194 Selection::Selection() : mainRange(0), moveExtends(false), tentativeMain(false), selType(selStream) { 195 AddSelection(SelectionRange(SelectionPosition(0))); 196 } 197 198 Selection::~Selection() { 199 } 200 201 bool Selection::IsRectangular() const noexcept { 202 return (selType == selRectangle) || (selType == selThin); 203 } 204 205 Sci::Position Selection::MainCaret() const noexcept { 206 return ranges[mainRange].caret.Position(); 207 } 208 209 Sci::Position Selection::MainAnchor() const noexcept { 210 return ranges[mainRange].anchor.Position(); 211 } 212 213 SelectionRange &Selection::Rectangular() noexcept { 214 return rangeRectangular; 215 } 216 217 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 230 SelectionSegment Selection::LimitsForRectangularElseMain() const { 231 if (IsRectangular()) { 232 return Limits(); 233 } else { 234 return SelectionSegment(ranges[mainRange].caret, ranges[mainRange].anchor); 235 } 236 } 237 238 size_t Selection::Count() const noexcept { 239 return ranges.size(); 240 } 241 242 size_t Selection::Main() const noexcept { 243 return mainRange; 244 } 245 246 void Selection::SetMain(size_t r) noexcept { 247 PLATFORM_ASSERT(r < ranges.size()); 248 mainRange = r; 249 } 250 251 SelectionRange &Selection::Range(size_t r) noexcept { 252 return ranges[r]; 253 } 254 255 const SelectionRange &Selection::Range(size_t r) const noexcept { 256 return ranges[r]; 257 } 258 259 SelectionRange &Selection::RangeMain() noexcept { 260 return ranges[mainRange]; 261 } 262 263 const SelectionRange &Selection::RangeMain() const noexcept { 264 return ranges[mainRange]; 265 } 266 267 SelectionPosition Selection::Start() const noexcept { 268 if (IsRectangular()) { 269 return rangeRectangular.Start(); 270 } else { 271 return ranges[mainRange].Start(); 272 } 273 } 274 275 bool Selection::MoveExtends() const noexcept { 276 return moveExtends; 277 } 278 279 void Selection::SetMoveExtends(bool moveExtends_) noexcept { 280 moveExtends = moveExtends_; 281 } 282 283 bool Selection::Empty() const noexcept { 284 for (const SelectionRange &range : ranges) { 285 if (!range.Empty()) 286 return false; 287 } 288 return true; 289 } 290 291 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 302 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 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 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 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 343 void Selection::SetSelection(SelectionRange range) { 344 ranges.clear(); 345 ranges.push_back(range); 346 mainRange = ranges.size() - 1; 347 } 348 349 void Selection::AddSelection(SelectionRange range) { 350 TrimSelection(range); 351 ranges.push_back(range); 352 mainRange = ranges.size() - 1; 353 } 354 355 void Selection::AddSelectionWithoutTrim(SelectionRange range) { 356 ranges.push_back(range); 357 mainRange = ranges.size() - 1; 358 } 359 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 375 void Selection::DropAdditionalRanges() { 376 SetSelection(RangeMain()); 377 } 378 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 389 void Selection::CommitTentative() noexcept { 390 rangesSaved.clear(); 391 tentativeMain = false; 392 } 393 394 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 402 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 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 = selStream; 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