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 const char *CellBuffer::RangePointer(int position
, int rangeLength
) {
379 return substance
.RangePointer(position
, rangeLength
);
382 int CellBuffer::GapPosition() const {
383 return substance
.GapPosition();
386 // The char* returned is to an allocation owned by the undo history
387 const char *CellBuffer::InsertString(int position
, const char *s
, int insertLength
, bool &startSequence
) {
389 // InsertString and DeleteChars are the bottleneck though which all changes occur
391 if (collectingUndo
) {
392 // Save into the undo/redo stack, but only the characters - not the formatting
393 // This takes up about half load time
394 data
= new char[insertLength
];
395 for (int i
= 0; i
< insertLength
; i
++) {
398 uh
.AppendAction(insertAction
, position
, data
, insertLength
, startSequence
);
401 BasicInsertString(position
, s
, insertLength
);
406 bool CellBuffer::SetStyleAt(int position
, char styleValue
, char mask
) {
408 char curVal
= style
.ValueAt(position
);
409 if ((curVal
& mask
) != styleValue
) {
410 style
.SetValueAt(position
, static_cast<char>((curVal
& ~mask
) | styleValue
));
417 bool CellBuffer::SetStyleFor(int position
, int lengthStyle
, char styleValue
, char mask
) {
418 bool changed
= false;
419 PLATFORM_ASSERT(lengthStyle
== 0 ||
420 (lengthStyle
> 0 && lengthStyle
+ position
<= style
.Length()));
421 while (lengthStyle
--) {
422 char curVal
= style
.ValueAt(position
);
423 if ((curVal
& mask
) != styleValue
) {
424 style
.SetValueAt(position
, static_cast<char>((curVal
& ~mask
) | styleValue
));
432 // The char* returned is to an allocation owned by the undo history
433 const char *CellBuffer::DeleteChars(int position
, int deleteLength
, bool &startSequence
) {
434 // InsertString and DeleteChars are the bottleneck though which all changes occur
435 PLATFORM_ASSERT(deleteLength
> 0);
438 if (collectingUndo
) {
439 // Save into the undo/redo stack, but only the characters - not the formatting
440 data
= new char[deleteLength
];
441 for (int i
= 0; i
< deleteLength
; i
++) {
442 data
[i
] = substance
.ValueAt(position
+ i
);
444 uh
.AppendAction(removeAction
, position
, data
, deleteLength
, startSequence
);
447 BasicDeleteChars(position
, deleteLength
);
452 int CellBuffer::Length() const {
453 return substance
.Length();
456 void CellBuffer::Allocate(int newSize
) {
457 substance
.ReAllocate(newSize
);
458 style
.ReAllocate(newSize
);
461 void CellBuffer::SetPerLine(PerLine
*pl
) {
465 int CellBuffer::Lines() const {
469 int CellBuffer::LineStart(int line
) const {
472 else if (line
>= Lines())
475 return lv
.LineStart(line
);
478 bool CellBuffer::IsReadOnly() const {
482 void CellBuffer::SetReadOnly(bool set
) {
486 void CellBuffer::SetSavePoint() {
490 bool CellBuffer::IsSavePoint() {
491 return uh
.IsSavePoint();
496 void CellBuffer::InsertLine(int line
, int position
, bool lineStart
) {
497 lv
.InsertLine(line
, position
, lineStart
);
500 void CellBuffer::RemoveLine(int line
) {
504 void CellBuffer::BasicInsertString(int position
, const char *s
, int insertLength
) {
505 if (insertLength
== 0)
507 PLATFORM_ASSERT(insertLength
> 0);
509 substance
.InsertFromArray(position
, s
, 0, insertLength
);
510 style
.InsertValue(position
, insertLength
, 0);
512 int lineInsert
= lv
.LineFromPosition(position
) + 1;
513 bool atLineStart
= lv
.LineStart(lineInsert
-1) == position
;
514 // Point all the lines after the insertion point further along in the buffer
515 lv
.InsertText(lineInsert
-1, insertLength
);
516 char chPrev
= substance
.ValueAt(position
- 1);
517 char chAfter
= substance
.ValueAt(position
+ insertLength
);
518 if (chPrev
== '\r' && chAfter
== '\n') {
519 // Splitting up a crlf pair at position
520 InsertLine(lineInsert
, position
, false);
524 for (int i
= 0; i
< insertLength
; i
++) {
527 InsertLine(lineInsert
, (position
+ i
) + 1, atLineStart
);
529 } else if (ch
== '\n') {
530 if (chPrev
== '\r') {
531 // Patch up what was end of line
532 lv
.SetLineStart(lineInsert
- 1, (position
+ i
) + 1);
534 InsertLine(lineInsert
, (position
+ i
) + 1, atLineStart
);
540 // Joining two lines where last insertion is cr and following substance starts with lf
541 if (chAfter
== '\n') {
543 // End of line already in buffer so drop the newly created one
544 RemoveLine(lineInsert
- 1);
549 void CellBuffer::BasicDeleteChars(int position
, int deleteLength
) {
550 if (deleteLength
== 0)
553 if ((position
== 0) && (deleteLength
== substance
.Length())) {
554 // If whole buffer is being deleted, faster to reinitialise lines data
555 // than to delete each line.
558 // Have to fix up line positions before doing deletion as looking at text in buffer
559 // to work out which lines have been removed
561 int lineRemove
= lv
.LineFromPosition(position
) + 1;
562 lv
.InsertText(lineRemove
-1, - (deleteLength
));
563 char chPrev
= substance
.ValueAt(position
- 1);
564 char chBefore
= chPrev
;
565 char chNext
= substance
.ValueAt(position
);
566 bool ignoreNL
= false;
567 if (chPrev
== '\r' && chNext
== '\n') {
569 lv
.SetLineStart(lineRemove
, position
);
571 ignoreNL
= true; // First \n is not real deletion
575 for (int i
= 0; i
< deleteLength
; i
++) {
576 chNext
= substance
.ValueAt(position
+ i
+ 1);
578 if (chNext
!= '\n') {
579 RemoveLine(lineRemove
);
581 } else if (ch
== '\n') {
583 ignoreNL
= false; // Further \n are real deletions
585 RemoveLine(lineRemove
);
591 // May have to fix up end if last deletion causes cr to be next to lf
592 // or removes one of a crlf pair
593 char chAfter
= substance
.ValueAt(position
+ deleteLength
);
594 if (chBefore
== '\r' && chAfter
== '\n') {
595 // Using lineRemove-1 as cr ended line before start of deletion
596 RemoveLine(lineRemove
- 1);
597 lv
.SetLineStart(lineRemove
- 1, position
+ 1);
600 substance
.DeleteRange(position
, deleteLength
);
601 style
.DeleteRange(position
, deleteLength
);
604 bool CellBuffer::SetUndoCollection(bool collectUndo
) {
605 collectingUndo
= collectUndo
;
606 uh
.DropUndoSequence();
607 return collectingUndo
;
610 bool CellBuffer::IsCollectingUndo() const {
611 return collectingUndo
;
614 void CellBuffer::BeginUndoAction() {
615 uh
.BeginUndoAction();
618 void CellBuffer::EndUndoAction() {
622 void CellBuffer::AddUndoAction(int token
, bool mayCoalesce
) {
624 uh
.AppendAction(containerAction
, token
, 0, 0, startSequence
, mayCoalesce
);
627 void CellBuffer::DeleteUndoHistory() {
628 uh
.DeleteUndoHistory();
631 bool CellBuffer::CanUndo() {
635 int CellBuffer::StartUndo() {
636 return uh
.StartUndo();
639 const Action
&CellBuffer::GetUndoStep() const {
640 return uh
.GetUndoStep();
643 void CellBuffer::PerformUndoStep() {
644 const Action
&actionStep
= uh
.GetUndoStep();
645 if (actionStep
.at
== insertAction
) {
646 BasicDeleteChars(actionStep
.position
, actionStep
.lenData
);
647 } else if (actionStep
.at
== removeAction
) {
648 BasicInsertString(actionStep
.position
, actionStep
.data
, actionStep
.lenData
);
650 uh
.CompletedUndoStep();
653 bool CellBuffer::CanRedo() {
657 int CellBuffer::StartRedo() {
658 return uh
.StartRedo();
661 const Action
&CellBuffer::GetRedoStep() const {
662 return uh
.GetRedoStep();
665 void CellBuffer::PerformRedoStep() {
666 const Action
&actionStep
= uh
.GetRedoStep();
667 if (actionStep
.at
== insertAction
) {
668 BasicInsertString(actionStep
.position
, actionStep
.data
, actionStep
.lenData
);
669 } else if (actionStep
.at
== removeAction
) {
670 BasicDeleteChars(actionStep
.position
, actionStep
.lenData
);
672 uh
.CompletedRedoStep();