aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorNeil <nyamatongwe@gmail.com>2024-02-01 08:56:45 +1100
committerNeil <nyamatongwe@gmail.com>2024-02-01 08:56:45 +1100
commitc6b542f84e65083552e52768db7bb4c11dcd7a64 (patch)
treee97a615381f422e67df985b18b3a50a56cb5590b /src
parent1345bb5b0e10e213fb1943fe491679d6fe45e9b0 (diff)
downloadscintilla-mirror-c6b542f84e65083552e52768db7bb4c11dcd7a64.tar.gz
Move UndoHistory into its own module that is accessible from CellBuffer and
tests but hidden from most of Scintilla. Access through std::unique_ptr.
Diffstat (limited to 'src')
-rw-r--r--src/CellBuffer.cxx322
-rw-r--r--src/CellBuffer.h55
-rw-r--r--src/UndoHistory.cxx295
-rw-r--r--src/UndoHistory.h66
4 files changed, 398 insertions, 340 deletions
diff --git a/src/CellBuffer.cxx b/src/CellBuffer.cxx
index 28df64dc1..9d988fb29 100644
--- a/src/CellBuffer.cxx
+++ b/src/CellBuffer.cxx
@@ -32,6 +32,7 @@
#include "SparseVector.h"
#include "ChangeHistory.h"
#include "CellBuffer.h"
+#include "UndoHistory.h"
#include "UniConversion.h"
namespace Scintilla::Internal {
@@ -348,268 +349,13 @@ void Action::Clear() noexcept {
lenData = 0;
}
-// The undo history stores a sequence of user operations that represent the user's view of the
-// commands executed on the text.
-// Each user operation contains a sequence of text insertion and text deletion actions.
-// All the user operations are stored in a list of individual actions with 'start' actions used
-// as delimiters between user operations.
-// Initially there is one start action in the history.
-// As each action is performed, it is recorded in the history. The action may either become
-// part of the current user operation or may start a new user operation. If it is to be part of the
-// current operation, then it overwrites the current last action. If it is to be part of a new
-// operation, it is appended after the current last action.
-// After writing the new action, a new start action is appended at the end of the history.
-// The decision of whether to start a new user operation is based upon two factors. If a
-// compound operation has been explicitly started by calling BeginUndoAction and no matching
-// EndUndoAction (these calls nest) has been called, then the action is coalesced into the current
-// operation. If there is no outstanding BeginUndoAction call then a new operation is started
-// unless it looks as if the new action is caused by the user typing or deleting a stream of text.
-// Sequences that look like typing or deletion are coalesced into a single user operation.
-
-UndoHistory::UndoHistory() {
-
- actions.resize(3);
- maxAction = 0;
- currentAction = 0;
- undoSequenceDepth = 0;
- savePoint = 0;
- tentativePoint = -1;
-
- actions[currentAction].Create(ActionType::start);
-}
-
-void UndoHistory::EnsureUndoRoom() {
- // Have to test that there is room for 2 more actions in the array
- // as two actions may be created by the calling function
- if (static_cast<size_t>(currentAction) >= (actions.size() - 2)) {
- // Run out of undo nodes so extend the array
- actions.resize(actions.size() * 2);
- }
-}
-
-const char *UndoHistory::AppendAction(ActionType at, Sci::Position position, const char *data, Sci::Position lengthData,
- bool &startSequence, bool mayCoalesce) {
- EnsureUndoRoom();
- //Platform::DebugPrintf("%% %d action %d %d %d\n", at, position, lengthData, currentAction);
- //Platform::DebugPrintf("^ %d action %d %d\n", actions[currentAction - 1].at,
- // actions[currentAction - 1].position, actions[currentAction - 1].lenData);
- if (currentAction < savePoint) {
- savePoint = -1;
- if (!detach) {
- detach = currentAction;
- }
- } else if (detach && (*detach > currentAction)) {
- detach = currentAction;
- }
- const int oldCurrentAction = currentAction;
- if (currentAction >= 1) {
- if (0 == undoSequenceDepth) {
- // Top level actions may not always be coalesced
- ptrdiff_t targetAct = -1;
- const Action *actPrevious = &(actions[currentAction + targetAct]);
- // Container actions may forward the coalesce state of Scintilla Actions.
- while ((actPrevious->at == ActionType::container) && actPrevious->mayCoalesce) {
- targetAct--;
- actPrevious = &(actions[currentAction + targetAct]);
- }
- // See if current action can be coalesced into previous action
- // Will work if both are inserts or deletes and position is same
- if ((currentAction == savePoint) || (currentAction == tentativePoint)) {
- currentAction++;
- } else if (!actions[currentAction].mayCoalesce) {
- // Not allowed to coalesce if this set
- currentAction++;
- } else if (!mayCoalesce || !actPrevious->mayCoalesce) {
- currentAction++;
- } else if (at == ActionType::container || actions[currentAction].at == ActionType::container) {
- ; // A coalescible containerAction
- } else if ((at != actPrevious->at) && (actPrevious->at != ActionType::start)) {
- currentAction++;
- } else if ((at == ActionType::insert) &&
- (position != (actPrevious->position + actPrevious->lenData))) {
- // Insertions must be immediately after to coalesce
- currentAction++;
- } else if (at == ActionType::remove) {
- if ((lengthData == 1) || (lengthData == 2)) {
- if ((position + lengthData) == actPrevious->position) {
- ; // Backspace -> OK
- } else if (position == actPrevious->position) {
- ; // Delete -> OK
- } else {
- // Removals must be at same position to coalesce
- currentAction++;
- }
- } else {
- // Removals must be of one character to coalesce
- currentAction++;
- }
- } else {
- // Action coalesced.
- }
-
- } else {
- // Actions not at top level are always coalesced unless this is after return to top level
- if (!actions[currentAction].mayCoalesce)
- currentAction++;
- }
- } else {
- currentAction++;
- }
- startSequence = oldCurrentAction != currentAction;
- const int actionWithData = currentAction;
- actions[currentAction].Create(at, position, data, lengthData, mayCoalesce);
- currentAction++;
- actions[currentAction].Create(ActionType::start);
- maxAction = currentAction;
- return actions[actionWithData].data.get();
-}
-
-void UndoHistory::BeginUndoAction() {
- EnsureUndoRoom();
- if (undoSequenceDepth == 0) {
- if (actions[currentAction].at != ActionType::start) {
- currentAction++;
- actions[currentAction].Create(ActionType::start);
- maxAction = currentAction;
- }
- actions[currentAction].mayCoalesce = false;
- }
- undoSequenceDepth++;
-}
-
-void UndoHistory::EndUndoAction() {
- PLATFORM_ASSERT(undoSequenceDepth > 0);
- EnsureUndoRoom();
- undoSequenceDepth--;
- if (0 == undoSequenceDepth) {
- if (actions[currentAction].at != ActionType::start) {
- currentAction++;
- actions[currentAction].Create(ActionType::start);
- maxAction = currentAction;
- }
- actions[currentAction].mayCoalesce = false;
- }
-}
-
-void UndoHistory::DropUndoSequence() noexcept {
- undoSequenceDepth = 0;
-}
-
-void UndoHistory::DeleteUndoHistory() {
- for (int i = 1; i < maxAction; i++)
- actions[i].Clear();
- maxAction = 0;
- currentAction = 0;
- actions[currentAction].Create(ActionType::start);
- savePoint = 0;
- tentativePoint = -1;
-}
-
-void UndoHistory::SetSavePoint() noexcept {
- savePoint = currentAction;
- detach.reset();
-}
-
-bool UndoHistory::IsSavePoint() const noexcept {
- return savePoint == currentAction;
-}
-
-bool UndoHistory::BeforeSavePoint() const noexcept {
- return (savePoint < 0) || (savePoint > currentAction);
-}
-
-bool UndoHistory::BeforeReachableSavePoint() const noexcept {
- return (savePoint >= 0) && !detach && (savePoint > currentAction);
-}
-
-bool UndoHistory::AfterSavePoint() const noexcept {
- return (savePoint >= 0) && (savePoint <= currentAction);
-}
-
-bool UndoHistory::AfterDetachPoint() const noexcept {
- return detach && (*detach < currentAction);
-}
-
-void UndoHistory::TentativeStart() noexcept {
- tentativePoint = currentAction;
-}
-
-void UndoHistory::TentativeCommit() noexcept {
- tentativePoint = -1;
- // Truncate undo history
- maxAction = currentAction;
-}
-
-bool UndoHistory::TentativeActive() const noexcept {
- return tentativePoint >= 0;
-}
-
-int UndoHistory::TentativeSteps() noexcept {
- // Drop any trailing startAction
- if (actions[currentAction].at == ActionType::start && currentAction > 0)
- currentAction--;
- if (tentativePoint >= 0)
- return currentAction - tentativePoint;
- else
- return -1;
-}
-
-bool UndoHistory::CanUndo() const noexcept {
- return (currentAction > 0) && (maxAction > 0);
-}
-
-int UndoHistory::StartUndo() noexcept {
- // Drop any trailing startAction
- if (actions[currentAction].at == ActionType::start && currentAction > 0)
- currentAction--;
-
- // Count the steps in this action
- int act = currentAction;
- while (actions[act].at != ActionType::start && act > 0) {
- act--;
- }
- return currentAction - act;
-}
-
-const Action &UndoHistory::GetUndoStep() const noexcept {
- return actions[currentAction];
-}
-
-void UndoHistory::CompletedUndoStep() noexcept {
- currentAction--;
-}
-
-bool UndoHistory::CanRedo() const noexcept {
- return maxAction > currentAction;
-}
-
-int UndoHistory::StartRedo() noexcept {
- // Drop any leading startAction
- if (currentAction < maxAction && actions[currentAction].at == ActionType::start)
- currentAction++;
-
- // Count the steps in this action
- int act = currentAction;
- while (act < maxAction && actions[act].at != ActionType::start) {
- act++;
- }
- return act - currentAction;
-}
-
-const Action &UndoHistory::GetRedoStep() const noexcept {
- return actions[currentAction];
-}
-
-void UndoHistory::CompletedRedoStep() noexcept {
- currentAction++;
-}
-
CellBuffer::CellBuffer(bool hasStyles_, bool largeDocument_) :
hasStyles(hasStyles_), largeDocument(largeDocument_) {
readOnly = false;
utf8Substance = false;
utf8LineEnds = LineEndType::Default;
collectingUndo = true;
+ uh = std::make_unique<UndoHistory>();
if (largeDocument)
plv = std::make_unique<LineVector<Sci::Position>>();
else
@@ -699,12 +445,12 @@ const char *CellBuffer::InsertString(Sci::Position position, const char *s, Sci:
if (collectingUndo) {
// Save into the undo/redo stack, but only the characters - not the formatting
// This takes up about half load time
- data = uh.AppendAction(ActionType::insert, position, s, insertLength, startSequence);
+ data = uh->AppendAction(ActionType::insert, position, s, insertLength, startSequence);
}
BasicInsertString(position, s, insertLength);
if (changeHistory) {
- changeHistory->Insert(position, insertLength, collectingUndo, uh.BeforeReachableSavePoint());
+ changeHistory->Insert(position, insertLength, collectingUndo, uh->BeforeReachableSavePoint());
}
}
return data;
@@ -751,12 +497,12 @@ const char *CellBuffer::DeleteChars(Sci::Position position, Sci::Position delete
// Save into the undo/redo stack, but only the characters - not the formatting
// The gap would be moved to position anyway for the deletion so this doesn't cost extra
data = substance.RangePointer(position, deleteLength);
- data = uh.AppendAction(ActionType::remove, position, data, deleteLength, startSequence);
+ data = uh->AppendAction(ActionType::remove, position, data, deleteLength, startSequence);
}
if (changeHistory) {
changeHistory->DeleteRangeSavingHistory(position, deleteLength,
- uh.BeforeReachableSavePoint(), uh.AfterDetachPoint());
+ uh->BeforeReachableSavePoint(), uh->AfterDetachPoint());
}
BasicDeleteChars(position, deleteLength);
@@ -903,30 +649,30 @@ bool CellBuffer::HasStyles() const noexcept {
}
void CellBuffer::SetSavePoint() {
- uh.SetSavePoint();
+ uh->SetSavePoint();
if (changeHistory) {
changeHistory->SetSavePoint();
}
}
bool CellBuffer::IsSavePoint() const noexcept {
- return uh.IsSavePoint();
+ return uh->IsSavePoint();
}
void CellBuffer::TentativeStart() noexcept {
- uh.TentativeStart();
+ uh->TentativeStart();
}
void CellBuffer::TentativeCommit() noexcept {
- uh.TentativeCommit();
+ uh->TentativeCommit();
}
int CellBuffer::TentativeSteps() noexcept {
- return uh.TentativeSteps();
+ return uh->TentativeSteps();
}
bool CellBuffer::TentativeActive() const noexcept {
- return uh.TentativeActive();
+ return uh->TentativeActive();
}
// Without undo
@@ -1327,7 +1073,7 @@ void CellBuffer::BasicDeleteChars(Sci::Position position, Sci::Position deleteLe
bool CellBuffer::SetUndoCollection(bool collectUndo) noexcept {
collectingUndo = collectUndo;
- uh.DropUndoSequence();
+ uh->DropUndoSequence();
return collectingUndo;
}
@@ -1336,37 +1082,37 @@ bool CellBuffer::IsCollectingUndo() const noexcept {
}
void CellBuffer::BeginUndoAction() {
- uh.BeginUndoAction();
+ uh->BeginUndoAction();
}
void CellBuffer::EndUndoAction() {
- uh.EndUndoAction();
+ uh->EndUndoAction();
}
void CellBuffer::AddUndoAction(Sci::Position token, bool mayCoalesce) {
bool startSequence = false;
- uh.AppendAction(ActionType::container, token, nullptr, 0, startSequence, mayCoalesce);
+ uh->AppendAction(ActionType::container, token, nullptr, 0, startSequence, mayCoalesce);
}
void CellBuffer::DeleteUndoHistory() {
- uh.DeleteUndoHistory();
+ uh->DeleteUndoHistory();
}
bool CellBuffer::CanUndo() const noexcept {
- return uh.CanUndo();
+ return uh->CanUndo();
}
int CellBuffer::StartUndo() noexcept {
- return uh.StartUndo();
+ return uh->StartUndo();
}
const Action &CellBuffer::GetUndoStep() const noexcept {
- return uh.GetUndoStep();
+ return uh->GetUndoStep();
}
void CellBuffer::PerformUndoStep() {
- const Action &actionStep = uh.GetUndoStep();
- if (changeHistory && uh.BeforeSavePoint()) {
+ const Action &actionStep = uh->GetUndoStep();
+ if (changeHistory && uh->BeforeSavePoint()) {
changeHistory->StartReversion();
}
if (actionStep.at == ActionType::insert) {
@@ -1376,54 +1122,54 @@ void CellBuffer::PerformUndoStep() {
}
if (changeHistory) {
changeHistory->DeleteRange(actionStep.position, actionStep.lenData,
- uh.BeforeSavePoint() && !uh.AfterDetachPoint());
+ uh->BeforeSavePoint() && !uh->AfterDetachPoint());
}
BasicDeleteChars(actionStep.position, actionStep.lenData);
} else if (actionStep.at == ActionType::remove) {
BasicInsertString(actionStep.position, actionStep.data.get(), actionStep.lenData);
if (changeHistory) {
- changeHistory->UndoDeleteStep(actionStep.position, actionStep.lenData, uh.AfterDetachPoint());
+ changeHistory->UndoDeleteStep(actionStep.position, actionStep.lenData, uh->AfterDetachPoint());
}
}
- uh.CompletedUndoStep();
+ uh->CompletedUndoStep();
}
bool CellBuffer::CanRedo() const noexcept {
- return uh.CanRedo();
+ return uh->CanRedo();
}
int CellBuffer::StartRedo() noexcept {
- return uh.StartRedo();
+ return uh->StartRedo();
}
const Action &CellBuffer::GetRedoStep() const noexcept {
- return uh.GetRedoStep();
+ return uh->GetRedoStep();
}
void CellBuffer::PerformRedoStep() {
- const Action &actionStep = uh.GetRedoStep();
+ const Action &actionStep = uh->GetRedoStep();
if (actionStep.at == ActionType::insert) {
BasicInsertString(actionStep.position, actionStep.data.get(), actionStep.lenData);
if (changeHistory) {
changeHistory->Insert(actionStep.position, actionStep.lenData, collectingUndo,
- uh.BeforeSavePoint() && !uh.AfterDetachPoint());
+ uh->BeforeSavePoint() && !uh->AfterDetachPoint());
}
} else if (actionStep.at == ActionType::remove) {
if (changeHistory) {
changeHistory->DeleteRangeSavingHistory(actionStep.position, actionStep.lenData,
- uh.BeforeReachableSavePoint(), uh.AfterDetachPoint());
+ uh->BeforeReachableSavePoint(), uh->AfterDetachPoint());
}
BasicDeleteChars(actionStep.position, actionStep.lenData);
}
- if (changeHistory && uh.AfterSavePoint()) {
+ if (changeHistory && uh->AfterSavePoint()) {
changeHistory->EndReversion();
}
- uh.CompletedRedoStep();
+ uh->CompletedRedoStep();
}
void CellBuffer::ChangeHistorySet(bool set) {
if (set) {
- if (!changeHistory && !uh.CanUndo()) {
+ if (!changeHistory && !uh->CanUndo()) {
changeHistory = std::make_unique<ChangeHistory>(Length());
}
} else {
diff --git a/src/CellBuffer.h b/src/CellBuffer.h
index 701472f4f..d71287d1b 100644
--- a/src/CellBuffer.h
+++ b/src/CellBuffer.h
@@ -20,7 +20,9 @@ public:
virtual void RemoveLine(Sci::Line line)=0;
};
+class UndoHistory;
class ChangeHistory;
+
/**
* The line vector contains information about each of the lines in a cell buffer.
*/
@@ -44,57 +46,6 @@ public:
void Clear() noexcept;
};
-/**
- *
- */
-class UndoHistory {
- std::vector<Action> actions;
- int maxAction;
- int currentAction;
- int undoSequenceDepth;
- int savePoint;
- int tentativePoint;
- std::optional<int> detach;
-
- void EnsureUndoRoom();
-
-public:
- UndoHistory();
-
- const char *AppendAction(ActionType at, Sci::Position position, const char *data, Sci::Position lengthData, bool &startSequence, bool mayCoalesce=true);
-
- void BeginUndoAction();
- void EndUndoAction();
- void DropUndoSequence() noexcept;
- void DeleteUndoHistory();
-
- /// The save point is a marker in the undo stack where the container has stated that
- /// the buffer was saved. Undo and redo can move over the save point.
- void SetSavePoint() noexcept;
- bool IsSavePoint() const noexcept;
- bool BeforeSavePoint() const noexcept;
- bool BeforeReachableSavePoint() const noexcept;
- bool AfterSavePoint() const noexcept;
- bool AfterDetachPoint() const noexcept;
-
- // Tentative actions are used for input composition so that it can be undone cleanly
- void TentativeStart() noexcept;
- void TentativeCommit() noexcept;
- bool TentativeActive() const noexcept;
- int TentativeSteps() noexcept;
-
- /// To perform an undo, StartUndo is called to retrieve the number of steps, then UndoStep is
- /// called that many times. Similarly for redo.
- bool CanUndo() const noexcept;
- int StartUndo() noexcept;
- const Action &GetUndoStep() const noexcept;
- void CompletedUndoStep() noexcept;
- bool CanRedo() const noexcept;
- int StartRedo() noexcept;
- const Action &GetRedoStep() const noexcept;
- void CompletedRedoStep() noexcept;
-};
-
struct SplitView {
const char *segment1 = nullptr;
size_t length1 = 0;
@@ -137,7 +88,7 @@ private:
Scintilla::LineEndType utf8LineEnds;
bool collectingUndo;
- UndoHistory uh;
+ std::unique_ptr<UndoHistory> uh;
std::unique_ptr<ChangeHistory> changeHistory;
diff --git a/src/UndoHistory.cxx b/src/UndoHistory.cxx
new file mode 100644
index 000000000..6b8c65165
--- /dev/null
+++ b/src/UndoHistory.cxx
@@ -0,0 +1,295 @@
+// Scintilla source code edit control
+/** @file UndoHistory.cxx
+ ** Manages undo for the document.
+ **/
+// Copyright 1998-2024 by Neil Hodgson <neilh@scintilla.org>
+// The License.txt file describes the conditions under which this software may be distributed.
+
+#include <cstddef>
+#include <cstdlib>
+#include <cassert>
+#include <cstring>
+#include <cstdio>
+#include <cstdarg>
+#include <climits>
+
+#include <stdexcept>
+#include <string>
+#include <string_view>
+#include <vector>
+#include <optional>
+#include <algorithm>
+#include <memory>
+
+#include "ScintillaTypes.h"
+
+#include "Debugging.h"
+
+#include "Position.h"
+#include "SplitVector.h"
+#include "Partitioning.h"
+#include "RunStyles.h"
+#include "SparseVector.h"
+#include "ChangeHistory.h"
+#include "CellBuffer.h"
+#include "UndoHistory.h"
+
+namespace Scintilla::Internal {
+
+// The undo history stores a sequence of user operations that represent the user's view of the
+// commands executed on the text.
+// Each user operation contains a sequence of text insertion and text deletion actions.
+// All the user operations are stored in a list of individual actions with 'start' actions used
+// as delimiters between user operations.
+// Initially there is one start action in the history.
+// As each action is performed, it is recorded in the history. The action may either become
+// part of the current user operation or may start a new user operation. If it is to be part of the
+// current operation, then it overwrites the current last action. If it is to be part of a new
+// operation, it is appended after the current last action.
+// After writing the new action, a new start action is appended at the end of the history.
+// The decision of whether to start a new user operation is based upon two factors. If a
+// compound operation has been explicitly started by calling BeginUndoAction and no matching
+// EndUndoAction (these calls nest) has been called, then the action is coalesced into the current
+// operation. If there is no outstanding BeginUndoAction call then a new operation is started
+// unless it looks as if the new action is caused by the user typing or deleting a stream of text.
+// Sequences that look like typing or deletion are coalesced into a single user operation.
+
+UndoHistory::UndoHistory() {
+
+ actions.resize(3);
+ maxAction = 0;
+ currentAction = 0;
+ undoSequenceDepth = 0;
+ savePoint = 0;
+ tentativePoint = -1;
+
+ actions[currentAction].Create(ActionType::start);
+}
+
+void UndoHistory::EnsureUndoRoom() {
+ // Have to test that there is room for 2 more actions in the array
+ // as two actions may be created by the calling function
+ if (static_cast<size_t>(currentAction) >= (actions.size() - 2)) {
+ // Run out of undo nodes so extend the array
+ actions.resize(actions.size() * 2);
+ }
+}
+
+const char *UndoHistory::AppendAction(ActionType at, Sci::Position position, const char *data, Sci::Position lengthData,
+ bool &startSequence, bool mayCoalesce) {
+ EnsureUndoRoom();
+ //Platform::DebugPrintf("%% %d action %d %d %d\n", at, position, lengthData, currentAction);
+ //Platform::DebugPrintf("^ %d action %d %d\n", actions[currentAction - 1].at,
+ // actions[currentAction - 1].position, actions[currentAction - 1].lenData);
+ if (currentAction < savePoint) {
+ savePoint = -1;
+ if (!detach) {
+ detach = currentAction;
+ }
+ } else if (detach && (*detach > currentAction)) {
+ detach = currentAction;
+ }
+ const int oldCurrentAction = currentAction;
+ if (currentAction >= 1) {
+ if (0 == undoSequenceDepth) {
+ // Top level actions may not always be coalesced
+ ptrdiff_t targetAct = -1;
+ const Action *actPrevious = &(actions[currentAction + targetAct]);
+ // Container actions may forward the coalesce state of Scintilla Actions.
+ while ((actPrevious->at == ActionType::container) && actPrevious->mayCoalesce) {
+ targetAct--;
+ actPrevious = &(actions[currentAction + targetAct]);
+ }
+ // See if current action can be coalesced into previous action
+ // Will work if both are inserts or deletes and position is same
+ if ((currentAction == savePoint) || (currentAction == tentativePoint)) {
+ currentAction++;
+ } else if (!actions[currentAction].mayCoalesce) {
+ // Not allowed to coalesce if this set
+ currentAction++;
+ } else if (!mayCoalesce || !actPrevious->mayCoalesce) {
+ currentAction++;
+ } else if (at == ActionType::container || actions[currentAction].at == ActionType::container) {
+ ; // A coalescible containerAction
+ } else if ((at != actPrevious->at) && (actPrevious->at != ActionType::start)) {
+ currentAction++;
+ } else if ((at == ActionType::insert) &&
+ (position != (actPrevious->position + actPrevious->lenData))) {
+ // Insertions must be immediately after to coalesce
+ currentAction++;
+ } else if (at == ActionType::remove) {
+ if ((lengthData == 1) || (lengthData == 2)) {
+ if ((position + lengthData) == actPrevious->position) {
+ ; // Backspace -> OK
+ } else if (position == actPrevious->position) {
+ ; // Delete -> OK
+ } else {
+ // Removals must be at same position to coalesce
+ currentAction++;
+ }
+ } else {
+ // Removals must be of one character to coalesce
+ currentAction++;
+ }
+ } else {
+ // Action coalesced.
+ }
+
+ } else {
+ // Actions not at top level are always coalesced unless this is after return to top level
+ if (!actions[currentAction].mayCoalesce)
+ currentAction++;
+ }
+ } else {
+ currentAction++;
+ }
+ startSequence = oldCurrentAction != currentAction;
+ const int actionWithData = currentAction;
+ actions[currentAction].Create(at, position, data, lengthData, mayCoalesce);
+ currentAction++;
+ actions[currentAction].Create(ActionType::start);
+ maxAction = currentAction;
+ return actions[actionWithData].data.get();
+}
+
+void UndoHistory::BeginUndoAction() {
+ EnsureUndoRoom();
+ if (undoSequenceDepth == 0) {
+ if (actions[currentAction].at != ActionType::start) {
+ currentAction++;
+ actions[currentAction].Create(ActionType::start);
+ maxAction = currentAction;
+ }
+ actions[currentAction].mayCoalesce = false;
+ }
+ undoSequenceDepth++;
+}
+
+void UndoHistory::EndUndoAction() {
+ PLATFORM_ASSERT(undoSequenceDepth > 0);
+ EnsureUndoRoom();
+ undoSequenceDepth--;
+ if (0 == undoSequenceDepth) {
+ if (actions[currentAction].at != ActionType::start) {
+ currentAction++;
+ actions[currentAction].Create(ActionType::start);
+ maxAction = currentAction;
+ }
+ actions[currentAction].mayCoalesce = false;
+ }
+}
+
+void UndoHistory::DropUndoSequence() noexcept {
+ undoSequenceDepth = 0;
+}
+
+void UndoHistory::DeleteUndoHistory() {
+ for (int i = 1; i < maxAction; i++)
+ actions[i].Clear();
+ maxAction = 0;
+ currentAction = 0;
+ actions[currentAction].Create(ActionType::start);
+ savePoint = 0;
+ tentativePoint = -1;
+}
+
+void UndoHistory::SetSavePoint() noexcept {
+ savePoint = currentAction;
+ detach.reset();
+}
+
+bool UndoHistory::IsSavePoint() const noexcept {
+ return savePoint == currentAction;
+}
+
+bool UndoHistory::BeforeSavePoint() const noexcept {
+ return (savePoint < 0) || (savePoint > currentAction);
+}
+
+bool UndoHistory::BeforeReachableSavePoint() const noexcept {
+ return (savePoint >= 0) && !detach && (savePoint > currentAction);
+}
+
+bool UndoHistory::AfterSavePoint() const noexcept {
+ return (savePoint >= 0) && (savePoint <= currentAction);
+}
+
+bool UndoHistory::AfterDetachPoint() const noexcept {
+ return detach && (*detach < currentAction);
+}
+
+void UndoHistory::TentativeStart() noexcept {
+ tentativePoint = currentAction;
+}
+
+void UndoHistory::TentativeCommit() noexcept {
+ tentativePoint = -1;
+ // Truncate undo history
+ maxAction = currentAction;
+}
+
+bool UndoHistory::TentativeActive() const noexcept {
+ return tentativePoint >= 0;
+}
+
+int UndoHistory::TentativeSteps() noexcept {
+ // Drop any trailing startAction
+ if (actions[currentAction].at == ActionType::start && currentAction > 0)
+ currentAction--;
+ if (tentativePoint >= 0)
+ return currentAction - tentativePoint;
+ else
+ return -1;
+}
+
+bool UndoHistory::CanUndo() const noexcept {
+ return (currentAction > 0) && (maxAction > 0);
+}
+
+int UndoHistory::StartUndo() noexcept {
+ // Drop any trailing startAction
+ if (actions[currentAction].at == ActionType::start && currentAction > 0)
+ currentAction--;
+
+ // Count the steps in this action
+ int act = currentAction;
+ while (actions[act].at != ActionType::start && act > 0) {
+ act--;
+ }
+ return currentAction - act;
+}
+
+const Action &UndoHistory::GetUndoStep() const noexcept {
+ return actions[currentAction];
+}
+
+void UndoHistory::CompletedUndoStep() noexcept {
+ currentAction--;
+}
+
+bool UndoHistory::CanRedo() const noexcept {
+ return maxAction > currentAction;
+}
+
+int UndoHistory::StartRedo() noexcept {
+ // Drop any leading startAction
+ if (currentAction < maxAction && actions[currentAction].at == ActionType::start)
+ currentAction++;
+
+ // Count the steps in this action
+ int act = currentAction;
+ while (act < maxAction && actions[act].at != ActionType::start) {
+ act++;
+ }
+ return act - currentAction;
+}
+
+const Action &UndoHistory::GetRedoStep() const noexcept {
+ return actions[currentAction];
+}
+
+void UndoHistory::CompletedRedoStep() noexcept {
+ currentAction++;
+}
+
+}
diff --git a/src/UndoHistory.h b/src/UndoHistory.h
new file mode 100644
index 000000000..02b7bd307
--- /dev/null
+++ b/src/UndoHistory.h
@@ -0,0 +1,66 @@
+// Scintilla source code edit control
+/** @file UndoHistory.h
+ ** Manages undo for the document.
+ **/
+// Copyright 1998-2024 by Neil Hodgson <neilh@scintilla.org>
+// The License.txt file describes the conditions under which this software may be distributed.
+
+#ifndef UNDOHISTORY_H
+#define UNDOHISTORY_H
+
+namespace Scintilla::Internal {
+
+/**
+ *
+ */
+class UndoHistory {
+ std::vector<Action> actions;
+ int maxAction;
+ int currentAction;
+ int undoSequenceDepth;
+ int savePoint;
+ int tentativePoint;
+ std::optional<int> detach;
+
+ void EnsureUndoRoom();
+
+public:
+ UndoHistory();
+
+ const char *AppendAction(ActionType at, Sci::Position position, const char *data, Sci::Position lengthData, bool &startSequence, bool mayCoalesce=true);
+
+ void BeginUndoAction();
+ void EndUndoAction();
+ void DropUndoSequence() noexcept;
+ void DeleteUndoHistory();
+
+ /// The save point is a marker in the undo stack where the container has stated that
+ /// the buffer was saved. Undo and redo can move over the save point.
+ void SetSavePoint() noexcept;
+ bool IsSavePoint() const noexcept;
+ bool BeforeSavePoint() const noexcept;
+ bool BeforeReachableSavePoint() const noexcept;
+ bool AfterSavePoint() const noexcept;
+ bool AfterDetachPoint() const noexcept;
+
+ // Tentative actions are used for input composition so that it can be undone cleanly
+ void TentativeStart() noexcept;
+ void TentativeCommit() noexcept;
+ bool TentativeActive() const noexcept;
+ int TentativeSteps() noexcept;
+
+ /// To perform an undo, StartUndo is called to retrieve the number of steps, then UndoStep is
+ /// called that many times. Similarly for redo.
+ bool CanUndo() const noexcept;
+ int StartUndo() noexcept;
+ const Action &GetUndoStep() const noexcept;
+ void CompletedUndoStep() noexcept;
+ bool CanRedo() const noexcept;
+ int StartRedo() noexcept;
+ const Action &GetRedoStep() const noexcept;
+ void CompletedRedoStep() noexcept;
+};
+
+}
+
+#endif