aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/UndoHistory.cxx
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/UndoHistory.cxx
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/UndoHistory.cxx')
-rw-r--r--src/UndoHistory.cxx295
1 files changed, 295 insertions, 0 deletions
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++;
+}
+
+}