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