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