diff options
author | Neil <nyamatongwe@gmail.com> | 2024-02-09 21:45:35 +1100 |
---|---|---|
committer | Neil <nyamatongwe@gmail.com> | 2024-02-09 21:45:35 +1100 |
commit | bd53ffcbefe4e7a22fc493b1916939bae5f9dc1d (patch) | |
tree | bfe435d75b222d464a9cbb21e25583d33c591936 | |
parent | 07a7683902feb4b9944394b9378f0a1a51972497 (diff) | |
download | scintilla-mirror-bd53ffcbefe4e7a22fc493b1916939bae5f9dc1d.tar.gz |
Implement API to read and write undo history from applications.
-rw-r--r-- | call/ScintillaCall.cxx | 52 | ||||
-rw-r--r-- | doc/ScintillaDoc.html | 118 | ||||
-rw-r--r-- | include/Scintilla.h | 12 | ||||
-rw-r--r-- | include/Scintilla.iface | 36 | ||||
-rw-r--r-- | include/ScintillaCall.h | 13 | ||||
-rw-r--r-- | include/ScintillaMessages.h | 12 | ||||
-rw-r--r-- | src/CellBuffer.cxx | 48 | ||||
-rw-r--r-- | src/CellBuffer.h | 13 | ||||
-rw-r--r-- | src/Document.cxx | 48 | ||||
-rw-r--r-- | src/Document.h | 13 | ||||
-rw-r--r-- | src/Editor.cxx | 43 | ||||
-rw-r--r-- | src/UndoHistory.cxx | 95 | ||||
-rw-r--r-- | src/UndoHistory.h | 18 | ||||
-rw-r--r-- | test/simpleTests.py | 76 |
14 files changed, 580 insertions, 17 deletions
diff --git a/call/ScintillaCall.cxx b/call/ScintillaCall.cxx index 98e1dbd3e..97cd64886 100644 --- a/call/ScintillaCall.cxx +++ b/call/ScintillaCall.cxx @@ -799,6 +799,58 @@ void ScintillaCall::EndUndoAction() { Call(Message::EndUndoAction); } +int ScintillaCall::UndoActions() { + return static_cast<int>(Call(Message::GetUndoActions)); +} + +void ScintillaCall::SetUndoSavePoint(int action) { + Call(Message::SetUndoSavePoint, action); +} + +int ScintillaCall::UndoSavePoint() { + return static_cast<int>(Call(Message::GetUndoSavePoint)); +} + +void ScintillaCall::SetUndoCurrent(int action) { + Call(Message::SetUndoCurrent, action); +} + +int ScintillaCall::UndoCurrent() { + return static_cast<int>(Call(Message::GetUndoCurrent)); +} + +void ScintillaCall::SetUndoTentative(int action) { + Call(Message::SetUndoTentative, action); +} + +int ScintillaCall::UndoTentative() { + return static_cast<int>(Call(Message::GetUndoTentative)); +} + +void ScintillaCall::PushUndoActionType(int type, Position pos) { + Call(Message::PushUndoActionType, type, pos); +} + +void ScintillaCall::ChangeLastUndoActionText(Position length, const char *text) { + CallString(Message::ChangeLastUndoActionText, length, text); +} + +int ScintillaCall::UndoActionType(int action) { + return static_cast<int>(Call(Message::GetUndoActionType, action)); +} + +Position ScintillaCall::UndoActionPosition(int action) { + return Call(Message::GetUndoActionPosition, action); +} + +int ScintillaCall::UndoActionText(int action, char *text) { + return static_cast<int>(CallPointer(Message::GetUndoActionText, action, text)); +} + +std::string ScintillaCall::UndoActionText(int action) { + return CallReturnString(Message::GetUndoActionText, action); +} + void ScintillaCall::IndicSetStyle(int indicator, Scintilla::IndicatorStyle indicatorStyle) { Call(Message::IndicSetStyle, indicator, static_cast<intptr_t>(indicatorStyle)); } diff --git a/doc/ScintillaDoc.html b/doc/ScintillaDoc.html index 3035d2210..12f2be0eb 100644 --- a/doc/ScintillaDoc.html +++ b/doc/ScintillaDoc.html @@ -341,97 +341,101 @@ <tr> <td>○ <a class="toc" href="#UndoAndRedo">Undo and Redo</a></td> + <td>○ <a class="toc" href="#UndoSaveRestore">Undo save and restore</a></td> <td>○ <a class="toc" href="#ChangeHistory">Change history</a></td> - <td>○ <a class="toc" href="#ScrollingAndAutomaticScrolling">Scrolling and automatic scrolling</a></td> </tr> <tr> + <td>○ <a class="toc" href="#ScrollingAndAutomaticScrolling">Scrolling and automatic scrolling</a></td> <td>○ <a class="toc" href="#WhiteSpace">White space</a></td> <td>○ <a class="toc" href="#Cursor">Cursor</a></td> - <td>○ <a class="toc" href="#MouseCapture">Mouse capture</a></td> </tr> <tr> + <td>○ <a class="toc" href="#MouseCapture">Mouse capture</a></td> <td>○ <a class="toc" href="#LineEndings">Line endings</a></td> <td>○ <a class="toc" href="#Words">Words</a></td> - <td>○ <a class="toc" href="#Styling">Styling</a></td> </tr> <tr> + <td>○ <a class="toc" href="#Styling">Styling</a></td> <td>○ <a class="toc" href="#StyleDefinition">Style definition</a></td> <td>○ <a class="toc" href="#ElementColours">Element colours</a></td> - <td>○ <a class="toc" href="#CaretAndSelectionStyles">Selection, caret, and hotspot styles</a></td> </tr> <tr> + <td>○ <a class="toc" href="#CaretAndSelectionStyles">Selection, caret, and hotspot styles</a></td> <td>○ <a class="toc" href="#CharacterRepresentations">Character representations</a></td> <td>○ <a class="toc" href="#Margins">Margins</a></td> - <td>○ <a class="toc" href="#Annotations">Annotations</a></td> </tr> <tr> + <td>○ <a class="toc" href="#Annotations">Annotations</a></td> <td>○ <a class="toc" href="#EndOfLineAnnotations">End of Line Annotations</a></td> <td>○ <a class="toc" href="#OtherSettings">Other settings</a></td> - <td>○ <a class="toc" href="#BraceHighlighting">Brace highlighting</a></td> </tr> <tr> + <td>○ <a class="toc" href="#BraceHighlighting">Brace highlighting</a></td> <td>○ <a class="toc" href="#TabsAndIndentationGuides">Tabs and Indentation Guides</a></td> <td>○ <a class="toc" href="#Markers">Markers</a></td> - <td>○ <a class="toc" href="#Indicators">Indicators</a></td> </tr> <tr> + <td>○ <a class="toc" href="#Indicators">Indicators</a></td> <td>○ <a class="toc" href="#Autocompletion">Autocompletion</a></td> <td>○ <a class="toc" href="#UserLists">User lists</a></td> - <td>○ <a class="toc" href="#CallTips">Call tips</a></td> </tr> <tr> + <td>○ <a class="toc" href="#CallTips">Call tips</a></td> <td>○ <a class="toc" href="#KeyboardCommands">Keyboard commands</a></td> <td>○ <a class="toc" href="#KeyBindings">Key bindings</a></td> - <td>○ <a class="toc" href="#PopupEditMenu">Popup edit menu</a></td> </tr> <tr> + <td>○ <a class="toc" href="#PopupEditMenu">Popup edit menu</a></td> <td>○ <a class="toc" href="#MacroRecording">Macro recording</a></td> <td>○ <a class="toc" href="#Printing">Printing</a></td> - <td>○ <a class="toc" href="#DirectAccess">Direct access</a></td> </tr> <tr> + <td>○ <a class="toc" href="#DirectAccess">Direct access</a></td> <td>○ <a class="toc" href="#MultipleViews">Multiple views</a></td> <td>○ <a class="toc" href="#BackgroundLoadSave">Background loading and saving</a></td> - <td>○ <a class="toc" href="#DocumentInterface">Document interface</a></td> </tr> <tr> + <td>○ <a class="toc" href="#DocumentInterface">Document interface</a></td> <td>○ <a class="toc" href="#Folding">Folding</a></td> <td>○ <a class="toc" href="#LineWrapping">Line wrapping</a></td> - <td>○ <a class="toc" href="#Zooming">Zooming</a></td> </tr> <tr> + <td>○ <a class="toc" href="#Zooming">Zooming</a></td> <td>○ <a class="toc" href="#LongLines">Long lines</a></td> <td>○ <a class="toc" href="#Accessibility">Accessibility</a></td> - <td>○ <a class="toc" href="#Lexer">Lexer</a></td> </tr> <tr> + <td>○ <a class="toc" href="#Lexer">Lexer</a></td> <td>○ <a class="toc" href="#LexerObjects">Lexer objects</a></td> <td>○ <a class="toc" href="#Notifications">Notifications</a></td> - <td>○ <a class="toc" href="#Images">Images</a></td> </tr> <tr> + <td>○ <a class="toc" href="#Images">Images</a></td> <td>○ <a class="toc" href="#GTK">GTK</a></td> <td>○ <a class="toc" href="#ProvisionalMessages"><span class="provisional">Provisional messages</span></a></td> - <td>○ <a class="toc" href="#DeprecatedMessages">Deprecated messages</a></td> </tr> <tr> + <td>○ <a class="toc" href="#DeprecatedMessages">Deprecated messages</a></td> <td>○ <a class="toc" href="#EditMessagesNeverSupportedByScintilla">Edit messages never supported by Scintilla</a></td> <td>○ <a class="toc" href="#RemovedFeatures">Removed features</a></td> + </tr> + + <tr> <td>○ <a class="toc" href="#BuildingScintilla">Building Scintilla</a></td> </tr> </tbody> @@ -1988,6 +1992,90 @@ struct Sci_TextToFindFull { look like typing or deletions that look like multiple uses of the Backspace or Delete keys. </p> + <h2 id="UndoSaveRestore">Undo Save and Restore</h2> + + <p>This feature is unfinished and has limitations. + Restoring undo state is not compatible with change history so turn change history off before restoral. + The operation sequences discussed here are a 'golden path' that has been tested to some extent and calling + the APIs in other circumstances or with out-of-bounds values may cause failures. + The behaviour of tentative actions in save and restore is uncertain as these are meant to be short-term states in language input + and which need to synchronize with a language IME (input method editor).</p> + + <p>It is possible to retrieve the undo stack from Scintilla and subsequently restore the state of the stack.</p> + + <p>An application may save both the document and its save stack between sessions to enable the user to return + to the same state the next time they edit. + For this to work, the loaded file must be exactly the same as when the undo stack was saved. + If the file was changed, even in minor ways like converting line ends from Windows to Unix style then + the undo actions will not line up so undo may fail completely and will produce unexpected results.</p> + + <code><a class="message" href="#SCI_GETUNDOACTIONS">SCI_GETUNDOACTIONS → int</a><br /> + <a class="message" href="#SCI_SETUNDOSAVEPOINT">SCI_SETUNDOSAVEPOINT(int action)</a><br /> + <a class="message" href="#SCI_GETUNDOSAVEPOINT">SCI_GETUNDOSAVEPOINT → int</a><br /> + <a class="message" href="#SCI_SETUNDOCURRENT">SCI_SETUNDOCURRENT(int action)</a><br /> + <a class="message" href="#SCI_GETUNDOCURRENT">SCI_GETUNDOCURRENT → int</a><br /> + <a class="message" href="#SCI_SETUNDOTENTATIVE">SCI_SETUNDOTENTATIVE(int action)</a><br /> + <a class="message" href="#SCI_GETUNDOTENTATIVE">SCI_GETUNDOTENTATIVE → int</a><br /> + <a class="message" href="#SCI_PUSHUNDOACTIONTYPE">SCI_PUSHUNDOACTIONTYPE(int type, position pos)</a><br /> + <a class="message" href="#SCI_CHANGELASTUNDOACTIONTEXT">SCI_CHANGELASTUNDOACTIONTEXT(position length, const char *text)</a><br /> + <a class="message" href="#SCI_GETUNDOACTIONTYPE">SCI_GETUNDOACTIONTYPE(int action) → int</a><br /> + <a class="message" href="#SCI_GETUNDOACTIONPOSITION">SCI_GETUNDOACTIONPOSITION(int action) → position</a><br /> + <a class="message" href="#SCI_GETUNDOACTIONTEXT ">SCI_GETUNDOACTIONTEXT(int action, char *text) → int</a><br /> + </code> + + <h3 id="UndoSave">Save</h3> + + <p>The retrieval APIs are the 'GET*' ones: + <code>SCI_GETUNDOACTIONS</code>, + <code>SCI_GETUNDOSAVEPOINT</code>, + <code>SCI_GETUNDOCURRENT</code>, + <code>SCI_GETUNDOTENTATIVE</code>, + <code>SCI_GETUNDOACTIONTYPE</code>, + <code>SCI_GETUNDOACTIONPOSITION</code>, and + <code>SCI_GETUNDOACTIONTEXT</code>. + </p> + + <p>The <code>SCI_GETUNDOACTIONS</code>, + <code>SCI_GETUNDOSAVEPOINT</code>, <code>SCI_GETUNDOCURRENT</code>, and + <code>SCI_GETUNDOTENTATIVE</code> APIs each return a single value and may be called in any order. + </p> + + <p>The <code>SCI_GETUNDOACTIONTYPE</code>, + <code>SCI_GETUNDOACTIONPOSITION</code>, and <code>SCI_GETUNDOACTIONTEXT</code> + APIs take an action index and should be called with indices from 0 to one less than the result of + <code>SCI_GETUNDOACTIONS</code>. + The actions should only be iterated in the positive direction and should start from 0. + That is because undo stack data is not all randomly accessible and iterating in other orders may take O(n^2) time. + Data may also be inaccurate if a cursor is not initialised first with 0 index calls. + </p> + + <h3 id="UndoRestore">Restore</h3> + + <p>Restoration is only possible when the undo state is empty so <code>SCI_EMPTYUNDOBUFFER</code> + should be called first if there may already be some undo actions.</p> + + <p>The restore APIs are the 'SET*' and others: + <code>SCI_SETUNDOSAVEPOINT</code>, + <code>SCI_SETUNDOCURRENT</code>, + <code>SCI_SETUNDOTENTATIVE</code>, + <code>SCI_PUSHUNDOACTIONTYPE</code>, and + <code>SCI_CHANGELASTUNDOACTIONTEXT</code>. + </p> + + <p>The history should first be set up with <code>SCI_PUSHUNDOACTIONTYPE</code>, and + <code>SCI_CHANGELASTUNDOACTIONTEXT</code> then the save, current, and tentativ points set + with <code>SCI_SETUNDOSAVEPOINT</code>, <code>SCI_SETUNDOCURRENT</code>, and + <code>SCI_SETUNDOTENTATIVE</code>. + </p> + + <p><code>SCI_PUSHUNDOACTIONTYPE(int type, position pos)</code> appends an action to the undo stack + with a particular type and position then the text and length of that action are set with + <code>SCI_CHANGELASTUNDOACTIONTEXT(position length, const char *text)</code>. + </p> + + <p>The current implementation may only work when the current and save point are the same and there is no tentative point. + </p> + <h2 id="ChangeHistory">Change history</h2> <p>Scintilla can display document changes (modified, saved, ...) in the margin or in the text.</p> diff --git a/include/Scintilla.h b/include/Scintilla.h index 8c555352c..be537abad 100644 --- a/include/Scintilla.h +++ b/include/Scintilla.h @@ -339,6 +339,18 @@ typedef sptr_t (*SciFnDirectStatus)(sptr_t ptr, unsigned int iMessage, uptr_t wP #define SCI_GETCHARACTERCATEGORYOPTIMIZATION 2721 #define SCI_BEGINUNDOACTION 2078 #define SCI_ENDUNDOACTION 2079 +#define SCI_GETUNDOACTIONS 2790 +#define SCI_SETUNDOSAVEPOINT 2791 +#define SCI_GETUNDOSAVEPOINT 2792 +#define SCI_SETUNDOCURRENT 2793 +#define SCI_GETUNDOCURRENT 2794 +#define SCI_SETUNDOTENTATIVE 2795 +#define SCI_GETUNDOTENTATIVE 2796 +#define SCI_PUSHUNDOACTIONTYPE 2797 +#define SCI_CHANGELASTUNDOACTIONTEXT 2798 +#define SCI_GETUNDOACTIONTYPE 2799 +#define SCI_GETUNDOACTIONPOSITION 2800 +#define SCI_GETUNDOACTIONTEXT 2801 #define INDIC_PLAIN 0 #define INDIC_SQUIGGLE 1 #define INDIC_TT 2 diff --git a/include/Scintilla.iface b/include/Scintilla.iface index 918121596..dd7495a6f 100644 --- a/include/Scintilla.iface +++ b/include/Scintilla.iface @@ -833,6 +833,42 @@ fun void BeginUndoAction=2078(,) # End a sequence of actions that is undone and redone as a unit. fun void EndUndoAction=2079(,) +# How many undo actions are in the history? +get int GetUndoActions=2790(,) + +# Set action as the save point +set void SetUndoSavePoint=2791(int action,) + +# Which action is the save point? +get int GetUndoSavePoint=2792(,) + +# Set action as the current point +set void SetUndoCurrent=2793(int action,) + +# Which action is the current point? +get int GetUndoCurrent=2794(,) + +# Set action as the tentative point +set void SetUndoTentative=2795(int action,) + +# Which action is the tentative point? +get int GetUndoTentative=2796(,) + +# Push one action onto undo history with no text +fun void PushUndoActionType=2797(int type, position pos) + +# Set the text and length of the most recently pushed action +fun void ChangeLastUndoActionText=2798(position length, string text) + +# What is the type of an action? +get int GetUndoActionType=2799(int action,) + +# What is the position of an action? +get position GetUndoActionPosition=2800(int action,) + +# What is the text of an action? +get int GetUndoActionText=2801(int action, stringresult text) + # Indicator style enumeration and some constants enu IndicatorStyle=INDIC_ val INDIC_PLAIN=0 diff --git a/include/ScintillaCall.h b/include/ScintillaCall.h index 0ce4366d7..47122fcfe 100644 --- a/include/ScintillaCall.h +++ b/include/ScintillaCall.h @@ -245,6 +245,19 @@ public: int CharacterCategoryOptimization(); void BeginUndoAction(); void EndUndoAction(); + int UndoActions(); + void SetUndoSavePoint(int action); + int UndoSavePoint(); + void SetUndoCurrent(int action); + int UndoCurrent(); + void SetUndoTentative(int action); + int UndoTentative(); + void PushUndoActionType(int type, Position pos); + void ChangeLastUndoActionText(Position length, const char *text); + int UndoActionType(int action); + Position UndoActionPosition(int action); + int UndoActionText(int action, char *text); + std::string UndoActionText(int action); void IndicSetStyle(int indicator, Scintilla::IndicatorStyle indicatorStyle); Scintilla::IndicatorStyle IndicGetStyle(int indicator); void IndicSetFore(int indicator, Colour fore); diff --git a/include/ScintillaMessages.h b/include/ScintillaMessages.h index 4b9421242..5cff5ed26 100644 --- a/include/ScintillaMessages.h +++ b/include/ScintillaMessages.h @@ -171,6 +171,18 @@ enum class Message { GetCharacterCategoryOptimization = 2721, BeginUndoAction = 2078, EndUndoAction = 2079, + GetUndoActions = 2790, + SetUndoSavePoint = 2791, + GetUndoSavePoint = 2792, + SetUndoCurrent = 2793, + GetUndoCurrent = 2794, + SetUndoTentative = 2795, + GetUndoTentative = 2796, + PushUndoActionType = 2797, + ChangeLastUndoActionText = 2798, + GetUndoActionType = 2799, + GetUndoActionPosition = 2800, + GetUndoActionText = 2801, IndicSetStyle = 2080, IndicGetStyle = 2081, IndicSetFore = 2082, diff --git a/src/CellBuffer.cxx b/src/CellBuffer.cxx index bfef83da5..b3111914e 100644 --- a/src/CellBuffer.cxx +++ b/src/CellBuffer.cxx @@ -1148,6 +1148,54 @@ void CellBuffer::PerformRedoStep() { uh->CompletedRedoStep(); } +int CellBuffer::UndoActions() const noexcept { + return uh->Actions(); +} + +void CellBuffer::SetUndoSavePoint(int action) noexcept { + uh->SetSavePoint(action); +} + +int CellBuffer::UndoSavePoint() const noexcept { + return uh->SavePoint(); +} + +void CellBuffer::SetUndoCurrent(int action) noexcept { + uh->SetCurrent(action); +} + +int CellBuffer::UndoCurrent() const noexcept { + return uh->Current(); +} + +void CellBuffer::SetUndoTentative(int action) noexcept { + uh->SetTentative(action); +} + +int CellBuffer::UndoTentative() const noexcept { + return uh->TentativePoint(); +} + +int CellBuffer::UndoActionType(int action) const noexcept { + return uh->Type(action); +} + +Sci::Position CellBuffer::UndoActionPosition(int action) const noexcept { + return uh->Position(action); +} + +std::string_view CellBuffer::UndoActionText(int action) const noexcept { + return uh->Text(action); +} + +void CellBuffer::PushUndoActionType(int type, Sci::Position position) { + uh->PushUndoActionType(type, position); +} + +void CellBuffer::ChangeLastUndoActionText(size_t length, const char *text) { + uh->ChangeLastUndoActionText(length, text); +} + void CellBuffer::ChangeHistorySet(bool set) { if (set) { if (!changeHistory && !uh->CanUndo()) { diff --git a/src/CellBuffer.h b/src/CellBuffer.h index 16ed14d6c..5422e92aa 100644 --- a/src/CellBuffer.h +++ b/src/CellBuffer.h @@ -180,6 +180,19 @@ public: Action GetRedoStep() const noexcept; void PerformRedoStep(); + int UndoActions() const noexcept; + void SetUndoSavePoint(int action) noexcept; + int UndoSavePoint() const noexcept; + void SetUndoCurrent(int action) noexcept; + int UndoCurrent() const noexcept; + void SetUndoTentative(int action) noexcept; + int UndoTentative() const noexcept; + int UndoActionType(int action) const noexcept; + Sci::Position UndoActionPosition(int action) const noexcept; + std::string_view UndoActionText(int action) const noexcept; + void PushUndoActionType(int type, Sci::Position position); + void ChangeLastUndoActionText(size_t length, const char *text); + void ChangeHistorySet(bool set); [[nodiscard]] int EditionAt(Sci::Position pos) const noexcept; [[nodiscard]] Sci::Position EditionEndRun(Sci::Position pos) const noexcept; diff --git a/src/Document.cxx b/src/Document.cxx index 306cdb725..52081e1cd 100644 --- a/src/Document.cxx +++ b/src/Document.cxx @@ -351,6 +351,54 @@ void Document::TentativeUndo() { } } +int Document::UndoActions() const noexcept { + return cb.UndoActions(); +} + +void Document::SetUndoSavePoint(int action) noexcept { + cb.SetUndoSavePoint(action); +} + +int Document::UndoSavePoint() const noexcept { + return cb.UndoSavePoint(); +} + +void Document::SetUndoCurrent(int action) noexcept { + cb.SetUndoCurrent(action); +} + +int Document::UndoCurrent() const noexcept { + return cb.UndoCurrent(); +} + +void Document::SetUndoTentative(int action) noexcept { + cb.SetUndoTentative(action); +} + +int Document::UndoTentative() const noexcept { + return cb.UndoTentative(); +} + +int Document::UndoActionType(int action) const noexcept { + return cb.UndoActionType(action); +} + +Sci::Position Document::UndoActionPosition(int action) const noexcept { + return cb.UndoActionPosition(action); +} + +std::string_view Document::UndoActionText(int action) const noexcept { + return cb.UndoActionText(action); +} + +void Document::PushUndoActionType(int type, Sci::Position position) { + cb.PushUndoActionType(type, position); +} + +void Document::ChangeLastUndoActionText(size_t length, const char *text) { + cb.ChangeLastUndoActionText(length, text); +} + int Document::GetMark(Sci::Line line, bool includeChangeHistory) const { int marksHistory = 0; if (includeChangeHistory && (line < LinesTotal())) { diff --git a/src/Document.h b/src/Document.h index af6bb98fd..185066a6c 100644 --- a/src/Document.h +++ b/src/Document.h @@ -406,6 +406,19 @@ public: void TentativeUndo(); bool TentativeActive() const noexcept { return cb.TentativeActive(); } + int UndoActions() const noexcept; + void SetUndoSavePoint(int action) noexcept; + int UndoSavePoint() const noexcept; + void SetUndoCurrent(int action) noexcept; + int UndoCurrent() const noexcept; + void SetUndoTentative(int action) noexcept; + int UndoTentative() const noexcept; + int UndoActionType(int action) const noexcept; + Sci::Position UndoActionPosition(int action) const noexcept; + std::string_view UndoActionText(int action) const noexcept; + void PushUndoActionType(int type, Sci::Position position); + void ChangeLastUndoActionText(size_t length, const char *text); + void ChangeHistorySet(bool set) { cb.ChangeHistorySet(set); } [[nodiscard]] int EditionAt(Sci::Position pos) const noexcept { return cb.EditionAt(pos); } [[nodiscard]] Sci::Position EditionEndRun(Sci::Position pos) const noexcept { return cb.EditionEndRun(pos); } diff --git a/src/Editor.cxx b/src/Editor.cxx index 2d7d07e22..b8ed636d3 100644 --- a/src/Editor.cxx +++ b/src/Editor.cxx @@ -6586,6 +6586,49 @@ sptr_t Editor::WndProc(Message iMessage, uptr_t wParam, sptr_t lParam) { pdoc->EndUndoAction(); return 0; + case Message::GetUndoActions: + return pdoc->UndoActions(); + + case Message::SetUndoSavePoint: + pdoc->SetUndoSavePoint(static_cast<int>(wParam)); + break; + + case Message::GetUndoSavePoint: + return pdoc->UndoSavePoint(); + + case Message::SetUndoCurrent: + pdoc->SetUndoCurrent(static_cast<int>(wParam)); + break; + + case Message::GetUndoCurrent: + return pdoc->UndoCurrent(); + + case Message::SetUndoTentative: + pdoc->SetUndoTentative(static_cast<int>(wParam)); + break; + + case Message::GetUndoTentative: + return pdoc->UndoTentative(); + + case Message::GetUndoActionType: + return pdoc->UndoActionType(static_cast<int>(wParam)); + + case Message::GetUndoActionPosition: + return pdoc->UndoActionPosition(static_cast<int>(wParam)); + + case Message::GetUndoActionText: { + std::string_view text = pdoc->UndoActionText(static_cast<int>(wParam)); + return BytesResult(lParam, reinterpret_cast<const unsigned char *>(text.data()), text.length()); + } + + case Message::PushUndoActionType: + pdoc->PushUndoActionType(static_cast<int>(wParam), lParam); + break; + + case Message::ChangeLastUndoActionText: + pdoc->ChangeLastUndoActionText(wParam, CharPtrFromSPtr(lParam)); + break; + case Message::GetCaretPeriod: return caret.period; diff --git a/src/UndoHistory.cxx b/src/UndoHistory.cxx index d6bda27f1..9c1b89c94 100644 --- a/src/UndoHistory.cxx +++ b/src/UndoHistory.cxx @@ -341,6 +341,20 @@ void UndoHistory::DeleteUndoHistory() noexcept { currentAction = 0; savePoint = 0; tentativePoint = -1; + scraps = std::make_unique<ScrapStack>(); + memory = {}; +} + +int UndoHistory::Actions() const noexcept { + return static_cast<int>(actions.SSize()); +} + +void UndoHistory::SetSavePoint(int action) noexcept { + savePoint = action; +} + +int UndoHistory::SavePoint() const noexcept { + return savePoint; } void UndoHistory::SetSavePoint() noexcept { @@ -376,6 +390,72 @@ bool UndoHistory::AfterOrAtDetachPoint() const noexcept { return detach && (*detach <= currentAction); } +void UndoHistory::SetCurrent(int action) noexcept { + // Find position in scraps for action + memory = {}; + size_t position = 0; + for (int act = 0; act < action; act++) { + position += actions.lengths.ValueAt(act); + } + scraps->SetCurrent(position); + currentAction = action; +} + +int UndoHistory::Current() const noexcept { + return currentAction; +} + +int UndoHistory::Type(int action) const noexcept { + const int baseType = static_cast<int>(actions.types[action].at); + const int open = actions.types[action].mayCoalesce ? coalesceFlag : 0; + return baseType | open; +} + +Sci::Position UndoHistory::Position(int action) const noexcept { + return actions.positions.SignedValueAt(action); +} + +std::string_view UndoHistory::Text(int action) noexcept { + // Assumes first call after any changes is for action 0. + // TODO: may need to invalidate memory in other circumstances + if (action == 0) { + memory = {}; + } + int act = 0; + size_t position = 0; + if (memory && memory->act <= action) { + act = memory->act; + position = memory->position; + } + for (; act < action; act++) { + position += actions.lengths.ValueAt(act); + } + const size_t length = actions.lengths.ValueAt(action); + const char *scrap = scraps->TextAt(position); + memory = {action, position}; + return {scrap, length}; +} + +void UndoHistory::PushUndoActionType(int type, Sci::Position position) { + actions.PushBack(); + actions.Create(actions.SSize()-1, static_cast<ActionType>(type & byteMask), + position, 0, type & coalesceFlag); +} + +void UndoHistory::ChangeLastUndoActionText(size_t length, const char *text) { + assert(actions.lengths.ValueAt(actions.SSize()-1) == 0); + actions.lengths.SetValueAt(actions.SSize()-1, length); + scraps->Push(text, length); +} + +void UndoHistory::SetTentative(int action) noexcept { + tentativePoint = action; +} + +int UndoHistory::TentativePoint() const noexcept { + return tentativePoint; +} + void UndoHistory::TentativeStart() noexcept { tentativePoint = currentAction; } @@ -402,11 +482,15 @@ bool UndoHistory::CanUndo() const noexcept { } int UndoHistory::StartUndo() noexcept { + assert(currentAction >= 0); + // Count the steps in this action - if (currentAction <= 0) { + if (currentAction == 0) { return 0; } + int act = currentAction - 1; + while (act > 0 && !actions.AtStart(act)) { act--; } @@ -439,14 +523,21 @@ bool UndoHistory::CanRedo() const noexcept { int UndoHistory::StartRedo() noexcept { // Count the steps in this action + if (currentAction >= actions.SSize()) { // Already at end so can't redo return 0; } + + // Slightly unusual logic handles case where last action still has mayCoalesce. + // Could set mayCoalesce of last action to false in StartUndo but this state is + // visible to applications so should not be changed. + const int maxAction = Actions() - 1; int act = currentAction; - while ((act + 1) < actions.SSize() && !actions.AtStart(act + 1)) { + while (act <= maxAction && actions.types[act].mayCoalesce) { act++; } + act = std::min(act, maxAction); return act - currentAction + 1; } diff --git a/src/UndoHistory.h b/src/UndoHistory.h index 03bea3de3..ab8c24250 100644 --- a/src/UndoHistory.h +++ b/src/UndoHistory.h @@ -67,6 +67,8 @@ public: [[nodiscard]] const char *TextAt(size_t position) const noexcept; }; +constexpr int coalesceFlag = 0x100; + /** * */ @@ -78,6 +80,8 @@ class UndoHistory { int tentativePoint = -1; std::optional<int> detach; std::unique_ptr<ScrapStack> scraps; + struct actPos { int act; size_t position; }; + std::optional<actPos> memory; int PreviousAction() const noexcept; @@ -92,8 +96,12 @@ public: void DropUndoSequence() noexcept; void DeleteUndoHistory() noexcept; + [[nodiscard]] int Actions() const noexcept; + /// 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(int action) noexcept; + [[nodiscard]] int SavePoint() const noexcept; void SetSavePoint() noexcept; bool IsSavePoint() const noexcept; bool BeforeSavePoint() const noexcept; @@ -103,7 +111,17 @@ public: bool AfterDetachPoint() const noexcept; bool AfterOrAtDetachPoint() const noexcept; + void SetCurrent(int action) noexcept; + [[nodiscard]] int Current() const noexcept; + [[nodiscard]] int Type(int action) const noexcept; + [[nodiscard]] Sci::Position Position(int action) const noexcept; + [[nodiscard]] std::string_view Text(int action) noexcept; + void PushUndoActionType(int type, Sci::Position position); + void ChangeLastUndoActionText(size_t length, const char *text); + // Tentative actions are used for input composition so that it can be undone cleanly + void SetTentative(int action) noexcept; + [[nodiscard]] int TentativePoint() const noexcept; void TentativeStart() noexcept; void TentativeCommit() noexcept; bool TentativeActive() const noexcept; diff --git a/test/simpleTests.py b/test/simpleTests.py index 4263ad036..72007f86a 100644 --- a/test/simpleTests.py +++ b/test/simpleTests.py @@ -887,6 +887,82 @@ class TestSimple(unittest.TestCase): self.assertEqual(self.ed.IsRangeWord(6, 7), 0) self.assertEqual(self.ed.IsRangeWord(6, 8), 1) +atInsertion = 0 +atDeletion = 1 +actMayCoalesce = 0x100 + +class TestUndoSaveRestore(unittest.TestCase): + + def setUp(self): + self.xite = Xite.xiteFrame + self.ed = self.xite.ed + self.ed.ClearAll() + self.ed.EmptyUndoBuffer() + self.data = b"xy" + + def testSave(self): + self.ed.InsertText(0, self.data) + self.assertEqual(self.ed.Contents(), b"xy") + self.ed.SetSavePoint() + self.ed.InsertText(0, self.data) + self.assertEqual(self.ed.Contents(), b"xyxy") + self.ed.DeleteRange(1, 2) + self.assertEqual(self.ed.Contents(), b"xy") + self.ed.DeleteRange(1, 1) + self.assertEqual(self.ed.Contents(), b"x") + + self.assertEqual(self.ed.UndoActions, 4) + self.assertEqual(self.ed.UndoCurrent, 4) + self.assertEqual(self.ed.UndoSavePoint, 1) + self.assertEqual(self.ed.UndoTentative, -1) + self.assertEqual(self.ed.GetUndoActionType(0), atInsertion) + self.assertEqual(self.ed.GetUndoActionPosition(0), 0) + self.assertEqual(bytes(self.ed.GetUndoActionText(0)), self.data) + self.assertEqual(self.ed.GetUndoActionType(1), atInsertion) + self.assertEqual(self.ed.GetUndoActionPosition(1), 0) + self.assertEqual(bytes(self.ed.GetUndoActionText(1)), self.data) + self.assertEqual(self.ed.GetUndoActionType(2), atDeletion + actMayCoalesce) + self.assertEqual(self.ed.GetUndoActionPosition(2), 1) + self.assertEqual(bytes(self.ed.GetUndoActionText(2)), b'yx') + self.assertEqual(self.ed.GetUndoActionType(3), atDeletion + actMayCoalesce) + self.assertEqual(self.ed.GetUndoActionPosition(3), 1) + self.assertEqual(bytes(self.ed.GetUndoActionText(3)), b'y') + + def testRestore(self): + self.ed.InsertText(0, self.data) + self.assertEqual(self.ed.Contents(), b"xy") + self.ed.EmptyUndoBuffer() + + self.ed.PushUndoActionType(atInsertion, 0) + self.ed.ChangeLastUndoActionText(2, b'xy') + self.ed.PushUndoActionType(atInsertion, 0) + self.ed.ChangeLastUndoActionText(2, b'xy') + self.ed.PushUndoActionType(atDeletion + actMayCoalesce, 1) + self.ed.ChangeLastUndoActionText(2, b'yx') + self.ed.PushUndoActionType(atDeletion + actMayCoalesce, 1) + self.ed.ChangeLastUndoActionText(1, b'y') + + self.assertEqual(self.ed.UndoActions, 4) + self.ed.SetUndoCurrent(1) + self.ed.SetUndoSavePoint(1) + self.ed.SetUndoTentative(-1) + + self.ed.Undo() + self.assertEqual(self.ed.UndoCurrent, 0) + self.assertEqual(self.ed.Contents(), b"") + self.ed.Redo() + self.assertEqual(self.ed.UndoCurrent, 1) + self.assertEqual(self.ed.Contents(), b"xy") + self.ed.Redo() + self.assertEqual(self.ed.UndoCurrent, 2) + self.assertEqual(self.ed.Contents(), b"xyxy") + self.ed.Redo() # Does 2 actions due to mayCoalesce + self.assertEqual(self.ed.UndoCurrent, 4) + self.assertEqual(self.ed.Contents(), b"x") + + # No more redo actions + self.assertEqual(self.ed.CanRedo(), 0) + class TestChangeHistory(unittest.TestCase): def setUp(self): |