1
// Scintilla source code edit control
2
/** @file CellBuffer.cxx
3
** Manages a buffer of cells.
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.
15
#include "Scintilla.h"
16
#include "SplitVector.h"
17
#include "Partitioning.h"
18
#include "CellBuffer.h"
21
using namespace Scintilla;
24
LineVector::LineVector() : starts(256), perLine(0) {
28
LineVector::~LineVector() {
32
void LineVector::Init() {
39
void LineVector::SetPerLine(PerLine *pl) {
43
void LineVector::InsertText(int line, int delta) {
44
starts.InsertText(line, delta);
47
void LineVector::InsertLine(int line, int position, bool lineStart) {
48
starts.InsertPartition(line, position);
50
if ((line > 0) && lineStart)
52
perLine->InsertLine(line);
56
void LineVector::SetLineStart(int line, int position) {
57
starts.SetPartitionStartPosition(line, position);
60
void LineVector::RemoveLine(int line) {
61
starts.RemovePartition(line);
63
perLine->RemoveLine(line);
67
int LineVector::LineFromPosition(int pos) const {
68
return starts.PartitionFromPosition(pos);
83
void Action::Create(actionType at_, int position_, char *data_, int lenData_, bool mayCoalesce_) {
89
mayCoalesce = mayCoalesce_;
92
void Action::Destroy() {
97
void Action::Grab(Action *source) {
100
position = source->position;
103
lenData = source->lenData;
104
mayCoalesce = source->mayCoalesce;
106
// Ownership of source data transferred to this
107
source->position = 0;
108
source->at = startAction;
111
source->mayCoalesce = true;
114
// The undo history stores a sequence of user operations that represent the user's view of the
115
// commands executed on the text.
116
// Each user operation contains a sequence of text insertion and text deletion actions.
117
// All the user operations are stored in a list of individual actions with 'start' actions used
118
// as delimiters between user operations.
119
// Initially there is one start action in the history.
120
// As each action is performed, it is recorded in the history. The action may either become
121
// part of the current user operation or may start a new user operation. If it is to be part of the
122
// current operation, then it overwrites the current last action. If it is to be part of a new
123
// operation, it is appended after the current last action.
124
// After writing the new action, a new start action is appended at the end of the history.
125
// The decision of whether to start a new user operation is based upon two factors. If a
126
// compound operation has been explicitly started by calling BeginUndoAction and no matching
127
// EndUndoAction (these calls nest) has been called, then the action is coalesced into the current
128
// operation. If there is no outstanding BeginUndoAction call then a new operation is started
129
// unless it looks as if the new action is caused by the user typing or deleting a stream of text.
130
// Sequences that look like typing or deletion are coalesced into a single user operation.
132
UndoHistory::UndoHistory() {
135
actions = new Action[lenActions];
138
undoSequenceDepth = 0;
141
actions[currentAction].Create(startAction);
144
UndoHistory::~UndoHistory() {
149
void UndoHistory::EnsureUndoRoom() {
150
// Have to test that there is room for 2 more actions in the array
151
// as two actions may be created by the calling function
152
if (currentAction >= (lenActions - 2)) {
153
// Run out of undo nodes so extend the array
154
int lenActionsNew = lenActions * 2;
155
Action *actionsNew = new Action[lenActionsNew];
156
for (int act = 0; act <= currentAction; act++)
157
actionsNew[act].Grab(&actions[act]);
159
lenActions = lenActionsNew;
160
actions = actionsNew;
164
void UndoHistory::AppendAction(actionType at, int position, char *data, int lengthData,
165
bool &startSequence, bool mayCoalesce) {
167
//Platform::DebugPrintf("%% %d action %d %d %d\n", at, position, lengthData, currentAction);
168
//Platform::DebugPrintf("^ %d action %d %d\n", actions[currentAction - 1].at,
169
// actions[currentAction - 1].position, actions[currentAction - 1].lenData);
170
if (currentAction < savePoint) {
173
int oldCurrentAction = currentAction;
174
if (currentAction >= 1) {
175
if (0 == undoSequenceDepth) {
176
// Top level actions may not always be coalesced
178
const Action *actPrevious = &(actions[currentAction + targetAct]);
179
// Container actions may forward the coalesce state of Scintilla Actions.
180
while ((actPrevious->at == containerAction) && actPrevious->mayCoalesce) {
182
actPrevious = &(actions[currentAction + targetAct]);
184
// See if current action can be coalesced into previous action
185
// Will work if both are inserts or deletes and position is same
186
if (currentAction == savePoint) {
188
} else if (!actions[currentAction].mayCoalesce) {
189
// Not allowed to coalesce if this set
191
} else if (!mayCoalesce || !actPrevious->mayCoalesce) {
193
} else if (at == containerAction || actions[currentAction].at == containerAction) {
194
; // A coalescible containerAction
195
} else if ((at != actPrevious->at) && (actPrevious->at != startAction)) {
197
} else if ((at == insertAction) &&
198
(position != (actPrevious->position + actPrevious->lenData))) {
199
// Insertions must be immediately after to coalesce
201
} else if (at == removeAction) {
202
if ((lengthData == 1) || (lengthData == 2)) {
203
if ((position + lengthData) == actPrevious->position) {
205
} else if (position == actPrevious->position) {
208
// Removals must be at same position to coalesce
212
// Removals must be of one character to coalesce
220
// Actions not at top level are always coalesced unless this is after return to top level
221
if (!actions[currentAction].mayCoalesce)
227
startSequence = oldCurrentAction != currentAction;
228
actions[currentAction].Create(at, position, data, lengthData, mayCoalesce);
230
actions[currentAction].Create(startAction);
231
maxAction = currentAction;
234
void UndoHistory::BeginUndoAction() {
236
if (undoSequenceDepth == 0) {
237
if (actions[currentAction].at != startAction) {
239
actions[currentAction].Create(startAction);
240
maxAction = currentAction;
242
actions[currentAction].mayCoalesce = false;
247
void UndoHistory::EndUndoAction() {
248
PLATFORM_ASSERT(undoSequenceDepth > 0);
251
if (0 == undoSequenceDepth) {
252
if (actions[currentAction].at != startAction) {
254
actions[currentAction].Create(startAction);
255
maxAction = currentAction;
257
actions[currentAction].mayCoalesce = false;
261
void UndoHistory::DropUndoSequence() {
262
undoSequenceDepth = 0;
265
void UndoHistory::DeleteUndoHistory() {
266
for (int i = 1; i < maxAction; i++)
267
actions[i].Destroy();
270
actions[currentAction].Create(startAction);
274
void UndoHistory::SetSavePoint() {
275
savePoint = currentAction;
278
bool UndoHistory::IsSavePoint() const {
279
return savePoint == currentAction;
282
bool UndoHistory::CanUndo() const {
283
return (currentAction > 0) && (maxAction > 0);
286
int UndoHistory::StartUndo() {
287
// Drop any trailing startAction
288
if (actions[currentAction].at == startAction && currentAction > 0)
291
// Count the steps in this action
292
int act = currentAction;
293
while (actions[act].at != startAction && act > 0) {
296
return currentAction - act;
299
const Action &UndoHistory::GetUndoStep() const {
300
return actions[currentAction];
303
void UndoHistory::CompletedUndoStep() {
307
bool UndoHistory::CanRedo() const {
308
return maxAction > currentAction;
311
int UndoHistory::StartRedo() {
312
// Drop any leading startAction
313
if (actions[currentAction].at == startAction && currentAction < maxAction)
316
// Count the steps in this action
317
int act = currentAction;
318
while (actions[act].at != startAction && act < maxAction) {
321
return act - currentAction;
324
const Action &UndoHistory::GetRedoStep() const {
325
return actions[currentAction];
328
void UndoHistory::CompletedRedoStep() {
332
CellBuffer::CellBuffer() {
334
collectingUndo = true;
337
CellBuffer::~CellBuffer() {
340
char CellBuffer::CharAt(int position) const {
341
return substance.ValueAt(position);
344
void CellBuffer::GetCharRange(char *buffer, int position, int lengthRetrieve) const {
345
if (lengthRetrieve < 0)
349
if ((position + lengthRetrieve) > substance.Length()) {
350
Platform::DebugPrintf("Bad GetCharRange %d for %d of %d\n", position,
351
lengthRetrieve, substance.Length());
354
substance.GetRange(buffer, position, lengthRetrieve);
357
char CellBuffer::StyleAt(int position) const {
358
return style.ValueAt(position);
361
void CellBuffer::GetStyleRange(unsigned char *buffer, int position, int lengthRetrieve) const {
362
if (lengthRetrieve < 0)
366
if ((position + lengthRetrieve) > style.Length()) {
367
Platform::DebugPrintf("Bad GetStyleRange %d for %d of %d\n", position,
368
lengthRetrieve, style.Length());
371
style.GetRange(reinterpret_cast<char *>(buffer), position, lengthRetrieve);
374
const char *CellBuffer::BufferPointer() {
375
return substance.BufferPointer();
378
// The char* returned is to an allocation owned by the undo history
379
const char *CellBuffer::InsertString(int position, const char *s, int insertLength, bool &startSequence) {
381
// InsertString and DeleteChars are the bottleneck though which all changes occur
383
if (collectingUndo) {
384
// Save into the undo/redo stack, but only the characters - not the formatting
385
// This takes up about half load time
386
data = new char[insertLength];
387
for (int i = 0; i < insertLength; i++) {
390
uh.AppendAction(insertAction, position, data, insertLength, startSequence);
393
BasicInsertString(position, s, insertLength);
398
bool CellBuffer::SetStyleAt(int position, char styleValue, char mask) {
400
char curVal = style.ValueAt(position);
401
if ((curVal & mask) != styleValue) {
402
style.SetValueAt(position, static_cast<char>((curVal & ~mask) | styleValue));
409
bool CellBuffer::SetStyleFor(int position, int lengthStyle, char styleValue, char mask) {
410
bool changed = false;
411
PLATFORM_ASSERT(lengthStyle == 0 ||
412
(lengthStyle > 0 && lengthStyle + position <= style.Length()));
413
while (lengthStyle--) {
414
char curVal = style.ValueAt(position);
415
if ((curVal & mask) != styleValue) {
416
style.SetValueAt(position, static_cast<char>((curVal & ~mask) | styleValue));
424
// The char* returned is to an allocation owned by the undo history
425
const char *CellBuffer::DeleteChars(int position, int deleteLength, bool &startSequence) {
426
// InsertString and DeleteChars are the bottleneck though which all changes occur
427
PLATFORM_ASSERT(deleteLength > 0);
430
if (collectingUndo) {
431
// Save into the undo/redo stack, but only the characters - not the formatting
432
data = new char[deleteLength];
433
for (int i = 0; i < deleteLength; i++) {
434
data[i] = substance.ValueAt(position + i);
436
uh.AppendAction(removeAction, position, data, deleteLength, startSequence);
439
BasicDeleteChars(position, deleteLength);
444
int CellBuffer::Length() const {
445
return substance.Length();
448
void CellBuffer::Allocate(int newSize) {
449
substance.ReAllocate(newSize);
450
style.ReAllocate(newSize);
453
void CellBuffer::SetPerLine(PerLine *pl) {
457
int CellBuffer::Lines() const {
461
int CellBuffer::LineStart(int line) const {
464
else if (line >= Lines())
467
return lv.LineStart(line);
470
bool CellBuffer::IsReadOnly() const {
474
void CellBuffer::SetReadOnly(bool set) {
478
void CellBuffer::SetSavePoint() {
482
bool CellBuffer::IsSavePoint() {
483
return uh.IsSavePoint();
488
void CellBuffer::InsertLine(int line, int position, bool lineStart) {
489
lv.InsertLine(line, position, lineStart);
492
void CellBuffer::RemoveLine(int line) {
496
void CellBuffer::BasicInsertString(int position, const char *s, int insertLength) {
497
if (insertLength == 0)
499
PLATFORM_ASSERT(insertLength > 0);
501
substance.InsertFromArray(position, s, 0, insertLength);
502
style.InsertValue(position, insertLength, 0);
504
int lineInsert = lv.LineFromPosition(position) + 1;
505
bool atLineStart = lv.LineStart(lineInsert-1) == position;
506
// Point all the lines after the insertion point further along in the buffer
507
lv.InsertText(lineInsert-1, insertLength);
508
char chPrev = substance.ValueAt(position - 1);
509
char chAfter = substance.ValueAt(position + insertLength);
510
if (chPrev == '\r' && chAfter == '\n') {
511
// Splitting up a crlf pair at position
512
InsertLine(lineInsert, position, false);
516
for (int i = 0; i < insertLength; i++) {
519
InsertLine(lineInsert, (position + i) + 1, atLineStart);
521
} else if (ch == '\n') {
522
if (chPrev == '\r') {
523
// Patch up what was end of line
524
lv.SetLineStart(lineInsert - 1, (position + i) + 1);
526
InsertLine(lineInsert, (position + i) + 1, atLineStart);
532
// Joining two lines where last insertion is cr and following substance starts with lf
533
if (chAfter == '\n') {
535
// End of line already in buffer so drop the newly created one
536
RemoveLine(lineInsert - 1);
541
void CellBuffer::BasicDeleteChars(int position, int deleteLength) {
542
if (deleteLength == 0)
545
if ((position == 0) && (deleteLength == substance.Length())) {
546
// If whole buffer is being deleted, faster to reinitialise lines data
547
// than to delete each line.
550
// Have to fix up line positions before doing deletion as looking at text in buffer
551
// to work out which lines have been removed
553
int lineRemove = lv.LineFromPosition(position) + 1;
554
lv.InsertText(lineRemove-1, - (deleteLength));
555
char chPrev = substance.ValueAt(position - 1);
556
char chBefore = chPrev;
557
char chNext = substance.ValueAt(position);
558
bool ignoreNL = false;
559
if (chPrev == '\r' && chNext == '\n') {
561
lv.SetLineStart(lineRemove, position);
563
ignoreNL = true; // First \n is not real deletion
567
for (int i = 0; i < deleteLength; i++) {
568
chNext = substance.ValueAt(position + i + 1);
570
if (chNext != '\n') {
571
RemoveLine(lineRemove);
573
} else if (ch == '\n') {
575
ignoreNL = false; // Further \n are real deletions
577
RemoveLine(lineRemove);
583
// May have to fix up end if last deletion causes cr to be next to lf
584
// or removes one of a crlf pair
585
char chAfter = substance.ValueAt(position + deleteLength);
586
if (chBefore == '\r' && chAfter == '\n') {
587
// Using lineRemove-1 as cr ended line before start of deletion
588
RemoveLine(lineRemove - 1);
589
lv.SetLineStart(lineRemove - 1, position + 1);
592
substance.DeleteRange(position, deleteLength);
593
style.DeleteRange(position, deleteLength);
596
bool CellBuffer::SetUndoCollection(bool collectUndo) {
597
collectingUndo = collectUndo;
598
uh.DropUndoSequence();
599
return collectingUndo;
602
bool CellBuffer::IsCollectingUndo() const {
603
return collectingUndo;
606
void CellBuffer::BeginUndoAction() {
607
uh.BeginUndoAction();
610
void CellBuffer::EndUndoAction() {
614
void CellBuffer::AddUndoAction(int token, bool mayCoalesce) {
616
uh.AppendAction(containerAction, token, 0, 0, startSequence, mayCoalesce);
619
void CellBuffer::DeleteUndoHistory() {
620
uh.DeleteUndoHistory();
623
bool CellBuffer::CanUndo() {
627
int CellBuffer::StartUndo() {
628
return uh.StartUndo();
631
const Action &CellBuffer::GetUndoStep() const {
632
return uh.GetUndoStep();
635
void CellBuffer::PerformUndoStep() {
636
const Action &actionStep = uh.GetUndoStep();
637
if (actionStep.at == insertAction) {
638
BasicDeleteChars(actionStep.position, actionStep.lenData);
639
} else if (actionStep.at == removeAction) {
640
BasicInsertString(actionStep.position, actionStep.data, actionStep.lenData);
642
uh.CompletedUndoStep();
645
bool CellBuffer::CanRedo() {
649
int CellBuffer::StartRedo() {
650
return uh.StartRedo();
653
const Action &CellBuffer::GetRedoStep() const {
654
return uh.GetRedoStep();
657
void CellBuffer::PerformRedoStep() {
658
const Action &actionStep = uh.GetRedoStep();
659
if (actionStep.at == insertAction) {
660
BasicInsertString(actionStep.position, actionStep.data, actionStep.lenData);
661
} else if (actionStep.at == removeAction) {
662
BasicDeleteChars(actionStep.position, actionStep.lenData);
664
uh.CompletedRedoStep();