aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorNeil <nyamatongwe@gmail.com>2024-02-09 21:45:35 +1100
committerNeil <nyamatongwe@gmail.com>2024-02-09 21:45:35 +1100
commitbd53ffcbefe4e7a22fc493b1916939bae5f9dc1d (patch)
treebfe435d75b222d464a9cbb21e25583d33c591936
parent07a7683902feb4b9944394b9378f0a1a51972497 (diff)
downloadscintilla-mirror-bd53ffcbefe4e7a22fc493b1916939bae5f9dc1d.tar.gz
Implement API to read and write undo history from applications.
-rw-r--r--call/ScintillaCall.cxx52
-rw-r--r--doc/ScintillaDoc.html118
-rw-r--r--include/Scintilla.h12
-rw-r--r--include/Scintilla.iface36
-rw-r--r--include/ScintillaCall.h13
-rw-r--r--include/ScintillaMessages.h12
-rw-r--r--src/CellBuffer.cxx48
-rw-r--r--src/CellBuffer.h13
-rw-r--r--src/Document.cxx48
-rw-r--r--src/Document.h13
-rw-r--r--src/Editor.cxx43
-rw-r--r--src/UndoHistory.cxx95
-rw-r--r--src/UndoHistory.h18
-rw-r--r--test/simpleTests.py76
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>&cir; <a class="toc" href="#UndoAndRedo">Undo and Redo</a></td>
+ <td>&cir; <a class="toc" href="#UndoSaveRestore">Undo save and restore</a></td>
<td>&cir; <a class="toc" href="#ChangeHistory">Change history</a></td>
- <td>&cir; <a class="toc" href="#ScrollingAndAutomaticScrolling">Scrolling and automatic scrolling</a></td>
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#ScrollingAndAutomaticScrolling">Scrolling and automatic scrolling</a></td>
<td>&cir; <a class="toc" href="#WhiteSpace">White space</a></td>
<td>&cir; <a class="toc" href="#Cursor">Cursor</a></td>
- <td>&cir; <a class="toc" href="#MouseCapture">Mouse capture</a></td>
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#MouseCapture">Mouse capture</a></td>
<td>&cir; <a class="toc" href="#LineEndings">Line endings</a></td>
<td>&cir; <a class="toc" href="#Words">Words</a></td>
- <td>&cir; <a class="toc" href="#Styling">Styling</a></td>
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#Styling">Styling</a></td>
<td>&cir; <a class="toc" href="#StyleDefinition">Style definition</a></td>
<td>&cir; <a class="toc" href="#ElementColours">Element colours</a></td>
- <td>&cir; <a class="toc" href="#CaretAndSelectionStyles">Selection, caret, and hotspot styles</a></td>
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#CaretAndSelectionStyles">Selection, caret, and hotspot styles</a></td>
<td>&cir; <a class="toc" href="#CharacterRepresentations">Character representations</a></td>
<td>&cir; <a class="toc" href="#Margins">Margins</a></td>
- <td>&cir; <a class="toc" href="#Annotations">Annotations</a></td>
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#Annotations">Annotations</a></td>
<td>&cir; <a class="toc" href="#EndOfLineAnnotations">End of Line Annotations</a></td>
<td>&cir; <a class="toc" href="#OtherSettings">Other settings</a></td>
- <td>&cir; <a class="toc" href="#BraceHighlighting">Brace highlighting</a></td>
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#BraceHighlighting">Brace highlighting</a></td>
<td>&cir; <a class="toc" href="#TabsAndIndentationGuides">Tabs and Indentation Guides</a></td>
<td>&cir; <a class="toc" href="#Markers">Markers</a></td>
- <td>&cir; <a class="toc" href="#Indicators">Indicators</a></td>
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#Indicators">Indicators</a></td>
<td>&cir; <a class="toc" href="#Autocompletion">Autocompletion</a></td>
<td>&cir; <a class="toc" href="#UserLists">User lists</a></td>
- <td>&cir; <a class="toc" href="#CallTips">Call tips</a></td>
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#CallTips">Call tips</a></td>
<td>&cir; <a class="toc" href="#KeyboardCommands">Keyboard commands</a></td>
<td>&cir; <a class="toc" href="#KeyBindings">Key bindings</a></td>
- <td>&cir; <a class="toc" href="#PopupEditMenu">Popup edit menu</a></td>
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#PopupEditMenu">Popup edit menu</a></td>
<td>&cir; <a class="toc" href="#MacroRecording">Macro recording</a></td>
<td>&cir; <a class="toc" href="#Printing">Printing</a></td>
- <td>&cir; <a class="toc" href="#DirectAccess">Direct access</a></td>
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#DirectAccess">Direct access</a></td>
<td>&cir; <a class="toc" href="#MultipleViews">Multiple views</a></td>
<td>&cir; <a class="toc" href="#BackgroundLoadSave">Background loading and saving</a></td>
- <td>&cir; <a class="toc" href="#DocumentInterface">Document interface</a></td>
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#DocumentInterface">Document interface</a></td>
<td>&cir; <a class="toc" href="#Folding">Folding</a></td>
<td>&cir; <a class="toc" href="#LineWrapping">Line wrapping</a></td>
- <td>&cir; <a class="toc" href="#Zooming">Zooming</a></td>
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#Zooming">Zooming</a></td>
<td>&cir; <a class="toc" href="#LongLines">Long lines</a></td>
<td>&cir; <a class="toc" href="#Accessibility">Accessibility</a></td>
- <td>&cir; <a class="toc" href="#Lexer">Lexer</a></td>
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#Lexer">Lexer</a></td>
<td>&cir; <a class="toc" href="#LexerObjects">Lexer objects</a></td>
<td>&cir; <a class="toc" href="#Notifications">Notifications</a></td>
- <td>&cir; <a class="toc" href="#Images">Images</a></td>
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#Images">Images</a></td>
<td>&cir; <a class="toc" href="#GTK">GTK</a></td>
<td>&cir; <a class="toc" href="#ProvisionalMessages"><span class="provisional">Provisional messages</span></a></td>
- <td>&cir; <a class="toc" href="#DeprecatedMessages">Deprecated messages</a></td>
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#DeprecatedMessages">Deprecated messages</a></td>
<td>&cir; <a class="toc" href="#EditMessagesNeverSupportedByScintilla">Edit messages never supported by Scintilla</a></td>
<td>&cir; <a class="toc" href="#RemovedFeatures">Removed features</a></td>
+ </tr>
+
+ <tr>
<td>&cir; <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 &rarr; int</a><br />
+ <a class="message" href="#SCI_SETUNDOSAVEPOINT">SCI_SETUNDOSAVEPOINT(int action)</a><br />
+ <a class="message" href="#SCI_GETUNDOSAVEPOINT">SCI_GETUNDOSAVEPOINT &rarr; int</a><br />
+ <a class="message" href="#SCI_SETUNDOCURRENT">SCI_SETUNDOCURRENT(int action)</a><br />
+ <a class="message" href="#SCI_GETUNDOCURRENT">SCI_GETUNDOCURRENT &rarr; int</a><br />
+ <a class="message" href="#SCI_SETUNDOTENTATIVE">SCI_SETUNDOTENTATIVE(int action)</a><br />
+ <a class="message" href="#SCI_GETUNDOTENTATIVE">SCI_GETUNDOTENTATIVE &rarr; 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) &rarr; int</a><br />
+ <a class="message" href="#SCI_GETUNDOACTIONPOSITION">SCI_GETUNDOACTIONPOSITION(int action) &rarr; position</a><br />
+ <a class="message" href="#SCI_GETUNDOACTIONTEXT ">SCI_GETUNDOACTIONTEXT(int action, char *text) &rarr; 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):