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