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