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