1 // Scintilla source code edit control 2 /** @file CellBuffer.cxx 3 ** Manages a buffer of cells. 4 **/ 5 // Copyright 1998-2001 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 #include <cassert> 11 #include <cstring> 12 #include <cstdio> 13 #include <cstdarg> 14 15 #include <stdexcept> 16 #include <string> 17 #include <string_view> 18 #include <vector> 19 #include <algorithm> 20 #include <memory> 21 22 #include "Platform.h" 23 24 #include "Scintilla.h" 25 #include "Position.h" 26 #include "SplitVector.h" 27 #include "Partitioning.h" 28 #include "CellBuffer.h" 29 #include "UniConversion.h" 30 31 namespace Scintilla { 32 33 struct CountWidths { 34 // Measures the number of characters in a string divided into those 35 // from the Base Multilingual Plane and those from other planes. 36 Sci::Position countBasePlane; 37 Sci::Position countOtherPlanes; 38 CountWidths(Sci::Position countBasePlane_=0, Sci::Position countOtherPlanes_=0) noexcept : 39 countBasePlane(countBasePlane_), 40 countOtherPlanes(countOtherPlanes_) { 41 } 42 CountWidths operator-() const noexcept { 43 return CountWidths(-countBasePlane , -countOtherPlanes); 44 } 45 Sci::Position WidthUTF32() const noexcept { 46 // All code points take one code unit in UTF-32. 47 return countBasePlane + countOtherPlanes; 48 } 49 Sci::Position WidthUTF16() const noexcept { 50 // UTF-16 takes 2 code units for other planes 51 return countBasePlane + 2 * countOtherPlanes; 52 } 53 void CountChar(int lenChar) noexcept { 54 if (lenChar == 4) { 55 countOtherPlanes++; 56 } else { 57 countBasePlane++; 58 } 59 } 60 }; 61 62 class ILineVector { 63 public: 64 virtual void Init() = 0; 65 virtual void SetPerLine(PerLine *pl) noexcept = 0; 66 virtual void InsertText(Sci::Line line, Sci::Position delta) noexcept = 0; 67 virtual void InsertLine(Sci::Line line, Sci::Position position, bool lineStart) = 0; 68 virtual void InsertLines(Sci::Line line, const Sci::Position *positions, size_t lines, bool lineStart) = 0; 69 virtual void SetLineStart(Sci::Line line, Sci::Position position) noexcept = 0; 70 virtual void RemoveLine(Sci::Line line) = 0; 71 virtual Sci::Line Lines() const noexcept = 0; 72 virtual Sci::Line LineFromPosition(Sci::Position pos) const noexcept = 0; 73 virtual Sci::Position LineStart(Sci::Line line) const noexcept = 0; 74 virtual void InsertCharacters(Sci::Line line, CountWidths delta) noexcept = 0; 75 virtual void SetLineCharactersWidth(Sci::Line line, CountWidths width) noexcept = 0; 76 virtual int LineCharacterIndex() const noexcept = 0; 77 virtual bool AllocateLineCharacterIndex(int lineCharacterIndex, Sci::Line lines) = 0; 78 virtual bool ReleaseLineCharacterIndex(int lineCharacterIndex) = 0; 79 virtual Sci::Position IndexLineStart(Sci::Line line, int lineCharacterIndex) const noexcept = 0; 80 virtual Sci::Line LineFromPositionIndex(Sci::Position pos, int lineCharacterIndex) const noexcept = 0; 81 virtual ~ILineVector() {} 82 }; 83 84 } 85 86 using namespace Scintilla; 87 88 template <typename POS> 89 class LineStartIndex { 90 public: 91 int refCount; 92 Partitioning<POS> starts; 93 94 LineStartIndex() : refCount(0), starts(4) { 95 // Minimal initial allocation 96 } 97 // Deleted so LineStartIndex objects can not be copied. 98 LineStartIndex(const LineStartIndex &) = delete; 99 LineStartIndex(LineStartIndex &&) = delete; 100 void operator=(const LineStartIndex &) = delete; 101 void operator=(LineStartIndex &&) = delete; 102 virtual ~LineStartIndex() { 103 } 104 bool Allocate(Sci::Line lines) { 105 refCount++; 106 Sci::Position length = starts.PositionFromPartition(starts.Partitions()); 107 for (Sci::Line line = starts.Partitions(); line < lines; line++) { 108 // Produce an ascending sequence that will be filled in with correct widths later 109 length++; 110 starts.InsertPartition(static_cast<POS>(line), static_cast<POS>(length)); 111 } 112 return refCount == 1; 113 } 114 bool Release() { 115 if (refCount == 1) { 116 starts.DeleteAll(); 117 } 118 refCount--; 119 return refCount == 0; 120 } 121 bool Active() const noexcept { 122 return refCount > 0; 123 } 124 Sci::Position LineWidth(Sci::Line line) const noexcept { 125 return starts.PositionFromPartition(static_cast<POS>(line) + 1) - 126 starts.PositionFromPartition(static_cast<POS>(line)); 127 } 128 void SetLineWidth(Sci::Line line, Sci::Position width) noexcept { 129 const Sci::Position widthCurrent = LineWidth(line); 130 starts.InsertText(static_cast<POS>(line), static_cast<POS>(width - widthCurrent)); 131 } 132 void InsertLines(Sci::Line line, Sci::Line lines) { 133 // Insert multiple lines with each temporarily 1 character wide. 134 // The line widths will be fixed up by later measuring code. 135 const POS lineAsPos = static_cast<POS>(line); 136 const POS lineStart = starts.PositionFromPartition(lineAsPos - 1) + 1; 137 for (POS l = 0; l < static_cast<POS>(lines); l++) { 138 starts.InsertPartition(lineAsPos + l, lineStart + l); 139 } 140 } 141 }; 142 143 template <typename POS> 144 class LineVector : public ILineVector { 145 Partitioning<POS> starts; 146 PerLine *perLine; 147 LineStartIndex<POS> startsUTF16; 148 LineStartIndex<POS> startsUTF32; 149 int activeIndices; 150 151 void SetActiveIndices() noexcept { 152 activeIndices = (startsUTF32.Active() ? SC_LINECHARACTERINDEX_UTF32 : 0) 153 | (startsUTF16.Active() ? SC_LINECHARACTERINDEX_UTF16 : 0); 154 } 155 156 public: 157 LineVector() : starts(256), perLine(nullptr), activeIndices(0) { 158 } 159 // Deleted so LineVector objects can not be copied. 160 LineVector(const LineVector &) = delete; 161 LineVector(LineVector &&) = delete; 162 LineVector &operator=(const LineVector &) = delete; 163 LineVector &operator=(LineVector &&) = delete; 164 ~LineVector() override { 165 } 166 void Init() override { 167 starts.DeleteAll(); 168 if (perLine) { 169 perLine->Init(); 170 } 171 startsUTF32.starts.DeleteAll(); 172 startsUTF16.starts.DeleteAll(); 173 } 174 void SetPerLine(PerLine *pl) noexcept override { 175 perLine = pl; 176 } 177 void InsertText(Sci::Line line, Sci::Position delta) noexcept override { 178 starts.InsertText(static_cast<POS>(line), static_cast<POS>(delta)); 179 } 180 void InsertLine(Sci::Line line, Sci::Position position, bool lineStart) override { 181 const POS lineAsPos = static_cast<POS>(line); 182 starts.InsertPartition(lineAsPos, static_cast<POS>(position)); 183 if (activeIndices) { 184 if (activeIndices & SC_LINECHARACTERINDEX_UTF32) { 185 startsUTF32.InsertLines(line, 1); 186 } 187 if (activeIndices & SC_LINECHARACTERINDEX_UTF16) { 188 startsUTF16.InsertLines(line, 1); 189 } 190 } 191 if (perLine) { 192 if ((line > 0) && lineStart) 193 line--; 194 perLine->InsertLine(line); 195 } 196 } 197 void InsertLines(Sci::Line line, const Sci::Position *positions, size_t lines, bool lineStart) override { 198 const POS lineAsPos = static_cast<POS>(line); 199 if constexpr (sizeof(Sci::Position) == sizeof(POS)) { 200 starts.InsertPartitions(lineAsPos, positions, lines); 201 } else { 202 starts.InsertPartitionsWithCast(lineAsPos, positions, lines); 203 } 204 if (activeIndices) { 205 if (activeIndices & SC_LINECHARACTERINDEX_UTF32) { 206 startsUTF32.InsertLines(line, lines); 207 } 208 if (activeIndices & SC_LINECHARACTERINDEX_UTF16) { 209 startsUTF16.InsertLines(line, lines); 210 } 211 } 212 if (perLine) { 213 if ((line > 0) && lineStart) 214 line--; 215 perLine->InsertLines(line, lines); 216 } 217 } 218 void SetLineStart(Sci::Line line, Sci::Position position) noexcept override { 219 starts.SetPartitionStartPosition(static_cast<POS>(line), static_cast<POS>(position)); 220 } 221 void RemoveLine(Sci::Line line) override { 222 starts.RemovePartition(static_cast<POS>(line)); 223 if (activeIndices & SC_LINECHARACTERINDEX_UTF32) { 224 startsUTF32.starts.RemovePartition(static_cast<POS>(line)); 225 } 226 if (activeIndices & SC_LINECHARACTERINDEX_UTF16) { 227 startsUTF16.starts.RemovePartition(static_cast<POS>(line)); 228 } 229 if (perLine) { 230 perLine->RemoveLine(line); 231 } 232 } 233 Sci::Line Lines() const noexcept override { 234 return static_cast<Sci::Line>(starts.Partitions()); 235 } 236 Sci::Line LineFromPosition(Sci::Position pos) const noexcept override { 237 return static_cast<Sci::Line>(starts.PartitionFromPosition(static_cast<POS>(pos))); 238 } 239 Sci::Position LineStart(Sci::Line line) const noexcept override { 240 return starts.PositionFromPartition(static_cast<POS>(line)); 241 } 242 void InsertCharacters(Sci::Line line, CountWidths delta) noexcept override { 243 if (activeIndices & SC_LINECHARACTERINDEX_UTF32) { 244 startsUTF32.starts.InsertText(static_cast<POS>(line), static_cast<POS>(delta.WidthUTF32())); 245 } 246 if (activeIndices & SC_LINECHARACTERINDEX_UTF16) { 247 startsUTF16.starts.InsertText(static_cast<POS>(line), static_cast<POS>(delta.WidthUTF16())); 248 } 249 } 250 void SetLineCharactersWidth(Sci::Line line, CountWidths width) noexcept override { 251 if (activeIndices & SC_LINECHARACTERINDEX_UTF32) { 252 assert(startsUTF32.starts.Partitions() == starts.Partitions()); 253 startsUTF32.SetLineWidth(line, width.WidthUTF32()); 254 } 255 if (activeIndices & SC_LINECHARACTERINDEX_UTF16) { 256 assert(startsUTF16.starts.Partitions() == starts.Partitions()); 257 startsUTF16.SetLineWidth(line, width.WidthUTF16()); 258 } 259 } 260 261 int LineCharacterIndex() const noexcept override { 262 return activeIndices; 263 } 264 bool AllocateLineCharacterIndex(int lineCharacterIndex, Sci::Line lines) override { 265 const int activeIndicesStart = activeIndices; 266 if ((lineCharacterIndex & SC_LINECHARACTERINDEX_UTF32) != 0) { 267 startsUTF32.Allocate(lines); 268 assert(startsUTF32.starts.Partitions() == starts.Partitions()); 269 } 270 if ((lineCharacterIndex & SC_LINECHARACTERINDEX_UTF16) != 0) { 271 startsUTF16.Allocate(lines); 272 assert(startsUTF16.starts.Partitions() == starts.Partitions()); 273 } 274 SetActiveIndices(); 275 return activeIndicesStart != activeIndices; 276 } 277 bool ReleaseLineCharacterIndex(int lineCharacterIndex) override { 278 const int activeIndicesStart = activeIndices; 279 if ((lineCharacterIndex & SC_LINECHARACTERINDEX_UTF32) != 0) { 280 startsUTF32.Release(); 281 } 282 if ((lineCharacterIndex & SC_LINECHARACTERINDEX_UTF16) != 0) { 283 startsUTF16.Release(); 284 } 285 SetActiveIndices(); 286 return activeIndicesStart != activeIndices; 287 } 288 Sci::Position IndexLineStart(Sci::Line line, int lineCharacterIndex) const noexcept override { 289 if (lineCharacterIndex == SC_LINECHARACTERINDEX_UTF32) { 290 return startsUTF32.starts.PositionFromPartition(static_cast<POS>(line)); 291 } else { 292 return startsUTF16.starts.PositionFromPartition(static_cast<POS>(line)); 293 } 294 } 295 Sci::Line LineFromPositionIndex(Sci::Position pos, int lineCharacterIndex) const noexcept override { 296 if (lineCharacterIndex == SC_LINECHARACTERINDEX_UTF32) { 297 return static_cast<Sci::Line>(startsUTF32.starts.PartitionFromPosition(static_cast<POS>(pos))); 298 } else { 299 return static_cast<Sci::Line>(startsUTF16.starts.PartitionFromPosition(static_cast<POS>(pos))); 300 } 301 } 302 }; 303 304 Action::Action() noexcept { 305 at = startAction; 306 position = 0; 307 lenData = 0; 308 mayCoalesce = false; 309 } 310 311 Action::~Action() { 312 } 313 314 void Action::Create(actionType at_, Sci::Position position_, const char *data_, Sci::Position lenData_, bool mayCoalesce_) { 315 data = nullptr; 316 position = position_; 317 at = at_; 318 if (lenData_) { 319 data = std::make_unique<char[]>(lenData_); 320 memcpy(&data[0], data_, lenData_); 321 } 322 lenData = lenData_; 323 mayCoalesce = mayCoalesce_; 324 } 325 326 void Action::Clear() noexcept { 327 data = nullptr; 328 lenData = 0; 329 } 330 331 // The undo history stores a sequence of user operations that represent the user's view of the 332 // commands executed on the text. 333 // Each user operation contains a sequence of text insertion and text deletion actions. 334 // All the user operations are stored in a list of individual actions with 'start' actions used 335 // as delimiters between user operations. 336 // Initially there is one start action in the history. 337 // As each action is performed, it is recorded in the history. The action may either become 338 // part of the current user operation or may start a new user operation. If it is to be part of the 339 // current operation, then it overwrites the current last action. If it is to be part of a new 340 // operation, it is appended after the current last action. 341 // After writing the new action, a new start action is appended at the end of the history. 342 // The decision of whether to start a new user operation is based upon two factors. If a 343 // compound operation has been explicitly started by calling BeginUndoAction and no matching 344 // EndUndoAction (these calls nest) has been called, then the action is coalesced into the current 345 // operation. If there is no outstanding BeginUndoAction call then a new operation is started 346 // unless it looks as if the new action is caused by the user typing or deleting a stream of text. 347 // Sequences that look like typing or deletion are coalesced into a single user operation. 348 349 UndoHistory::UndoHistory() { 350 351 actions.resize(3); 352 maxAction = 0; 353 currentAction = 0; 354 undoSequenceDepth = 0; 355 savePoint = 0; 356 tentativePoint = -1; 357 358 actions[currentAction].Create(startAction); 359 } 360 361 UndoHistory::~UndoHistory() { 362 } 363 364 void UndoHistory::EnsureUndoRoom() { 365 // Have to test that there is room for 2 more actions in the array 366 // as two actions may be created by the calling function 367 if (static_cast<size_t>(currentAction) >= (actions.size() - 2)) { 368 // Run out of undo nodes so extend the array 369 actions.resize(actions.size() * 2); 370 } 371 } 372 373 const char *UndoHistory::AppendAction(actionType at, Sci::Position position, const char *data, Sci::Position lengthData, 374 bool &startSequence, bool mayCoalesce) { 375 EnsureUndoRoom(); 376 //Platform::DebugPrintf("%% %d action %d %d %d\n", at, position, lengthData, currentAction); 377 //Platform::DebugPrintf("^ %d action %d %d\n", actions[currentAction - 1].at, 378 // actions[currentAction - 1].position, actions[currentAction - 1].lenData); 379 if (currentAction < savePoint) { 380 savePoint = -1; 381 } 382 int oldCurrentAction = currentAction; 383 if (currentAction >= 1) { 384 if (0 == undoSequenceDepth) { 385 // Top level actions may not always be coalesced 386 int targetAct = -1; 387 const Action *actPrevious = &(actions[currentAction + targetAct]); 388 // Container actions may forward the coalesce state of Scintilla Actions. 389 while ((actPrevious->at == containerAction) && actPrevious->mayCoalesce) { 390 targetAct--; 391 actPrevious = &(actions[currentAction + targetAct]); 392 } 393 // See if current action can be coalesced into previous action 394 // Will work if both are inserts or deletes and position is same 395 if ((currentAction == savePoint) || (currentAction == tentativePoint)) { 396 currentAction++; 397 } else if (!actions[currentAction].mayCoalesce) { 398 // Not allowed to coalesce if this set 399 currentAction++; 400 } else if (!mayCoalesce || !actPrevious->mayCoalesce) { 401 currentAction++; 402 } else if (at == containerAction || actions[currentAction].at == containerAction) { 403 ; // A coalescible containerAction 404 } else if ((at != actPrevious->at) && (actPrevious->at != startAction)) { 405 currentAction++; 406 } else if ((at == insertAction) && 407 (position != (actPrevious->position + actPrevious->lenData))) { 408 // Insertions must be immediately after to coalesce 409 currentAction++; 410 } else if (at == removeAction) { 411 if ((lengthData == 1) || (lengthData == 2)) { 412 if ((position + lengthData) == actPrevious->position) { 413 ; // Backspace -> OK 414 } else if (position == actPrevious->position) { 415 ; // Delete -> OK 416 } else { 417 // Removals must be at same position to coalesce 418 currentAction++; 419 } 420 } else { 421 // Removals must be of one character to coalesce 422 currentAction++; 423 } 424 } else { 425 // Action coalesced. 426 } 427 428 } else { 429 // Actions not at top level are always coalesced unless this is after return to top level 430 if (!actions[currentAction].mayCoalesce) 431 currentAction++; 432 } 433 } else { 434 currentAction++; 435 } 436 startSequence = oldCurrentAction != currentAction; 437 const int actionWithData = currentAction; 438 actions[currentAction].Create(at, position, data, lengthData, mayCoalesce); 439 currentAction++; 440 actions[currentAction].Create(startAction); 441 maxAction = currentAction; 442 return actions[actionWithData].data.get(); 443 } 444 445 void UndoHistory::BeginUndoAction() { 446 EnsureUndoRoom(); 447 if (undoSequenceDepth == 0) { 448 if (actions[currentAction].at != startAction) { 449 currentAction++; 450 actions[currentAction].Create(startAction); 451 maxAction = currentAction; 452 } 453 actions[currentAction].mayCoalesce = false; 454 } 455 undoSequenceDepth++; 456 } 457 458 void UndoHistory::EndUndoAction() { 459 PLATFORM_ASSERT(undoSequenceDepth > 0); 460 EnsureUndoRoom(); 461 undoSequenceDepth--; 462 if (0 == undoSequenceDepth) { 463 if (actions[currentAction].at != startAction) { 464 currentAction++; 465 actions[currentAction].Create(startAction); 466 maxAction = currentAction; 467 } 468 actions[currentAction].mayCoalesce = false; 469 } 470 } 471 472 void UndoHistory::DropUndoSequence() { 473 undoSequenceDepth = 0; 474 } 475 476 void UndoHistory::DeleteUndoHistory() { 477 for (int i = 1; i < maxAction; i++) 478 actions[i].Clear(); 479 maxAction = 0; 480 currentAction = 0; 481 actions[currentAction].Create(startAction); 482 savePoint = 0; 483 tentativePoint = -1; 484 } 485 486 void UndoHistory::SetSavePoint() noexcept { 487 savePoint = currentAction; 488 } 489 490 bool UndoHistory::IsSavePoint() const noexcept { 491 return savePoint == currentAction; 492 } 493 494 void UndoHistory::TentativeStart() { 495 tentativePoint = currentAction; 496 } 497 498 void UndoHistory::TentativeCommit() { 499 tentativePoint = -1; 500 // Truncate undo history 501 maxAction = currentAction; 502 } 503 504 bool UndoHistory::TentativeActive() const noexcept { 505 return tentativePoint >= 0; 506 } 507 508 int UndoHistory::TentativeSteps() noexcept { 509 // Drop any trailing startAction 510 if (actions[currentAction].at == startAction && currentAction > 0) 511 currentAction--; 512 if (tentativePoint >= 0) 513 return currentAction - tentativePoint; 514 else 515 return -1; 516 } 517 518 bool UndoHistory::CanUndo() const noexcept { 519 return (currentAction > 0) && (maxAction > 0); 520 } 521 522 int UndoHistory::StartUndo() { 523 // Drop any trailing startAction 524 if (actions[currentAction].at == startAction && currentAction > 0) 525 currentAction--; 526 527 // Count the steps in this action 528 int act = currentAction; 529 while (actions[act].at != startAction && act > 0) { 530 act--; 531 } 532 return currentAction - act; 533 } 534 535 const Action &UndoHistory::GetUndoStep() const { 536 return actions[currentAction]; 537 } 538 539 void UndoHistory::CompletedUndoStep() { 540 currentAction--; 541 } 542 543 bool UndoHistory::CanRedo() const noexcept { 544 return maxAction > currentAction; 545 } 546 547 int UndoHistory::StartRedo() { 548 // Drop any leading startAction 549 if (currentAction < maxAction && actions[currentAction].at == startAction) 550 currentAction++; 551 552 // Count the steps in this action 553 int act = currentAction; 554 while (act < maxAction && actions[act].at != startAction) { 555 act++; 556 } 557 return act - currentAction; 558 } 559 560 const Action &UndoHistory::GetRedoStep() const { 561 return actions[currentAction]; 562 } 563 564 void UndoHistory::CompletedRedoStep() { 565 currentAction++; 566 } 567 568 CellBuffer::CellBuffer(bool hasStyles_, bool largeDocument_) : 569 hasStyles(hasStyles_), largeDocument(largeDocument_) { 570 readOnly = false; 571 utf8Substance = false; 572 utf8LineEnds = 0; 573 collectingUndo = true; 574 if (largeDocument) 575 plv = std::make_unique<LineVector<Sci::Position>>(); 576 else 577 plv = std::make_unique<LineVector<int>>(); 578 } 579 580 CellBuffer::~CellBuffer() { 581 } 582 583 char CellBuffer::CharAt(Sci::Position position) const noexcept { 584 return substance.ValueAt(position); 585 } 586 587 unsigned char CellBuffer::UCharAt(Sci::Position position) const noexcept { 588 return substance.ValueAt(position); 589 } 590 591 void CellBuffer::GetCharRange(char *buffer, Sci::Position position, Sci::Position lengthRetrieve) const { 592 if (lengthRetrieve <= 0) 593 return; 594 if (position < 0) 595 return; 596 if ((position + lengthRetrieve) > substance.Length()) { 597 Platform::DebugPrintf("Bad GetCharRange %.0f for %.0f of %.0f\n", 598 static_cast<double>(position), 599 static_cast<double>(lengthRetrieve), 600 static_cast<double>(substance.Length())); 601 return; 602 } 603 substance.GetRange(buffer, position, lengthRetrieve); 604 } 605 606 char CellBuffer::StyleAt(Sci::Position position) const noexcept { 607 return hasStyles ? style.ValueAt(position) : 0; 608 } 609 610 void CellBuffer::GetStyleRange(unsigned char *buffer, Sci::Position position, Sci::Position lengthRetrieve) const { 611 if (lengthRetrieve < 0) 612 return; 613 if (position < 0) 614 return; 615 if (!hasStyles) { 616 std::fill(buffer, buffer + lengthRetrieve, static_cast<unsigned char>(0)); 617 return; 618 } 619 if ((position + lengthRetrieve) > style.Length()) { 620 Platform::DebugPrintf("Bad GetStyleRange %.0f for %.0f of %.0f\n", 621 static_cast<double>(position), 622 static_cast<double>(lengthRetrieve), 623 static_cast<double>(style.Length())); 624 return; 625 } 626 style.GetRange(reinterpret_cast<char *>(buffer), position, lengthRetrieve); 627 } 628 629 const char *CellBuffer::BufferPointer() { 630 return substance.BufferPointer(); 631 } 632 633 const char *CellBuffer::RangePointer(Sci::Position position, Sci::Position rangeLength) noexcept { 634 return substance.RangePointer(position, rangeLength); 635 } 636 637 Sci::Position CellBuffer::GapPosition() const noexcept { 638 return substance.GapPosition(); 639 } 640 641 // The char* returned is to an allocation owned by the undo history 642 const char *CellBuffer::InsertString(Sci::Position position, const char *s, Sci::Position insertLength, bool &startSequence) { 643 // InsertString and DeleteChars are the bottleneck though which all changes occur 644 const char *data = s; 645 if (!readOnly) { 646 if (collectingUndo) { 647 // Save into the undo/redo stack, but only the characters - not the formatting 648 // This takes up about half load time 649 data = uh.AppendAction(insertAction, position, s, insertLength, startSequence); 650 } 651 652 BasicInsertString(position, s, insertLength); 653 } 654 return data; 655 } 656 657 bool CellBuffer::SetStyleAt(Sci::Position position, char styleValue) noexcept { 658 if (!hasStyles) { 659 return false; 660 } 661 const char curVal = style.ValueAt(position); 662 if (curVal != styleValue) { 663 style.SetValueAt(position, styleValue); 664 return true; 665 } else { 666 return false; 667 } 668 } 669 670 bool CellBuffer::SetStyleFor(Sci::Position position, Sci::Position lengthStyle, char styleValue) noexcept { 671 if (!hasStyles) { 672 return false; 673 } 674 bool changed = false; 675 PLATFORM_ASSERT(lengthStyle == 0 || 676 (lengthStyle > 0 && lengthStyle + position <= style.Length())); 677 while (lengthStyle--) { 678 const char curVal = style.ValueAt(position); 679 if (curVal != styleValue) { 680 style.SetValueAt(position, styleValue); 681 changed = true; 682 } 683 position++; 684 } 685 return changed; 686 } 687 688 // The char* returned is to an allocation owned by the undo history 689 const char *CellBuffer::DeleteChars(Sci::Position position, Sci::Position deleteLength, bool &startSequence) { 690 // InsertString and DeleteChars are the bottleneck though which all changes occur 691 PLATFORM_ASSERT(deleteLength > 0); 692 const char *data = nullptr; 693 if (!readOnly) { 694 if (collectingUndo) { 695 // Save into the undo/redo stack, but only the characters - not the formatting 696 // The gap would be moved to position anyway for the deletion so this doesn't cost extra 697 data = substance.RangePointer(position, deleteLength); 698 data = uh.AppendAction(removeAction, position, data, deleteLength, startSequence); 699 } 700 701 BasicDeleteChars(position, deleteLength); 702 } 703 return data; 704 } 705 706 Sci::Position CellBuffer::Length() const noexcept { 707 return substance.Length(); 708 } 709 710 void CellBuffer::Allocate(Sci::Position newSize) { 711 substance.ReAllocate(newSize); 712 if (hasStyles) { 713 style.ReAllocate(newSize); 714 } 715 } 716 717 void CellBuffer::SetUTF8Substance(bool utf8Substance_) noexcept { 718 utf8Substance = utf8Substance_; 719 } 720 721 void CellBuffer::SetLineEndTypes(int utf8LineEnds_) { 722 if (utf8LineEnds != utf8LineEnds_) { 723 const int indexes = plv->LineCharacterIndex(); 724 utf8LineEnds = utf8LineEnds_; 725 ResetLineEnds(); 726 AllocateLineCharacterIndex(indexes); 727 } 728 } 729 730 bool CellBuffer::ContainsLineEnd(const char *s, Sci::Position length) const noexcept { 731 unsigned char chBeforePrev = 0; 732 unsigned char chPrev = 0; 733 for (Sci::Position i = 0; i < length; i++) { 734 const unsigned char ch = s[i]; 735 if ((ch == '\r') || (ch == '\n')) { 736 return true; 737 } else if (utf8LineEnds) { 738 if (UTF8IsMultibyteLineEnd(chBeforePrev, chPrev, ch)) { 739 return true; 740 } 741 } 742 chBeforePrev = chPrev; 743 chPrev = ch; 744 } 745 return false; 746 } 747 748 void CellBuffer::SetPerLine(PerLine *pl) noexcept { 749 plv->SetPerLine(pl); 750 } 751 752 int CellBuffer::LineCharacterIndex() const noexcept { 753 return plv->LineCharacterIndex(); 754 } 755 756 void CellBuffer::AllocateLineCharacterIndex(int lineCharacterIndex) { 757 if (utf8Substance) { 758 if (plv->AllocateLineCharacterIndex(lineCharacterIndex, Lines())) { 759 // Changed so recalculate whole file 760 RecalculateIndexLineStarts(0, Lines() - 1); 761 } 762 } 763 } 764 765 void CellBuffer::ReleaseLineCharacterIndex(int lineCharacterIndex) { 766 plv->ReleaseLineCharacterIndex(lineCharacterIndex); 767 } 768 769 Sci::Line CellBuffer::Lines() const noexcept { 770 return plv->Lines(); 771 } 772 773 Sci::Position CellBuffer::LineStart(Sci::Line line) const noexcept { 774 if (line < 0) 775 return 0; 776 else if (line >= Lines()) 777 return Length(); 778 else 779 return plv->LineStart(line); 780 } 781 782 Sci::Line CellBuffer::LineFromPosition(Sci::Position pos) const noexcept { 783 return plv->LineFromPosition(pos); 784 } 785 786 Sci::Position CellBuffer::IndexLineStart(Sci::Line line, int lineCharacterIndex) const noexcept { 787 return plv->IndexLineStart(line, lineCharacterIndex); 788 } 789 790 Sci::Line CellBuffer::LineFromPositionIndex(Sci::Position pos, int lineCharacterIndex) const noexcept { 791 return plv->LineFromPositionIndex(pos, lineCharacterIndex); 792 } 793 794 bool CellBuffer::IsReadOnly() const noexcept { 795 return readOnly; 796 } 797 798 void CellBuffer::SetReadOnly(bool set) noexcept { 799 readOnly = set; 800 } 801 802 bool CellBuffer::IsLarge() const noexcept { 803 return largeDocument; 804 } 805 806 bool CellBuffer::HasStyles() const noexcept { 807 return hasStyles; 808 } 809 810 void CellBuffer::SetSavePoint() { 811 uh.SetSavePoint(); 812 } 813 814 bool CellBuffer::IsSavePoint() const noexcept { 815 return uh.IsSavePoint(); 816 } 817 818 void CellBuffer::TentativeStart() { 819 uh.TentativeStart(); 820 } 821 822 void CellBuffer::TentativeCommit() { 823 uh.TentativeCommit(); 824 } 825 826 int CellBuffer::TentativeSteps() noexcept { 827 return uh.TentativeSteps(); 828 } 829 830 bool CellBuffer::TentativeActive() const noexcept { 831 return uh.TentativeActive(); 832 } 833 834 // Without undo 835 836 void CellBuffer::InsertLine(Sci::Line line, Sci::Position position, bool lineStart) { 837 plv->InsertLine(line, position, lineStart); 838 } 839 840 void CellBuffer::RemoveLine(Sci::Line line) { 841 plv->RemoveLine(line); 842 } 843 844 bool CellBuffer::UTF8LineEndOverlaps(Sci::Position position) const noexcept { 845 const unsigned char bytes[] = { 846 static_cast<unsigned char>(substance.ValueAt(position-2)), 847 static_cast<unsigned char>(substance.ValueAt(position-1)), 848 static_cast<unsigned char>(substance.ValueAt(position)), 849 static_cast<unsigned char>(substance.ValueAt(position+1)), 850 }; 851 return UTF8IsSeparator(bytes) || UTF8IsSeparator(bytes+1) || UTF8IsNEL(bytes+1); 852 } 853 854 bool CellBuffer::UTF8IsCharacterBoundary(Sci::Position position) const { 855 assert(position >= 0 && position <= Length()); 856 if (position > 0) { 857 std::string back; 858 for (int i = 0; i < UTF8MaxBytes; i++) { 859 const Sci::Position posBack = position - i; 860 if (posBack < 0) { 861 return false; 862 } 863 back.insert(0, 1, substance.ValueAt(posBack)); 864 if (!UTF8IsTrailByte(back.front())) { 865 if (i > 0) { 866 // Have reached a non-trail 867 const int cla = UTF8Classify(back); 868 if ((cla & UTF8MaskInvalid) || (cla != i)) { 869 return false; 870 } 871 } 872 break; 873 } 874 } 875 } 876 if (position < Length()) { 877 const unsigned char fore = substance.ValueAt(position); 878 if (UTF8IsTrailByte(fore)) { 879 return false; 880 } 881 } 882 return true; 883 } 884 885 void CellBuffer::ResetLineEnds() { 886 // Reinitialize line data -- too much work to preserve 887 plv->Init(); 888 889 const Sci::Position position = 0; 890 const Sci::Position length = Length(); 891 Sci::Line lineInsert = 1; 892 const bool atLineStart = true; 893 plv->InsertText(lineInsert-1, length); 894 unsigned char chBeforePrev = 0; 895 unsigned char chPrev = 0; 896 for (Sci::Position i = 0; i < length; i++) { 897 const unsigned char ch = substance.ValueAt(position + i); 898 if (ch == '\r') { 899 InsertLine(lineInsert, (position + i) + 1, atLineStart); 900 lineInsert++; 901 } else if (ch == '\n') { 902 if (chPrev == '\r') { 903 // Patch up what was end of line 904 plv->SetLineStart(lineInsert - 1, (position + i) + 1); 905 } else { 906 InsertLine(lineInsert, (position + i) + 1, atLineStart); 907 lineInsert++; 908 } 909 } else if (utf8LineEnds) { 910 if (UTF8IsMultibyteLineEnd(chBeforePrev, chPrev, ch)) { 911 InsertLine(lineInsert, (position + i) + 1, atLineStart); 912 lineInsert++; 913 } 914 } 915 chBeforePrev = chPrev; 916 chPrev = ch; 917 } 918 } 919 920 namespace { 921 922 CountWidths CountCharacterWidthsUTF8(std::string_view sv) noexcept { 923 CountWidths cw; 924 size_t remaining = sv.length(); 925 while (remaining > 0) { 926 const int utf8Status = UTF8Classify(sv); 927 const int lenChar = utf8Status & UTF8MaskWidth; 928 cw.CountChar(lenChar); 929 sv.remove_prefix(lenChar); 930 remaining -= lenChar; 931 } 932 return cw; 933 } 934 935 } 936 937 bool CellBuffer::MaintainingLineCharacterIndex() const noexcept { 938 return plv->LineCharacterIndex() != SC_LINECHARACTERINDEX_NONE; 939 } 940 941 void CellBuffer::RecalculateIndexLineStarts(Sci::Line lineFirst, Sci::Line lineLast) { 942 std::string text; 943 Sci::Position posLineEnd = LineStart(lineFirst); 944 for (Sci::Line line = lineFirst; line <= lineLast; line++) { 945 // Find line start and end, retrieve text of line, count characters and update line width 946 const Sci::Position posLineStart = posLineEnd; 947 posLineEnd = LineStart(line+1); 948 const Sci::Position width = posLineEnd - posLineStart; 949 text.resize(width); 950 GetCharRange(text.data(), posLineStart, width); 951 const CountWidths cw = CountCharacterWidthsUTF8(text); 952 plv->SetLineCharactersWidth(line, cw); 953 } 954 } 955 956 void CellBuffer::BasicInsertString(Sci::Position position, const char *s, Sci::Position insertLength) { 957 if (insertLength == 0) 958 return; 959 PLATFORM_ASSERT(insertLength > 0); 960 961 const unsigned char chAfter = substance.ValueAt(position); 962 bool breakingUTF8LineEnd = false; 963 if (utf8LineEnds && UTF8IsTrailByte(chAfter)) { 964 breakingUTF8LineEnd = UTF8LineEndOverlaps(position); 965 } 966 967 const Sci::Line linePosition = plv->LineFromPosition(position); 968 Sci::Line lineInsert = linePosition + 1; 969 970 // A simple insertion is one that inserts valid text on a single line at a character boundary 971 bool simpleInsertion = false; 972 973 const bool maintainingIndex = MaintainingLineCharacterIndex(); 974 975 // Check for breaking apart a UTF-8 sequence and inserting invalid UTF-8 976 if (utf8Substance && maintainingIndex) { 977 // Actually, don't need to check that whole insertion is valid just that there 978 // are no potential fragments at ends. 979 simpleInsertion = UTF8IsCharacterBoundary(position) && 980 UTF8IsValid(std::string_view(s, insertLength)); 981 } 982 983 substance.InsertFromArray(position, s, 0, insertLength); 984 if (hasStyles) { 985 style.InsertValue(position, insertLength, 0); 986 } 987 988 const bool atLineStart = plv->LineStart(lineInsert-1) == position; 989 // Point all the lines after the insertion point further along in the buffer 990 plv->InsertText(lineInsert-1, insertLength); 991 unsigned char chBeforePrev = substance.ValueAt(position - 2); 992 unsigned char chPrev = substance.ValueAt(position - 1); 993 if (chPrev == '\r' && chAfter == '\n') { 994 // Splitting up a crlf pair at position 995 InsertLine(lineInsert, position, false); 996 lineInsert++; 997 } 998 if (breakingUTF8LineEnd) { 999 RemoveLine(lineInsert); 1000 } 1001 1002 constexpr size_t PositionBlockSize = 128; 1003 Sci::Position positions[PositionBlockSize]{}; 1004 size_t nPositions = 0; 1005 const Sci::Line lineStart = lineInsert; 1006 1007 // s may not NULL-terminated, ensure *ptr == '\n' or *next == '\n' is valid. 1008 const char * const end = s + insertLength - 1; 1009 const char *ptr = s; 1010 unsigned char ch = 0; 1011 1012 if (chPrev == '\r' && *ptr == '\n') { 1013 ++ptr; 1014 // Patch up what was end of line 1015 plv->SetLineStart(lineInsert - 1, (position + ptr - s)); 1016 simpleInsertion = false; 1017 } 1018 1019 if (ptr < end) { 1020 uint8_t eolTable[256]{}; 1021 eolTable[static_cast<uint8_t>('\n')] = 1; 1022 eolTable[static_cast<uint8_t>('\r')] = 2; 1023 if (utf8LineEnds) { 1024 // see UniConversion.h for LS, PS and NEL 1025 eolTable[0x85] = 4; 1026 eolTable[0xa8] = 3; 1027 eolTable[0xa9] = 3; 1028 } 1029 1030 do { 1031 // skip to line end 1032 ch = *ptr++; 1033 uint8_t type; 1034 while ((type = eolTable[ch]) == 0 && ptr < end) { 1035 chBeforePrev = chPrev; 1036 chPrev = ch; 1037 ch = *ptr++; 1038 } 1039 switch (type) { 1040 case 2: // '\r' 1041 if (*ptr == '\n') { 1042 ++ptr; 1043 } 1044 [[fallthrough]]; 1045 case 1: // '\n' 1046 positions[nPositions++] = position + ptr - s; 1047 if (nPositions == PositionBlockSize) { 1048 plv->InsertLines(lineInsert, positions, nPositions, atLineStart); 1049 lineInsert += nPositions; 1050 nPositions = 0; 1051 } 1052 break; 1053 case 3: 1054 case 4: 1055 // LS, PS and NEL 1056 if ((type == 3 && chPrev == 0x80 && chBeforePrev == 0xe2) || (type == 4 && chPrev == 0xc2)) { 1057 positions[nPositions++] = position + ptr - s; 1058 if (nPositions == PositionBlockSize) { 1059 plv->InsertLines(lineInsert, positions, nPositions, atLineStart); 1060 lineInsert += nPositions; 1061 nPositions = 0; 1062 } 1063 } 1064 break; 1065 } 1066 1067 chBeforePrev = chPrev; 1068 chPrev = ch; 1069 } while (ptr < end); 1070 } 1071 1072 if (nPositions != 0) { 1073 plv->InsertLines(lineInsert, positions, nPositions, atLineStart); 1074 lineInsert += nPositions; 1075 } 1076 1077 ch = *end; 1078 if (ptr == end) { 1079 ++ptr; 1080 if (ch == '\r' || ch == '\n') { 1081 InsertLine(lineInsert, (position + ptr - s), atLineStart); 1082 lineInsert++; 1083 } else if (utf8LineEnds && !UTF8IsAscii(ch)) { 1084 if (UTF8IsMultibyteLineEnd(chBeforePrev, chPrev, ch)) { 1085 InsertLine(lineInsert, (position + ptr - s), atLineStart); 1086 lineInsert++; 1087 } 1088 } 1089 } 1090 1091 // Joining two lines where last insertion is cr and following substance starts with lf 1092 if (chAfter == '\n') { 1093 if (ch == '\r') { 1094 // End of line already in buffer so drop the newly created one 1095 RemoveLine(lineInsert - 1); 1096 simpleInsertion = false; 1097 } 1098 } else if (utf8LineEnds && !UTF8IsAscii(chAfter)) { 1099 chBeforePrev = chPrev; 1100 chPrev = ch; 1101 // May have end of UTF-8 line end in buffer and start in insertion 1102 for (int j = 0; j < UTF8SeparatorLength-1; j++) { 1103 const unsigned char chAt = substance.ValueAt(position + insertLength + j); 1104 const unsigned char back3[3] = {chBeforePrev, chPrev, chAt}; 1105 if (UTF8IsSeparator(back3)) { 1106 InsertLine(lineInsert, (position + insertLength + j) + 1, atLineStart); 1107 lineInsert++; 1108 } 1109 if ((j == 0) && UTF8IsNEL(back3+1)) { 1110 InsertLine(lineInsert, (position + insertLength + j) + 1, atLineStart); 1111 lineInsert++; 1112 } 1113 chBeforePrev = chPrev; 1114 chPrev = chAt; 1115 } 1116 } 1117 if (maintainingIndex) { 1118 if (simpleInsertion && (lineInsert == lineStart)) { 1119 const CountWidths cw = CountCharacterWidthsUTF8(std::string_view(s, insertLength)); 1120 plv->InsertCharacters(linePosition, cw); 1121 } else { 1122 RecalculateIndexLineStarts(linePosition, lineInsert - 1); 1123 } 1124 } 1125 } 1126 1127 void CellBuffer::BasicDeleteChars(Sci::Position position, Sci::Position deleteLength) { 1128 if (deleteLength == 0) 1129 return; 1130 1131 Sci::Line lineRecalculateStart = INVALID_POSITION; 1132 1133 if ((position == 0) && (deleteLength == substance.Length())) { 1134 // If whole buffer is being deleted, faster to reinitialise lines data 1135 // than to delete each line. 1136 plv->Init(); 1137 } else { 1138 // Have to fix up line positions before doing deletion as looking at text in buffer 1139 // to work out which lines have been removed 1140 1141 const Sci::Line linePosition = plv->LineFromPosition(position); 1142 Sci::Line lineRemove = linePosition + 1; 1143 1144 plv->InsertText(lineRemove-1, - (deleteLength)); 1145 const unsigned char chPrev = substance.ValueAt(position - 1); 1146 const unsigned char chBefore = chPrev; 1147 unsigned char chNext = substance.ValueAt(position); 1148 1149 // Check for breaking apart a UTF-8 sequence 1150 // Needs further checks that text is UTF-8 or that some other break apart is occurring 1151 if (utf8Substance && MaintainingLineCharacterIndex()) { 1152 const Sci::Position posEnd = position + deleteLength; 1153 const Sci::Line lineEndRemove = plv->LineFromPosition(posEnd); 1154 const bool simpleDeletion = 1155 (linePosition == lineEndRemove) && 1156 UTF8IsCharacterBoundary(position) && UTF8IsCharacterBoundary(posEnd); 1157 if (simpleDeletion) { 1158 std::string text(deleteLength, '\0'); 1159 GetCharRange(text.data(), position, deleteLength); 1160 if (UTF8IsValid(text)) { 1161 // Everything is good 1162 const CountWidths cw = CountCharacterWidthsUTF8(text); 1163 plv->InsertCharacters(linePosition, -cw); 1164 } else { 1165 lineRecalculateStart = linePosition; 1166 } 1167 } else { 1168 lineRecalculateStart = linePosition; 1169 } 1170 } 1171 1172 bool ignoreNL = false; 1173 if (chPrev == '\r' && chNext == '\n') { 1174 // Move back one 1175 plv->SetLineStart(lineRemove, position); 1176 lineRemove++; 1177 ignoreNL = true; // First \n is not real deletion 1178 } 1179 if (utf8LineEnds && UTF8IsTrailByte(chNext)) { 1180 if (UTF8LineEndOverlaps(position)) { 1181 RemoveLine(lineRemove); 1182 } 1183 } 1184 1185 unsigned char ch = chNext; 1186 for (Sci::Position i = 0; i < deleteLength; i++) { 1187 chNext = substance.ValueAt(position + i + 1); 1188 if (ch == '\r') { 1189 if (chNext != '\n') { 1190 RemoveLine(lineRemove); 1191 } 1192 } else if (ch == '\n') { 1193 if (ignoreNL) { 1194 ignoreNL = false; // Further \n are real deletions 1195 } else { 1196 RemoveLine(lineRemove); 1197 } 1198 } else if (utf8LineEnds) { 1199 if (!UTF8IsAscii(ch)) { 1200 const unsigned char next3[3] = {ch, chNext, 1201 static_cast<unsigned char>(substance.ValueAt(position + i + 2))}; 1202 if (UTF8IsSeparator(next3) || UTF8IsNEL(next3)) { 1203 RemoveLine(lineRemove); 1204 } 1205 } 1206 } 1207 1208 ch = chNext; 1209 } 1210 // May have to fix up end if last deletion causes cr to be next to lf 1211 // or removes one of a crlf pair 1212 const char chAfter = substance.ValueAt(position + deleteLength); 1213 if (chBefore == '\r' && chAfter == '\n') { 1214 // Using lineRemove-1 as cr ended line before start of deletion 1215 RemoveLine(lineRemove - 1); 1216 plv->SetLineStart(lineRemove - 1, position + 1); 1217 } 1218 } 1219 substance.DeleteRange(position, deleteLength); 1220 if (lineRecalculateStart >= 0) { 1221 RecalculateIndexLineStarts(lineRecalculateStart, lineRecalculateStart); 1222 } 1223 if (hasStyles) { 1224 style.DeleteRange(position, deleteLength); 1225 } 1226 } 1227 1228 bool CellBuffer::SetUndoCollection(bool collectUndo) { 1229 collectingUndo = collectUndo; 1230 uh.DropUndoSequence(); 1231 return collectingUndo; 1232 } 1233 1234 bool CellBuffer::IsCollectingUndo() const noexcept { 1235 return collectingUndo; 1236 } 1237 1238 void CellBuffer::BeginUndoAction() { 1239 uh.BeginUndoAction(); 1240 } 1241 1242 void CellBuffer::EndUndoAction() { 1243 uh.EndUndoAction(); 1244 } 1245 1246 void CellBuffer::AddUndoAction(Sci::Position token, bool mayCoalesce) { 1247 bool startSequence; 1248 uh.AppendAction(containerAction, token, nullptr, 0, startSequence, mayCoalesce); 1249 } 1250 1251 void CellBuffer::DeleteUndoHistory() { 1252 uh.DeleteUndoHistory(); 1253 } 1254 1255 bool CellBuffer::CanUndo() const noexcept { 1256 return uh.CanUndo(); 1257 } 1258 1259 int CellBuffer::StartUndo() { 1260 return uh.StartUndo(); 1261 } 1262 1263 const Action &CellBuffer::GetUndoStep() const { 1264 return uh.GetUndoStep(); 1265 } 1266 1267 void CellBuffer::PerformUndoStep() { 1268 const Action &actionStep = uh.GetUndoStep(); 1269 if (actionStep.at == insertAction) { 1270 if (substance.Length() < actionStep.lenData) { 1271 throw std::runtime_error( 1272 "CellBuffer::PerformUndoStep: deletion must be less than document length."); 1273 } 1274 BasicDeleteChars(actionStep.position, actionStep.lenData); 1275 } else if (actionStep.at == removeAction) { 1276 BasicInsertString(actionStep.position, actionStep.data.get(), actionStep.lenData); 1277 } 1278 uh.CompletedUndoStep(); 1279 } 1280 1281 bool CellBuffer::CanRedo() const noexcept { 1282 return uh.CanRedo(); 1283 } 1284 1285 int CellBuffer::StartRedo() { 1286 return uh.StartRedo(); 1287 } 1288 1289 const Action &CellBuffer::GetRedoStep() const { 1290 return uh.GetRedoStep(); 1291 } 1292 1293 void CellBuffer::PerformRedoStep() { 1294 const Action &actionStep = uh.GetRedoStep(); 1295 if (actionStep.at == insertAction) { 1296 BasicInsertString(actionStep.position, actionStep.data.get(), actionStep.lenData); 1297 } else if (actionStep.at == removeAction) { 1298 BasicDeleteChars(actionStep.position, actionStep.lenData); 1299 } 1300 uh.CompletedRedoStep(); 1301 } 1302 1303