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 <stdlib.h>
9 #include <string.h>
10 #include <stdio.h>
11 #include <stdarg.h>
12 
13 #include <algorithm>
14 
15 #include "Platform.h"
16 
17 #include "Scintilla.h"
18 #include "SplitVector.h"
19 #include "Partitioning.h"
20 #include "CellBuffer.h"
21 #include "UniConversion.h"
22 
23 #ifdef SCI_NAMESPACE
24 using namespace Scintilla;
25 #endif
26 
LineVector()27 LineVector::LineVector() : starts(256), perLine(0) {
28 	Init();
29 }
30 
~LineVector()31 LineVector::~LineVector() {
32 	starts.DeleteAll();
33 }
34 
Init()35 void LineVector::Init() {
36 	starts.DeleteAll();
37 	if (perLine) {
38 		perLine->Init();
39 	}
40 }
41 
SetPerLine(PerLine * pl)42 void LineVector::SetPerLine(PerLine *pl) {
43 	perLine = pl;
44 }
45 
InsertText(int line,int delta)46 void LineVector::InsertText(int line, int delta) {
47 	starts.InsertText(line, delta);
48 }
49 
InsertLine(int line,int position,bool lineStart)50 void LineVector::InsertLine(int line, int position, bool lineStart) {
51 	starts.InsertPartition(line, position);
52 	if (perLine) {
53 		if ((line > 0) && lineStart)
54 			line--;
55 		perLine->InsertLine(line);
56 	}
57 }
58 
SetLineStart(int line,int position)59 void LineVector::SetLineStart(int line, int position) {
60 	starts.SetPartitionStartPosition(line, position);
61 }
62 
RemoveLine(int line)63 void LineVector::RemoveLine(int line) {
64 	starts.RemovePartition(line);
65 	if (perLine) {
66 		perLine->RemoveLine(line);
67 	}
68 }
69 
LineFromPosition(int pos) const70 int LineVector::LineFromPosition(int pos) const {
71 	return starts.PartitionFromPosition(pos);
72 }
73 
Action()74 Action::Action() {
75 	at = startAction;
76 	position = 0;
77 	data = 0;
78 	lenData = 0;
79 	mayCoalesce = false;
80 }
81 
~Action()82 Action::~Action() {
83 	Destroy();
84 }
85 
Create(actionType at_,int position_,const char * data_,int lenData_,bool mayCoalesce_)86 void Action::Create(actionType at_, int position_, const char *data_, int lenData_, bool mayCoalesce_) {
87 	delete []data;
88 	data = NULL;
89 	position = position_;
90 	at = at_;
91 	if (lenData_) {
92 		data = new char[lenData_];
93 		memcpy(data, data_, lenData_);
94 	}
95 	lenData = lenData_;
96 	mayCoalesce = mayCoalesce_;
97 }
98 
Destroy()99 void Action::Destroy() {
100 	delete []data;
101 	data = 0;
102 }
103 
Grab(Action * source)104 void Action::Grab(Action *source) {
105 	delete []data;
106 
107 	position = source->position;
108 	at = source->at;
109 	data = source->data;
110 	lenData = source->lenData;
111 	mayCoalesce = source->mayCoalesce;
112 
113 	// Ownership of source data transferred to this
114 	source->position = 0;
115 	source->at = startAction;
116 	source->data = 0;
117 	source->lenData = 0;
118 	source->mayCoalesce = true;
119 }
120 
121 // The undo history stores a sequence of user operations that represent the user's view of the
122 // commands executed on the text.
123 // Each user operation contains a sequence of text insertion and text deletion actions.
124 // All the user operations are stored in a list of individual actions with 'start' actions used
125 // as delimiters between user operations.
126 // Initially there is one start action in the history.
127 // As each action is performed, it is recorded in the history. The action may either become
128 // part of the current user operation or may start a new user operation. If it is to be part of the
129 // current operation, then it overwrites the current last action. If it is to be part of a new
130 // operation, it is appended after the current last action.
131 // After writing the new action, a new start action is appended at the end of the history.
132 // The decision of whether to start a new user operation is based upon two factors. If a
133 // compound operation has been explicitly started by calling BeginUndoAction and no matching
134 // EndUndoAction (these calls nest) has been called, then the action is coalesced into the current
135 // operation. If there is no outstanding BeginUndoAction call then a new operation is started
136 // unless it looks as if the new action is caused by the user typing or deleting a stream of text.
137 // Sequences that look like typing or deletion are coalesced into a single user operation.
138 
UndoHistory()139 UndoHistory::UndoHistory() {
140 
141 	lenActions = 100;
142 	actions = new Action[lenActions];
143 	maxAction = 0;
144 	currentAction = 0;
145 	undoSequenceDepth = 0;
146 	savePoint = 0;
147 	tentativePoint = -1;
148 
149 	actions[currentAction].Create(startAction);
150 }
151 
~UndoHistory()152 UndoHistory::~UndoHistory() {
153 	delete []actions;
154 	actions = 0;
155 }
156 
EnsureUndoRoom()157 void UndoHistory::EnsureUndoRoom() {
158 	// Have to test that there is room for 2 more actions in the array
159 	// as two actions may be created by the calling function
160 	if (currentAction >= (lenActions - 2)) {
161 		// Run out of undo nodes so extend the array
162 		int lenActionsNew = lenActions * 2;
163 		Action *actionsNew = new Action[lenActionsNew];
164 		for (int act = 0; act <= currentAction; act++)
165 			actionsNew[act].Grab(&actions[act]);
166 		delete []actions;
167 		lenActions = lenActionsNew;
168 		actions = actionsNew;
169 	}
170 }
171 
AppendAction(actionType at,int position,const char * data,int lengthData,bool & startSequence,bool mayCoalesce)172 const char *UndoHistory::AppendAction(actionType at, int position, const char *data, int lengthData,
173 	bool &startSequence, bool mayCoalesce) {
174 	EnsureUndoRoom();
175 	//Platform::DebugPrintf("%% %d action %d %d %d\n", at, position, lengthData, currentAction);
176 	//Platform::DebugPrintf("^ %d action %d %d\n", actions[currentAction - 1].at,
177 	//	actions[currentAction - 1].position, actions[currentAction - 1].lenData);
178 	if (currentAction < savePoint) {
179 		savePoint = -1;
180 	}
181 	int oldCurrentAction = currentAction;
182 	if (currentAction >= 1) {
183 		if (0 == undoSequenceDepth) {
184 			// Top level actions may not always be coalesced
185 			int targetAct = -1;
186 			const Action *actPrevious = &(actions[currentAction + targetAct]);
187 			// Container actions may forward the coalesce state of Scintilla Actions.
188 			while ((actPrevious->at == containerAction) && actPrevious->mayCoalesce) {
189 				targetAct--;
190 				actPrevious = &(actions[currentAction + targetAct]);
191 			}
192 			// See if current action can be coalesced into previous action
193 			// Will work if both are inserts or deletes and position is same
194 #if defined(_MSC_VER) && defined(_PREFAST_)
195 			// Visual Studio 2013 Code Analysis wrongly believes actions can be NULL at its next reference
196 			__analysis_assume(actions);
197 #endif
198 			if ((currentAction == savePoint) || (currentAction == tentativePoint)) {
199 				currentAction++;
200 			} else if (!actions[currentAction].mayCoalesce) {
201 				// Not allowed to coalesce if this set
202 				currentAction++;
203 			} else if (!mayCoalesce || !actPrevious->mayCoalesce) {
204 				currentAction++;
205 			} else if (at == containerAction || actions[currentAction].at == containerAction) {
206 				;	// A coalescible containerAction
207 			} else if ((at != actPrevious->at) && (actPrevious->at != startAction)) {
208 				currentAction++;
209 			} else if ((at == insertAction) &&
210 			           (position != (actPrevious->position + actPrevious->lenData))) {
211 				// Insertions must be immediately after to coalesce
212 				currentAction++;
213 			} else if (at == removeAction) {
214 				if ((lengthData == 1) || (lengthData == 2)) {
215 					if ((position + lengthData) == actPrevious->position) {
216 						; // Backspace -> OK
217 					} else if (position == actPrevious->position) {
218 						; // Delete -> OK
219 					} else {
220 						// Removals must be at same position to coalesce
221 						currentAction++;
222 					}
223 				} else {
224 					// Removals must be of one character to coalesce
225 					currentAction++;
226 				}
227 			} else {
228 				// Action coalesced.
229 			}
230 
231 		} else {
232 			// Actions not at top level are always coalesced unless this is after return to top level
233 			if (!actions[currentAction].mayCoalesce)
234 				currentAction++;
235 		}
236 	} else {
237 		currentAction++;
238 	}
239 	startSequence = oldCurrentAction != currentAction;
240 	int actionWithData = currentAction;
241 	actions[currentAction].Create(at, position, data, lengthData, mayCoalesce);
242 	currentAction++;
243 	actions[currentAction].Create(startAction);
244 	maxAction = currentAction;
245 	return actions[actionWithData].data;
246 }
247 
BeginUndoAction()248 void UndoHistory::BeginUndoAction() {
249 	EnsureUndoRoom();
250 	if (undoSequenceDepth == 0) {
251 		if (actions[currentAction].at != startAction) {
252 			currentAction++;
253 			actions[currentAction].Create(startAction);
254 			maxAction = currentAction;
255 		}
256 		actions[currentAction].mayCoalesce = false;
257 	}
258 	undoSequenceDepth++;
259 }
260 
EndUndoAction()261 void UndoHistory::EndUndoAction() {
262 	PLATFORM_ASSERT(undoSequenceDepth > 0);
263 	EnsureUndoRoom();
264 	undoSequenceDepth--;
265 	if (0 == undoSequenceDepth) {
266 		if (actions[currentAction].at != startAction) {
267 			currentAction++;
268 			actions[currentAction].Create(startAction);
269 			maxAction = currentAction;
270 		}
271 		actions[currentAction].mayCoalesce = false;
272 	}
273 }
274 
DropUndoSequence()275 void UndoHistory::DropUndoSequence() {
276 	undoSequenceDepth = 0;
277 }
278 
DeleteUndoHistory()279 void UndoHistory::DeleteUndoHistory() {
280 	for (int i = 1; i < maxAction; i++)
281 		actions[i].Destroy();
282 	maxAction = 0;
283 	currentAction = 0;
284 	actions[currentAction].Create(startAction);
285 	savePoint = 0;
286 	tentativePoint = -1;
287 }
288 
SetSavePoint()289 void UndoHistory::SetSavePoint() {
290 	savePoint = currentAction;
291 }
292 
IsSavePoint() const293 bool UndoHistory::IsSavePoint() const {
294 	return savePoint == currentAction;
295 }
296 
TentativeStart()297 void UndoHistory::TentativeStart() {
298 	tentativePoint = currentAction;
299 }
300 
TentativeCommit()301 void UndoHistory::TentativeCommit() {
302 	tentativePoint = -1;
303 	// Truncate undo history
304 	maxAction = currentAction;
305 }
306 
TentativeSteps()307 int UndoHistory::TentativeSteps() {
308 	// Drop any trailing startAction
309 	if (actions[currentAction].at == startAction && currentAction > 0)
310 		currentAction--;
311 	if (tentativePoint >= 0)
312 		return currentAction - tentativePoint;
313 	else
314 		return -1;
315 }
316 
CanUndo() const317 bool UndoHistory::CanUndo() const {
318 	return (currentAction > 0) && (maxAction > 0);
319 }
320 
StartUndo()321 int UndoHistory::StartUndo() {
322 	// Drop any trailing startAction
323 	if (actions[currentAction].at == startAction && currentAction > 0)
324 		currentAction--;
325 
326 	// Count the steps in this action
327 	int act = currentAction;
328 	while (actions[act].at != startAction && act > 0) {
329 		act--;
330 	}
331 	return currentAction - act;
332 }
333 
GetUndoStep() const334 const Action &UndoHistory::GetUndoStep() const {
335 	return actions[currentAction];
336 }
337 
CompletedUndoStep()338 void UndoHistory::CompletedUndoStep() {
339 	currentAction--;
340 }
341 
CanRedo() const342 bool UndoHistory::CanRedo() const {
343 	return maxAction > currentAction;
344 }
345 
StartRedo()346 int UndoHistory::StartRedo() {
347 	// Drop any leading startAction
348 	if (actions[currentAction].at == startAction && currentAction < maxAction)
349 		currentAction++;
350 
351 	// Count the steps in this action
352 	int act = currentAction;
353 	while (actions[act].at != startAction && act < maxAction) {
354 		act++;
355 	}
356 	return act - currentAction;
357 }
358 
GetRedoStep() const359 const Action &UndoHistory::GetRedoStep() const {
360 	return actions[currentAction];
361 }
362 
CompletedRedoStep()363 void UndoHistory::CompletedRedoStep() {
364 	currentAction++;
365 }
366 
CellBuffer()367 CellBuffer::CellBuffer() {
368 	readOnly = false;
369 	utf8LineEnds = 0;
370 	collectingUndo = true;
371 }
372 
~CellBuffer()373 CellBuffer::~CellBuffer() {
374 }
375 
CharAt(int position) const376 char CellBuffer::CharAt(int position) const {
377 	return substance.ValueAt(position);
378 }
379 
GetCharRange(char * buffer,int position,int lengthRetrieve) const380 void CellBuffer::GetCharRange(char *buffer, int position, int lengthRetrieve) const {
381 	if (lengthRetrieve < 0)
382 		return;
383 	if (position < 0)
384 		return;
385 	if ((position + lengthRetrieve) > substance.Length()) {
386 		Platform::DebugPrintf("Bad GetCharRange %d for %d of %d\n", position,
387 		                      lengthRetrieve, substance.Length());
388 		return;
389 	}
390 	substance.GetRange(buffer, position, lengthRetrieve);
391 }
392 
StyleAt(int position) const393 char CellBuffer::StyleAt(int position) const {
394 	return style.ValueAt(position);
395 }
396 
GetStyleRange(unsigned char * buffer,int position,int lengthRetrieve) const397 void CellBuffer::GetStyleRange(unsigned char *buffer, int position, int lengthRetrieve) const {
398 	if (lengthRetrieve < 0)
399 		return;
400 	if (position < 0)
401 		return;
402 	if ((position + lengthRetrieve) > style.Length()) {
403 		Platform::DebugPrintf("Bad GetStyleRange %d for %d of %d\n", position,
404 		                      lengthRetrieve, style.Length());
405 		return;
406 	}
407 	style.GetRange(reinterpret_cast<char *>(buffer), position, lengthRetrieve);
408 }
409 
BufferPointer()410 const char *CellBuffer::BufferPointer() {
411 	return substance.BufferPointer();
412 }
413 
RangePointer(int position,int rangeLength)414 const char *CellBuffer::RangePointer(int position, int rangeLength) {
415 	return substance.RangePointer(position, rangeLength);
416 }
417 
GapPosition() const418 int CellBuffer::GapPosition() const {
419 	return substance.GapPosition();
420 }
421 
422 // The char* returned is to an allocation owned by the undo history
InsertString(int position,const char * s,int insertLength,bool & startSequence)423 const char *CellBuffer::InsertString(int position, const char *s, int insertLength, bool &startSequence) {
424 	// InsertString and DeleteChars are the bottleneck though which all changes occur
425 	const char *data = s;
426 	if (!readOnly) {
427 		if (collectingUndo) {
428 			// Save into the undo/redo stack, but only the characters - not the formatting
429 			// This takes up about half load time
430 			data = uh.AppendAction(insertAction, position, s, insertLength, startSequence);
431 		}
432 
433 		BasicInsertString(position, s, insertLength);
434 	}
435 	return data;
436 }
437 
SetStyleAt(int position,char styleValue)438 bool CellBuffer::SetStyleAt(int position, char styleValue) {
439 	char curVal = style.ValueAt(position);
440 	if (curVal != styleValue) {
441 		style.SetValueAt(position, styleValue);
442 		return true;
443 	} else {
444 		return false;
445 	}
446 }
447 
SetStyleFor(int position,int lengthStyle,char styleValue)448 bool CellBuffer::SetStyleFor(int position, int lengthStyle, char styleValue) {
449 	bool changed = false;
450 	PLATFORM_ASSERT(lengthStyle == 0 ||
451 		(lengthStyle > 0 && lengthStyle + position <= style.Length()));
452 	while (lengthStyle--) {
453 		char curVal = style.ValueAt(position);
454 		if (curVal != styleValue) {
455 			style.SetValueAt(position, styleValue);
456 			changed = true;
457 		}
458 		position++;
459 	}
460 	return changed;
461 }
462 
463 // The char* returned is to an allocation owned by the undo history
DeleteChars(int position,int deleteLength,bool & startSequence)464 const char *CellBuffer::DeleteChars(int position, int deleteLength, bool &startSequence) {
465 	// InsertString and DeleteChars are the bottleneck though which all changes occur
466 	PLATFORM_ASSERT(deleteLength > 0);
467 	const char *data = 0;
468 	if (!readOnly) {
469 		if (collectingUndo) {
470 			// Save into the undo/redo stack, but only the characters - not the formatting
471 			// The gap would be moved to position anyway for the deletion so this doesn't cost extra
472 			data = substance.RangePointer(position, deleteLength);
473 			data = uh.AppendAction(removeAction, position, data, deleteLength, startSequence);
474 		}
475 
476 		BasicDeleteChars(position, deleteLength);
477 	}
478 	return data;
479 }
480 
Length() const481 int CellBuffer::Length() const {
482 	return substance.Length();
483 }
484 
Allocate(int newSize)485 void CellBuffer::Allocate(int newSize) {
486 	substance.ReAllocate(newSize);
487 	style.ReAllocate(newSize);
488 }
489 
SetLineEndTypes(int utf8LineEnds_)490 void CellBuffer::SetLineEndTypes(int utf8LineEnds_) {
491 	if (utf8LineEnds != utf8LineEnds_) {
492 		utf8LineEnds = utf8LineEnds_;
493 		ResetLineEnds();
494 	}
495 }
496 
SetPerLine(PerLine * pl)497 void CellBuffer::SetPerLine(PerLine *pl) {
498 	lv.SetPerLine(pl);
499 }
500 
Lines() const501 int CellBuffer::Lines() const {
502 	return lv.Lines();
503 }
504 
LineStart(int line) const505 int CellBuffer::LineStart(int line) const {
506 	if (line < 0)
507 		return 0;
508 	else if (line >= Lines())
509 		return Length();
510 	else
511 		return lv.LineStart(line);
512 }
513 
IsReadOnly() const514 bool CellBuffer::IsReadOnly() const {
515 	return readOnly;
516 }
517 
SetReadOnly(bool set)518 void CellBuffer::SetReadOnly(bool set) {
519 	readOnly = set;
520 }
521 
SetSavePoint()522 void CellBuffer::SetSavePoint() {
523 	uh.SetSavePoint();
524 }
525 
IsSavePoint() const526 bool CellBuffer::IsSavePoint() const {
527 	return uh.IsSavePoint();
528 }
529 
TentativeStart()530 void CellBuffer::TentativeStart() {
531 	uh.TentativeStart();
532 }
533 
TentativeCommit()534 void CellBuffer::TentativeCommit() {
535 	uh.TentativeCommit();
536 }
537 
TentativeSteps()538 int CellBuffer::TentativeSteps() {
539 	return uh.TentativeSteps();
540 }
541 
TentativeActive() const542 bool CellBuffer::TentativeActive() const {
543 	return uh.TentativeActive();
544 }
545 
546 // Without undo
547 
InsertLine(int line,int position,bool lineStart)548 void CellBuffer::InsertLine(int line, int position, bool lineStart) {
549 	lv.InsertLine(line, position, lineStart);
550 }
551 
RemoveLine(int line)552 void CellBuffer::RemoveLine(int line) {
553 	lv.RemoveLine(line);
554 }
555 
UTF8LineEndOverlaps(int position) const556 bool CellBuffer::UTF8LineEndOverlaps(int position) const {
557 	unsigned char bytes[] = {
558 		static_cast<unsigned char>(substance.ValueAt(position-2)),
559 		static_cast<unsigned char>(substance.ValueAt(position-1)),
560 		static_cast<unsigned char>(substance.ValueAt(position)),
561 		static_cast<unsigned char>(substance.ValueAt(position+1)),
562 	};
563 	return UTF8IsSeparator(bytes) || UTF8IsSeparator(bytes+1) || UTF8IsNEL(bytes+1);
564 }
565 
ResetLineEnds()566 void CellBuffer::ResetLineEnds() {
567 	// Reinitialize line data -- too much work to preserve
568 	lv.Init();
569 
570 	int position = 0;
571 	int length = Length();
572 	int lineInsert = 1;
573 	bool atLineStart = true;
574 	lv.InsertText(lineInsert-1, length);
575 	unsigned char chBeforePrev = 0;
576 	unsigned char chPrev = 0;
577 	for (int i = 0; i < length; i++) {
578 		unsigned char ch = substance.ValueAt(position + i);
579 		if (ch == '\r') {
580 			InsertLine(lineInsert, (position + i) + 1, atLineStart);
581 			lineInsert++;
582 		} else if (ch == '\n') {
583 			if (chPrev == '\r') {
584 				// Patch up what was end of line
585 				lv.SetLineStart(lineInsert - 1, (position + i) + 1);
586 			} else {
587 				InsertLine(lineInsert, (position + i) + 1, atLineStart);
588 				lineInsert++;
589 			}
590 		} else if (utf8LineEnds) {
591 			unsigned char back3[3] = {chBeforePrev, chPrev, ch};
592 			if (UTF8IsSeparator(back3) || UTF8IsNEL(back3+1)) {
593 				InsertLine(lineInsert, (position + i) + 1, atLineStart);
594 				lineInsert++;
595 			}
596 		}
597 		chBeforePrev = chPrev;
598 		chPrev = ch;
599 	}
600 }
601 
BasicInsertString(int position,const char * s,int insertLength)602 void CellBuffer::BasicInsertString(int position, const char *s, int insertLength) {
603 	if (insertLength == 0)
604 		return;
605 	PLATFORM_ASSERT(insertLength > 0);
606 
607 	unsigned char chAfter = substance.ValueAt(position);
608 	bool breakingUTF8LineEnd = false;
609 	if (utf8LineEnds && UTF8IsTrailByte(chAfter)) {
610 		breakingUTF8LineEnd = UTF8LineEndOverlaps(position);
611 	}
612 
613 	substance.InsertFromArray(position, s, 0, insertLength);
614 	style.InsertValue(position, insertLength, 0);
615 
616 	int lineInsert = lv.LineFromPosition(position) + 1;
617 	bool atLineStart = lv.LineStart(lineInsert-1) == position;
618 	// Point all the lines after the insertion point further along in the buffer
619 	lv.InsertText(lineInsert-1, insertLength);
620 	unsigned char chBeforePrev = substance.ValueAt(position - 2);
621 	unsigned char chPrev = substance.ValueAt(position - 1);
622 	if (chPrev == '\r' && chAfter == '\n') {
623 		// Splitting up a crlf pair at position
624 		InsertLine(lineInsert, position, false);
625 		lineInsert++;
626 	}
627 	if (breakingUTF8LineEnd) {
628 		RemoveLine(lineInsert);
629 	}
630 	unsigned char ch = ' ';
631 	for (int i = 0; i < insertLength; i++) {
632 		ch = s[i];
633 		if (ch == '\r') {
634 			InsertLine(lineInsert, (position + i) + 1, atLineStart);
635 			lineInsert++;
636 		} else if (ch == '\n') {
637 			if (chPrev == '\r') {
638 				// Patch up what was end of line
639 				lv.SetLineStart(lineInsert - 1, (position + i) + 1);
640 			} else {
641 				InsertLine(lineInsert, (position + i) + 1, atLineStart);
642 				lineInsert++;
643 			}
644 		} else if (utf8LineEnds) {
645 			unsigned char back3[3] = {chBeforePrev, chPrev, ch};
646 			if (UTF8IsSeparator(back3) || UTF8IsNEL(back3+1)) {
647 				InsertLine(lineInsert, (position + i) + 1, atLineStart);
648 				lineInsert++;
649 			}
650 		}
651 		chBeforePrev = chPrev;
652 		chPrev = ch;
653 	}
654 	// Joining two lines where last insertion is cr and following substance starts with lf
655 	if (chAfter == '\n') {
656 		if (ch == '\r') {
657 			// End of line already in buffer so drop the newly created one
658 			RemoveLine(lineInsert - 1);
659 		}
660 	} else if (utf8LineEnds && !UTF8IsAscii(chAfter)) {
661 		// May have end of UTF-8 line end in buffer and start in insertion
662 		for (int j = 0; j < UTF8SeparatorLength-1; j++) {
663 			unsigned char chAt = substance.ValueAt(position + insertLength + j);
664 			unsigned char back3[3] = {chBeforePrev, chPrev, chAt};
665 			if (UTF8IsSeparator(back3)) {
666 				InsertLine(lineInsert, (position + insertLength + j) + 1, atLineStart);
667 				lineInsert++;
668 			}
669 			if ((j == 0) && UTF8IsNEL(back3+1)) {
670 				InsertLine(lineInsert, (position + insertLength + j) + 1, atLineStart);
671 				lineInsert++;
672 			}
673 			chBeforePrev = chPrev;
674 			chPrev = chAt;
675 		}
676 	}
677 }
678 
BasicDeleteChars(int position,int deleteLength)679 void CellBuffer::BasicDeleteChars(int position, int deleteLength) {
680 	if (deleteLength == 0)
681 		return;
682 
683 	if ((position == 0) && (deleteLength == substance.Length())) {
684 		// If whole buffer is being deleted, faster to reinitialise lines data
685 		// than to delete each line.
686 		lv.Init();
687 	} else {
688 		// Have to fix up line positions before doing deletion as looking at text in buffer
689 		// to work out which lines have been removed
690 
691 		int lineRemove = lv.LineFromPosition(position) + 1;
692 		lv.InsertText(lineRemove-1, - (deleteLength));
693 		unsigned char chPrev = substance.ValueAt(position - 1);
694 		unsigned char chBefore = chPrev;
695 		unsigned char chNext = substance.ValueAt(position);
696 		bool ignoreNL = false;
697 		if (chPrev == '\r' && chNext == '\n') {
698 			// Move back one
699 			lv.SetLineStart(lineRemove, position);
700 			lineRemove++;
701 			ignoreNL = true; 	// First \n is not real deletion
702 		}
703 		if (utf8LineEnds && UTF8IsTrailByte(chNext)) {
704 			if (UTF8LineEndOverlaps(position)) {
705 				RemoveLine(lineRemove);
706 			}
707 		}
708 
709 		unsigned char ch = chNext;
710 		for (int i = 0; i < deleteLength; i++) {
711 			chNext = substance.ValueAt(position + i + 1);
712 			if (ch == '\r') {
713 				if (chNext != '\n') {
714 					RemoveLine(lineRemove);
715 				}
716 			} else if (ch == '\n') {
717 				if (ignoreNL) {
718 					ignoreNL = false; 	// Further \n are real deletions
719 				} else {
720 					RemoveLine(lineRemove);
721 				}
722 			} else if (utf8LineEnds) {
723 				if (!UTF8IsAscii(ch)) {
724 					unsigned char next3[3] = {ch, chNext,
725 						static_cast<unsigned char>(substance.ValueAt(position + i + 2))};
726 					if (UTF8IsSeparator(next3) || UTF8IsNEL(next3)) {
727 						RemoveLine(lineRemove);
728 					}
729 				}
730 			}
731 
732 			ch = chNext;
733 		}
734 		// May have to fix up end if last deletion causes cr to be next to lf
735 		// or removes one of a crlf pair
736 		char chAfter = substance.ValueAt(position + deleteLength);
737 		if (chBefore == '\r' && chAfter == '\n') {
738 			// Using lineRemove-1 as cr ended line before start of deletion
739 			RemoveLine(lineRemove - 1);
740 			lv.SetLineStart(lineRemove - 1, position + 1);
741 		}
742 	}
743 	substance.DeleteRange(position, deleteLength);
744 	style.DeleteRange(position, deleteLength);
745 }
746 
SetUndoCollection(bool collectUndo)747 bool CellBuffer::SetUndoCollection(bool collectUndo) {
748 	collectingUndo = collectUndo;
749 	uh.DropUndoSequence();
750 	return collectingUndo;
751 }
752 
IsCollectingUndo() const753 bool CellBuffer::IsCollectingUndo() const {
754 	return collectingUndo;
755 }
756 
BeginUndoAction()757 void CellBuffer::BeginUndoAction() {
758 	uh.BeginUndoAction();
759 }
760 
EndUndoAction()761 void CellBuffer::EndUndoAction() {
762 	uh.EndUndoAction();
763 }
764 
AddUndoAction(int token,bool mayCoalesce)765 void CellBuffer::AddUndoAction(int token, bool mayCoalesce) {
766 	bool startSequence;
767 	uh.AppendAction(containerAction, token, 0, 0, startSequence, mayCoalesce);
768 }
769 
DeleteUndoHistory()770 void CellBuffer::DeleteUndoHistory() {
771 	uh.DeleteUndoHistory();
772 }
773 
CanUndo() const774 bool CellBuffer::CanUndo() const {
775 	return uh.CanUndo();
776 }
777 
StartUndo()778 int CellBuffer::StartUndo() {
779 	return uh.StartUndo();
780 }
781 
GetUndoStep() const782 const Action &CellBuffer::GetUndoStep() const {
783 	return uh.GetUndoStep();
784 }
785 
PerformUndoStep()786 void CellBuffer::PerformUndoStep() {
787 	const Action &actionStep = uh.GetUndoStep();
788 	if (actionStep.at == insertAction) {
789 		BasicDeleteChars(actionStep.position, actionStep.lenData);
790 	} else if (actionStep.at == removeAction) {
791 		BasicInsertString(actionStep.position, actionStep.data, actionStep.lenData);
792 	}
793 	uh.CompletedUndoStep();
794 }
795 
CanRedo() const796 bool CellBuffer::CanRedo() const {
797 	return uh.CanRedo();
798 }
799 
StartRedo()800 int CellBuffer::StartRedo() {
801 	return uh.StartRedo();
802 }
803 
GetRedoStep() const804 const Action &CellBuffer::GetRedoStep() const {
805 	return uh.GetRedoStep();
806 }
807 
PerformRedoStep()808 void CellBuffer::PerformRedoStep() {
809 	const Action &actionStep = uh.GetRedoStep();
810 	if (actionStep.at == insertAction) {
811 		BasicInsertString(actionStep.position, actionStep.data, actionStep.lenData);
812 	} else if (actionStep.at == removeAction) {
813 		BasicDeleteChars(actionStep.position, actionStep.lenData);
814 	}
815 	uh.CompletedRedoStep();
816 }
817 
818