diff options
author | Neil <nyamatongwe@gmail.com> | 2024-02-01 08:56:45 +1100 |
---|---|---|
committer | Neil <nyamatongwe@gmail.com> | 2024-02-01 08:56:45 +1100 |
commit | c6b542f84e65083552e52768db7bb4c11dcd7a64 (patch) | |
tree | e97a615381f422e67df985b18b3a50a56cb5590b | |
parent | 1345bb5b0e10e213fb1943fe491679d6fe45e9b0 (diff) | |
download | scintilla-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.
-rw-r--r-- | cocoa/Scintilla/Scintilla.xcodeproj/project.pbxproj | 8 | ||||
-rw-r--r-- | gtk/deps.mak | 13 | ||||
-rw-r--r-- | gtk/makefile | 1 | ||||
-rw-r--r-- | qt/ScintillaEdit/ScintillaEdit.pro | 1 | ||||
-rw-r--r-- | qt/ScintillaEditBase/ScintillaEditBase.pro | 2 | ||||
-rw-r--r-- | scripts/HeaderOrder.txt | 1 | ||||
-rw-r--r-- | src/CellBuffer.cxx | 322 | ||||
-rw-r--r-- | src/CellBuffer.h | 55 | ||||
-rw-r--r-- | src/UndoHistory.cxx | 295 | ||||
-rw-r--r-- | src/UndoHistory.h | 66 | ||||
-rw-r--r-- | test/unit/UnitTester.vcxproj | 1 | ||||
-rw-r--r-- | test/unit/makefile | 1 | ||||
-rw-r--r-- | test/unit/test.mak | 1 | ||||
-rw-r--r-- | test/unit/testCellBuffer.cxx | 1 | ||||
-rw-r--r-- | win32/deps.mak | 13 | ||||
-rw-r--r-- | win32/makefile | 1 | ||||
-rw-r--r-- | win32/nmdeps.mak | 13 | ||||
-rw-r--r-- | win32/scintilla.mak | 1 |
18 files changed, 456 insertions, 340 deletions
diff --git a/cocoa/Scintilla/Scintilla.xcodeproj/project.pbxproj b/cocoa/Scintilla/Scintilla.xcodeproj/project.pbxproj index 004756d74..f5ec8e774 100644 --- a/cocoa/Scintilla/Scintilla.xcodeproj/project.pbxproj +++ b/cocoa/Scintilla/Scintilla.xcodeproj/project.pbxproj @@ -98,6 +98,8 @@ 286F8EE0260448C300EC8D60 /* Geometry.h in Headers */ = {isa = PBXBuildFile; fileRef = 286F8EDD260448C300EC8D60 /* Geometry.h */; }; 287F3C6A246F90240040E76F /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 287F3C69246F90240040E76F /* Cocoa.framework */; }; 287F3C6C246F90300040E76F /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 287F3C6B246F90300040E76F /* QuartzCore.framework */; }; + 28B962A52B6AF44F00ACCD96 /* UndoHistory.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 28B962A32B6AF44F00ACCD96 /* UndoHistory.cxx */; }; + 28B962A62B6AF44F00ACCD96 /* UndoHistory.h in Headers */ = {isa = PBXBuildFile; fileRef = 28B962A42B6AF44F00ACCD96 /* UndoHistory.h */; }; 28EA9CAE255894B4007710C4 /* CharacterCategoryMap.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 28EA9CAA255894B4007710C4 /* CharacterCategoryMap.cxx */; }; 28EA9CAF255894B4007710C4 /* CharacterType.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 28EA9CAB255894B4007710C4 /* CharacterType.cxx */; }; 28EA9CB0255894B4007710C4 /* CharacterCategoryMap.h in Headers */ = {isa = PBXBuildFile; fileRef = 28EA9CAC255894B4007710C4 /* CharacterCategoryMap.h */; }; @@ -199,6 +201,8 @@ 287F3C69246F90240040E76F /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 287F3C6B246F90300040E76F /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 287F3E0F246F9AE50040E76F /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = SOURCE_ROOT; }; + 28B962A32B6AF44F00ACCD96 /* UndoHistory.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = UndoHistory.cxx; path = ../../src/UndoHistory.cxx; sourceTree = "<group>"; }; + 28B962A42B6AF44F00ACCD96 /* UndoHistory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = UndoHistory.h; path = ../../src/UndoHistory.h; sourceTree = "<group>"; }; 28EA9CAA255894B4007710C4 /* CharacterCategoryMap.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CharacterCategoryMap.cxx; path = ../../src/CharacterCategoryMap.cxx; sourceTree = "<group>"; }; 28EA9CAB255894B4007710C4 /* CharacterType.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CharacterType.cxx; path = ../../src/CharacterType.cxx; sourceTree = "<group>"; }; 28EA9CAC255894B4007710C4 /* CharacterCategoryMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CharacterCategoryMap.h; path = ../../src/CharacterCategoryMap.h; sourceTree = "<group>"; }; @@ -306,6 +310,8 @@ 2829372124E2D58700C84BA2 /* SplitVector.h */, 2829370424E2D58500C84BA2 /* Style.cxx */, 282936F224E2D58400C84BA2 /* Style.h */, + 28B962A32B6AF44F00ACCD96 /* UndoHistory.cxx */, + 28B962A42B6AF44F00ACCD96 /* UndoHistory.h */, 2829370F24E2D58500C84BA2 /* UniConversion.cxx */, 282936FC24E2D58400C84BA2 /* UniConversion.h */, 282936F324E2D58400C84BA2 /* UniqueString.cxx */, @@ -397,6 +403,7 @@ 2829376F24E2D58800C84BA2 /* Document.h in Headers */, 2829374A24E2D58800C84BA2 /* ContractionState.h in Headers */, 2829376024E2D58800C84BA2 /* XPM.h in Headers */, + 28B962A62B6AF44F00ACCD96 /* UndoHistory.h in Headers */, 2829372F24E2D58800C84BA2 /* ElapsedPeriod.h in Headers */, 28EA9CB1255894B4007710C4 /* CharacterType.h in Headers */, 2829373A24E2D58800C84BA2 /* MarginView.h in Headers */, @@ -522,6 +529,7 @@ 2829373C24E2D58800C84BA2 /* CaseFolder.cxx in Sources */, 2829374824E2D58800C84BA2 /* RESearch.cxx in Sources */, 2829377024E2D58800C84BA2 /* EditView.cxx in Sources */, + 28B962A52B6AF44F00ACCD96 /* UndoHistory.cxx in Sources */, 2829376724E2D58800C84BA2 /* ScintillaBase.cxx in Sources */, 2829374724E2D58800C84BA2 /* Style.cxx in Sources */, 2829375A24E2D58800C84BA2 /* ViewStyle.cxx in Sources */, diff --git a/gtk/deps.mak b/gtk/deps.mak index e7a562661..323e94aca 100644 --- a/gtk/deps.mak +++ b/gtk/deps.mak @@ -142,6 +142,7 @@ CellBuffer.o: \ ../src/SparseVector.h \ ../src/ChangeHistory.h \ ../src/CellBuffer.h \ + ../src/UndoHistory.h \ ../src/UniConversion.h ChangeHistory.o: \ ../src/ChangeHistory.cxx \ @@ -478,6 +479,18 @@ Style.o: \ ../src/Geometry.h \ ../src/Platform.h \ ../src/Style.h +UndoHistory.o: \ + ../src/UndoHistory.cxx \ + ../include/ScintillaTypes.h \ + ../src/Debugging.h \ + ../src/Position.h \ + ../src/SplitVector.h \ + ../src/Partitioning.h \ + ../src/RunStyles.h \ + ../src/SparseVector.h \ + ../src/ChangeHistory.h \ + ../src/CellBuffer.h \ + ../src/UndoHistory.h UniConversion.o: \ ../src/UniConversion.cxx \ ../src/UniConversion.h diff --git a/gtk/makefile b/gtk/makefile index 17816f531..df0c71348 100644 --- a/gtk/makefile +++ b/gtk/makefile @@ -149,6 +149,7 @@ SRC_OBJS = \ RunStyles.o \ Selection.o \ Style.o \ + UndoHistory.o \ UniConversion.o \ UniqueString.o \ ViewStyle.o \ diff --git a/qt/ScintillaEdit/ScintillaEdit.pro b/qt/ScintillaEdit/ScintillaEdit.pro index a74f39572..8630c63e6 100644 --- a/qt/ScintillaEdit/ScintillaEdit.pro +++ b/qt/ScintillaEdit/ScintillaEdit.pro @@ -23,6 +23,7 @@ SOURCES += \ ../ScintillaEditBase/ScintillaEditBase.cpp \ ../../src/XPM.cxx \ ../../src/ViewStyle.cxx \ + ../../src/UndoHistory.cxx \ ../../src/UniqueString.cxx \ ../../src/UniConversion.cxx \ ../../src/Style.cxx \ diff --git a/qt/ScintillaEditBase/ScintillaEditBase.pro b/qt/ScintillaEditBase/ScintillaEditBase.pro index c859b9339..5b9f61308 100644 --- a/qt/ScintillaEditBase/ScintillaEditBase.pro +++ b/qt/ScintillaEditBase/ScintillaEditBase.pro @@ -23,6 +23,7 @@ SOURCES += \ ../../src/ViewStyle.cxx \ ../../src/UniqueString.cxx \ ../../src/UniConversion.cxx \ + ../../src/UndoHistory.cxx \ ../../src/Style.cxx \ ../../src/Selection.cxx \ ../../src/ScintillaBase.cxx \ @@ -58,6 +59,7 @@ HEADERS += \ ScintillaEditBase.h \ ../../src/XPM.h \ ../../src/ViewStyle.h \ + ../../src/UndoHistory.h \ ../../src/UniConversion.h \ ../../src/Style.h \ ../../src/SplitVector.h \ diff --git a/scripts/HeaderOrder.txt b/scripts/HeaderOrder.txt index c17d1897b..5b57a789c 100644 --- a/scripts/HeaderOrder.txt +++ b/scripts/HeaderOrder.txt @@ -122,6 +122,7 @@ #include "ContractionState.h" #include "ChangeHistory.h" #include "CellBuffer.h" +#include "UndoHistory.h" #include "PerLine.h" #include "CallTip.h" #include "KeyMap.h" 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 diff --git a/test/unit/UnitTester.vcxproj b/test/unit/UnitTester.vcxproj index a7df099c0..fdf567cff 100644 --- a/test/unit/UnitTester.vcxproj +++ b/test/unit/UnitTester.vcxproj @@ -165,6 +165,7 @@ <ClCompile Include="..\..\src\PerLine.cxx" />
<ClCompile Include="..\..\src\RESearch.cxx" />
<ClCompile Include="..\..\src\RunStyles.cxx" />
+ <ClCompile Include="..\..\src\UndoHistory.cxx" />
<ClCompile Include="..\..\src\UniConversion.cxx" />
<ClCompile Include="..\..\src\UniqueString.cxx" />
<ClCompile Include="test*.cxx" />
diff --git a/test/unit/makefile b/test/unit/makefile index 9830ee794..866727505 100644 --- a/test/unit/makefile +++ b/test/unit/makefile @@ -68,6 +68,7 @@ Geometry.o \ PerLine.o \ RESearch.o \ RunStyles.o \ +UndoHistory.o \ UniConversion.o \ UniqueString.o diff --git a/test/unit/test.mak b/test/unit/test.mak index 10a6ebac7..e4a448f8c 100644 --- a/test/unit/test.mak +++ b/test/unit/test.mak @@ -25,6 +25,7 @@ TESTEDSRC=\ ../../src/PerLine.cxx \ ../../src/RESearch.cxx \ ../../src/RunStyles.cxx \ + ../../src/UndoHistory.cxx \ ../../src/UniConversion.cxx \ ../../src/UniqueString.cxx diff --git a/test/unit/testCellBuffer.cxx b/test/unit/testCellBuffer.cxx index b6e03d2ee..bc8bf3b67 100644 --- a/test/unit/testCellBuffer.cxx +++ b/test/unit/testCellBuffer.cxx @@ -23,6 +23,7 @@ #include "SparseVector.h" #include "ChangeHistory.h" #include "CellBuffer.h" +#include "UndoHistory.h" #include "catch.hpp" diff --git a/win32/deps.mak b/win32/deps.mak index 5448f054d..8c36e8ddb 100644 --- a/win32/deps.mak +++ b/win32/deps.mak @@ -101,6 +101,7 @@ $(DIR_O)/CellBuffer.o: \ ../src/SparseVector.h \ ../src/ChangeHistory.h \ ../src/CellBuffer.h \ + ../src/UndoHistory.h \ ../src/UniConversion.h $(DIR_O)/ChangeHistory.o: \ ../src/ChangeHistory.cxx \ @@ -437,6 +438,18 @@ $(DIR_O)/Style.o: \ ../src/Geometry.h \ ../src/Platform.h \ ../src/Style.h +$(DIR_O)/UndoHistory.o: \ + ../src/UndoHistory.cxx \ + ../include/ScintillaTypes.h \ + ../src/Debugging.h \ + ../src/Position.h \ + ../src/SplitVector.h \ + ../src/Partitioning.h \ + ../src/RunStyles.h \ + ../src/SparseVector.h \ + ../src/ChangeHistory.h \ + ../src/CellBuffer.h \ + ../src/UndoHistory.h $(DIR_O)/UniConversion.o: \ ../src/UniConversion.cxx \ ../src/UniConversion.h diff --git a/win32/makefile b/win32/makefile index e2cd4ce50..0ef6b23af 100644 --- a/win32/makefile +++ b/win32/makefile @@ -108,6 +108,7 @@ SRC_OBJS = \ $(DIR_O)/RunStyles.o \ $(DIR_O)/Selection.o \ $(DIR_O)/Style.o \ + $(DIR_O)/UndoHistory.o \ $(DIR_O)/UniConversion.o \ $(DIR_O)/UniqueString.o \ $(DIR_O)/ViewStyle.o \ diff --git a/win32/nmdeps.mak b/win32/nmdeps.mak index 62ada451c..961aba5ee 100644 --- a/win32/nmdeps.mak +++ b/win32/nmdeps.mak @@ -101,6 +101,7 @@ $(DIR_O)/CellBuffer.obj: \ ../src/SparseVector.h \ ../src/ChangeHistory.h \ ../src/CellBuffer.h \ + ../src/UndoHistory.h \ ../src/UniConversion.h $(DIR_O)/ChangeHistory.obj: \ ../src/ChangeHistory.cxx \ @@ -437,6 +438,18 @@ $(DIR_O)/Style.obj: \ ../src/Geometry.h \ ../src/Platform.h \ ../src/Style.h +$(DIR_O)/UndoHistory.obj: \ + ../src/UndoHistory.cxx \ + ../include/ScintillaTypes.h \ + ../src/Debugging.h \ + ../src/Position.h \ + ../src/SplitVector.h \ + ../src/Partitioning.h \ + ../src/RunStyles.h \ + ../src/SparseVector.h \ + ../src/ChangeHistory.h \ + ../src/CellBuffer.h \ + ../src/UndoHistory.h $(DIR_O)/UniConversion.obj: \ ../src/UniConversion.cxx \ ../src/UniConversion.h diff --git a/win32/scintilla.mak b/win32/scintilla.mak index cc18f26a7..995e122d3 100644 --- a/win32/scintilla.mak +++ b/win32/scintilla.mak @@ -104,6 +104,7 @@ SRC_OBJS=\ $(DIR_O)\RunStyles.obj \ $(DIR_O)\Selection.obj \ $(DIR_O)\Style.obj \ + $(DIR_O)\UndoHistory.obj \ $(DIR_O)\UniConversion.obj \ $(DIR_O)\UniqueString.obj \ $(DIR_O)\ViewStyle.obj \ |