diff options
-rw-r--r-- | call/ScintillaCall.cxx | 8 | ||||
-rw-r--r-- | doc/ScintillaDoc.html | 10 | ||||
-rw-r--r-- | doc/ScintillaHistory.html | 6 | ||||
-rw-r--r-- | include/Scintilla.h | 2 | ||||
-rw-r--r-- | include/Scintilla.iface | 6 | ||||
-rw-r--r-- | include/ScintillaCall.h | 2 | ||||
-rw-r--r-- | include/ScintillaMessages.h | 2 | ||||
-rw-r--r-- | qt/ScintillaEdit/ScintillaDocument.cpp | 5 | ||||
-rw-r--r-- | src/CellBuffer.cxx | 4 | ||||
-rw-r--r-- | src/CellBuffer.h | 1 | ||||
-rw-r--r-- | src/Document.cxx | 48 | ||||
-rw-r--r-- | src/Document.h | 27 | ||||
-rw-r--r-- | src/EditModel.cxx | 59 | ||||
-rw-r--r-- | src/EditModel.h | 41 | ||||
-rw-r--r-- | src/Editor.cxx | 98 | ||||
-rw-r--r-- | src/Editor.h | 7 | ||||
-rw-r--r-- | src/Selection.cxx | 7 | ||||
-rw-r--r-- | src/Selection.h | 2 | ||||
-rw-r--r-- | src/UndoHistory.cxx | 8 | ||||
-rw-r--r-- | src/UndoHistory.h | 1 | ||||
-rw-r--r-- | test/unit/testDocument.cxx | 1 |
21 files changed, 337 insertions, 8 deletions
diff --git a/call/ScintillaCall.cxx b/call/ScintillaCall.cxx index 64c9728e2..32855b9be 100644 --- a/call/ScintillaCall.cxx +++ b/call/ScintillaCall.cxx @@ -1259,6 +1259,14 @@ ChangeHistoryOption ScintillaCall::ChangeHistory() { return static_cast<Scintilla::ChangeHistoryOption>(Call(Message::GetChangeHistory)); } +void ScintillaCall::SetSelectionUndoHistory(bool selectionUndoHistory) { + Call(Message::SetSelectionUndoHistory, selectionUndoHistory); +} + +bool ScintillaCall::SelectionUndoHistory() { + return Call(Message::GetSelectionUndoHistory); +} + Line ScintillaCall::FirstVisibleLine() { return Call(Message::GetFirstVisibleLine); } diff --git a/doc/ScintillaDoc.html b/doc/ScintillaDoc.html index 083062dd2..44df71562 100644 --- a/doc/ScintillaDoc.html +++ b/doc/ScintillaDoc.html @@ -1938,6 +1938,8 @@ struct Sci_TextToFindFull { <a class="message" href="#SCI_ENDUNDOACTION">SCI_ENDUNDOACTION</a><br /> <a class="message" href="#SCI_GETUNDOSEQUENCE">SCI_GETUNDOSEQUENCE → int</a><br /> <a class="message" href="#SCI_ADDUNDOACTION">SCI_ADDUNDOACTION(int token, int flags)</a><br /> + <a class="message" href="#SCI_SETSELECTIONUNDOHISTORY">SCI_SETSELECTIONUNDOHISTORY(bool selectionUndoHistory)</a><br /> + <a class="message" href="#SCI_GETSELECTIONUNDOHISTORY">SCI_GETSELECTIONUNDOHISTORY → bool</a><br /> </code> <p><b id="SCI_UNDO">SCI_UNDO</b><br /> @@ -2016,6 +2018,14 @@ struct Sci_TextToFindFull { look like typing or deletions that look like multiple uses of the Backspace or Delete keys. </p> + <p><b id="SCI_SETSELECTIONUNDOHISTORY">SCI_SETSELECTIONUNDOHISTORY(bool selectionUndoHistory)</b><br /> + <b id="SCI_GETSELECTIONUNDOHISTORY">SCI_GETSELECTIONUNDOHISTORY → bool</b><br /> + The selection for each action can be saved and then restored when undo or redo is performed. + <code>SCI_SETSELECTIONUNDOHISTORY</code> controls this. + The current <code>bool</code> argument may change to a set of flags. + There is a memory cost for this feature with a minimum of 150 bytes for each of undo and redo for each recorded action. + Recording may be turned on at any time.</p> + <h2 id="UndoSaveRestore">Undo Save and Restore</h2> <p>This feature is unfinished and has limitations. diff --git a/doc/ScintillaHistory.html b/doc/ScintillaHistory.html index ba416abe2..5f28aae36 100644 --- a/doc/ScintillaHistory.html +++ b/doc/ScintillaHistory.html @@ -595,6 +595,12 @@ Released 18 December 2024. </li> <li> + Remember selection with undo and redo. Controlled with SCI_SETSELECTIONUNDOHISTORY. + <a href="https://sourceforge.net/p/scintilla/feature-requests/1273/">Feature #1273</a>, + <a href="https://sourceforge.net/p/scintilla/bugs/1479/">Bug #1479</a>, + <a href="https://sourceforge.net/p/scintilla/bugs/1224/">Bug #1224</a>. + </li> + <li> Fix bug on Qt where double-click stopped working when Scintilla instance had been running for weeks. </li> </ul> diff --git a/include/Scintilla.h b/include/Scintilla.h index e434b9f95..9f5b4e7a2 100644 --- a/include/Scintilla.h +++ b/include/Scintilla.h @@ -532,6 +532,8 @@ typedef sptr_t (*SciFnDirectStatus)(sptr_t ptr, unsigned int iMessage, uptr_t wP #define SC_CHANGE_HISTORY_INDICATORS 4 #define SCI_SETCHANGEHISTORY 2780 #define SCI_GETCHANGEHISTORY 2781 +#define SCI_SETSELECTIONUNDOHISTORY 2782 +#define SCI_GETSELECTIONUNDOHISTORY 2783 #define SCI_GETFIRSTVISIBLELINE 2152 #define SCI_GETLINE 2153 #define SCI_GETLINECOUNT 2154 diff --git a/include/Scintilla.iface b/include/Scintilla.iface index 4a3d15b63..ba37ceaf3 100644 --- a/include/Scintilla.iface +++ b/include/Scintilla.iface @@ -1334,6 +1334,12 @@ set void SetChangeHistory=2780(ChangeHistoryOption changeHistory,) # Report change history status. get ChangeHistoryOption GetChangeHistory=2781(,) +# Enable or disable selection undo history. +set void SetSelectionUndoHistory=2782(bool selectionUndoHistory,) + +# Report selection undo history status. +get bool GetSelectionUndoHistory=2783(,) + # Retrieve the display line at the top of the display. get line GetFirstVisibleLine=2152(,) diff --git a/include/ScintillaCall.h b/include/ScintillaCall.h index c1f35571f..ab0bb3306 100644 --- a/include/ScintillaCall.h +++ b/include/ScintillaCall.h @@ -360,6 +360,8 @@ public: Position FormatRangeFull(bool draw, RangeToFormatFull *fr); void SetChangeHistory(Scintilla::ChangeHistoryOption changeHistory); Scintilla::ChangeHistoryOption ChangeHistory(); + void SetSelectionUndoHistory(bool selectionUndoHistory); + bool SelectionUndoHistory(); Line FirstVisibleLine(); Position GetLine(Line line, char *text); std::string GetLine(Line line); diff --git a/include/ScintillaMessages.h b/include/ScintillaMessages.h index df7f2e743..02dd42e24 100644 --- a/include/ScintillaMessages.h +++ b/include/ScintillaMessages.h @@ -285,6 +285,8 @@ enum class Message { FormatRangeFull = 2777, SetChangeHistory = 2780, GetChangeHistory = 2781, + SetSelectionUndoHistory = 2782, + GetSelectionUndoHistory = 2783, GetFirstVisibleLine = 2152, GetLine = 2153, GetLineCount = 2154, diff --git a/qt/ScintillaEdit/ScintillaDocument.cpp b/qt/ScintillaEdit/ScintillaDocument.cpp index b1091cec8..fc5ef2ec3 100644 --- a/qt/ScintillaEdit/ScintillaDocument.cpp +++ b/qt/ScintillaEdit/ScintillaDocument.cpp @@ -55,6 +55,7 @@ public: void NotifyDeleted(Document *doc, void *userData) noexcept override; void NotifyStyleNeeded(Document *doc, void *userData, Sci::Position endPos) override; void NotifyErrorOccurred(Document *doc, void *userData, Status status) override; + void NotifyGroupCompleted(Document *doc, void *userData) noexcept override; }; WatcherHelper::WatcherHelper(ScintillaDocument *owner_) : owner(owner_) { @@ -88,6 +89,10 @@ void WatcherHelper::NotifyErrorOccurred(Document *, void *, Status status) { emit owner->error_occurred(static_cast<int>(status)); } +void WatcherHelper::NotifyGroupCompleted(Document *, void *) noexcept { + // Needed to satisfy protocol. May implement an event in future. +} + ScintillaDocument::ScintillaDocument(QObject *parent, void *pdoc_) : QObject(parent), pdoc(static_cast<Scintilla::IDocumentEditable *>(pdoc_)), docWatcher(nullptr) { if (!pdoc) { diff --git a/src/CellBuffer.cxx b/src/CellBuffer.cxx index 643929352..04486d4c6 100644 --- a/src/CellBuffer.cxx +++ b/src/CellBuffer.cxx @@ -1075,6 +1075,10 @@ int CellBuffer::UndoSequenceDepth() const noexcept { return uh->UndoSequenceDepth(); } +bool CellBuffer::AfterUndoSequenceStart() const noexcept { + return uh->AfterUndoSequenceStart(); +} + void CellBuffer::AddUndoAction(Sci::Position token, bool mayCoalesce) { bool startSequence = false; uh->AppendAction(ActionType::container, token, nullptr, 0, startSequence, mayCoalesce); diff --git a/src/CellBuffer.h b/src/CellBuffer.h index d5d9a0467..1b035597a 100644 --- a/src/CellBuffer.h +++ b/src/CellBuffer.h @@ -167,6 +167,7 @@ public: void BeginUndoAction(bool mayCoalesce=false) noexcept; void EndUndoAction() noexcept; int UndoSequenceDepth() const noexcept; + bool AfterUndoSequenceStart() const noexcept; void AddUndoAction(Sci::Position token, bool mayCoalesce); void DeleteUndoHistory() noexcept; diff --git a/src/Document.cxx b/src/Document.cxx index ac398fcd3..379a88786 100644 --- a/src/Document.cxx +++ b/src/Document.cxx @@ -18,6 +18,7 @@ #include <string_view> #include <vector> #include <array> +#include <map> #include <forward_list> #include <optional> #include <algorithm> @@ -1320,6 +1321,10 @@ bool Document::DeleteChars(Sci::Position pos, Sci::Position len) { } else { enteredModification++; if (!cb.IsReadOnly()) { + if (cb.IsCollectingUndo() && cb.CanRedo()) { + // Abandoning some undo actions so truncate any later selections + TruncateUndoComments(cb.UndoCurrent()); + } NotifyModified( DocModification( ModificationFlags::BeforeDelete | ModificationFlags::User, @@ -1373,6 +1378,10 @@ Sci::Position Document::InsertString(Sci::Position position, const char *s, Sci: s = insertion.c_str(); insertLength = insertion.length(); } + if (cb.IsCollectingUndo() && cb.CanRedo()) { + // Abandoning some undo actions so truncate any later selections + TruncateUndoComments(cb.UndoCurrent()); + } NotifyModified( DocModification( ModificationFlags::BeforeInsert | ModificationFlags::User, @@ -1556,6 +1565,20 @@ Sci::Position Document::Redo() { return newPos; } +void Document::EndUndoAction() noexcept { + cb.EndUndoAction(); + if (UndoSequenceDepth() == 0) { + // Broadcast notification to views to allow end of group processing. + // NotifyGroupCompleted may throw (for memory exhaustion) but this method + // may not as it is called in UndoGroup destructor so ignore exception. + try { + NotifyGroupCompleted(); + } catch (...) { + // Ignore any exception + } + } +} + int Document::UndoSequenceDepth() const noexcept { return cb.UndoSequenceDepth(); } @@ -2504,6 +2527,25 @@ void Document::SetLexInterface(std::unique_ptr<LexInterface> pLexInterface) noex pli = std::move(pLexInterface); } +void Document::SetViewState(void *view, ViewStateShared pVSS) { + viewData[view] = pVSS; +} + +ViewStateShared Document::GetViewState(void *view) const noexcept { + const std::map<void *, ViewStateShared>::const_iterator it = viewData.find(view); + + if (it != viewData.end()) { + return it->second; + } + return {}; +} + +void Document::TruncateUndoComments(int action) { + for (auto &[key, value] : viewData) { + value->TruncateUndo(action); + } +} + int SCI_METHOD Document::SetLineState(Sci_Position line, int state) { const int statePrevious = States()->SetLineState(line, state, LinesTotal()); if (state != statePrevious) { @@ -2700,6 +2742,12 @@ void Document::NotifySavePoint(bool atSavePoint) { } } +void Document::NotifyGroupCompleted() noexcept { + for (const WatcherWithUserData &watcher : watchers) { + watcher.watcher->NotifyGroupCompleted(this, watcher.userData); + } +} + void Document::NotifyModified(DocModification mh) { if (FlagSet(mh.modificationType, ModificationFlags::InsertText)) { decorations->InsertSpace(mh.position, mh.length); diff --git a/src/Document.h b/src/Document.h index cecf02899..4e18c42d1 100644 --- a/src/Document.h +++ b/src/Document.h @@ -172,6 +172,22 @@ public: bool isEnabled; }; +// Base class for view state that can be held and transferred without understanding the contents. +// Declared here but real implementation subclass declared in EditModel +struct ViewState { + ViewState() noexcept = default; + // Deleted so ViewState objects can not be copied + ViewState(const ViewState &) = delete; + ViewState(ViewState &&) = delete; + ViewState &operator=(const ViewState &) = delete; + ViewState &operator=(ViewState &&) = delete; + virtual ~ViewState() noexcept = default; + + virtual void TruncateUndo(int index)=0; +}; + +using ViewStateShared = std::shared_ptr<ViewState>; + struct LexerReleaser { // Called by unique_ptr to destroy/free the Resource void operator()(Scintilla::ILexer5 *pLexer) noexcept { @@ -304,6 +320,8 @@ private: std::unique_ptr<RegexSearchBase> regex; std::unique_ptr<LexInterface> pli; + std::map<void *, ViewStateShared>viewData; + public: Scintilla::EndOfLine eolMode; @@ -396,8 +414,9 @@ public: } bool IsCollectingUndo() const noexcept { return cb.IsCollectingUndo(); } void BeginUndoAction(bool coalesceWithPrior=false) noexcept { cb.BeginUndoAction(coalesceWithPrior); } - void EndUndoAction() noexcept { cb.EndUndoAction(); } + void EndUndoAction() noexcept; int UndoSequenceDepth() const noexcept; + bool AfterUndoSequenceStart() const noexcept { return cb.AfterUndoSequenceStart(); } void AddUndoAction(Sci::Position token, bool mayCoalesce) { cb.AddUndoAction(token, mayCoalesce); } void SetSavePoint(); bool IsSavePoint() const noexcept { return cb.IsSavePoint(); } @@ -534,6 +553,10 @@ public: LexInterface *GetLexInterface() const noexcept; void SetLexInterface(std::unique_ptr<LexInterface> pLexInterface) noexcept; + void SetViewState(void *view, ViewStateShared pVSS); + ViewStateShared GetViewState(void *view) const noexcept; + void TruncateUndoComments(int action); + int SCI_METHOD SetLineState(Sci_Position line, int state) override; int SCI_METHOD GetLineState(Sci_Position line) const override; Sci::Line GetMaxLineState() const noexcept; @@ -574,6 +597,7 @@ public: private: void NotifyModifyAttempt(); void NotifySavePoint(bool atSavePoint); + void NotifyGroupCompleted() noexcept; void NotifyModified(DocModification mh); }; @@ -664,6 +688,7 @@ public: virtual void NotifyDeleted(Document *doc, void *userData) noexcept = 0; virtual void NotifyStyleNeeded(Document *doc, void *userData, Sci::Position endPos) = 0; virtual void NotifyErrorOccurred(Document *doc, void *userData, Scintilla::Status status) = 0; + virtual void NotifyGroupCompleted(Document *doc, void *userData) noexcept = 0; }; } diff --git a/src/EditModel.cxx b/src/EditModel.cxx index 13b3da5b0..60848e6dc 100644 --- a/src/EditModel.cxx +++ b/src/EditModel.cxx @@ -58,6 +58,54 @@ using namespace Scintilla::Internal; Caret::Caret() noexcept : active(false), on(false), period(500) {} +SelectionSimple::SelectionSimple(const Selection &sel) { + selType = sel.selType; + if (sel.IsRectangular()) { + // rectangular or thin + // Could be large so don't remember each range, just the rectangular bounds then reconstitute when undone + rangeRectangular = sel.RectangularCopy(); + } else { + ranges = sel.RangesCopy(); + } + mainRange = sel.Main(); +} + +void ModelState::RememberSelectionForUndo(int index, const Selection &sel) { + historyForUndo.indexCurrent = index; + historyForUndo.ssCurrent = SelectionSimple(sel); +} + +void ModelState::ForgetSelectionForUndo() noexcept { + historyForUndo.indexCurrent = -1; +} + +void ModelState::RememberSelectionOntoStack(int index) { + if ((historyForUndo.indexCurrent >= 0) && (index == historyForUndo.indexCurrent + 1)) { + // Don't overwrite initial selection save if most recent action was coalesced + historyForUndo.stack[index] = historyForUndo.ssCurrent; + } +} + +void ModelState::RememberSelectionForRedoOntoStack(int index, const Selection &sel) { + historyForRedo.stack[index] = SelectionSimple(sel); +} + +const SelectionSimple *ModelState::SelectionFromStack(int index, UndoRedo history) const { + const SelectionHistory &sh = history == UndoRedo::undo ? historyForUndo : historyForRedo; + std::map<int, SelectionSimple>::const_iterator it = sh.stack.find(index); + if (it != sh.stack.end()) { + return &it->second; + } + return {}; +} + +void ModelState::TruncateUndo(int index) { + std::map<int, SelectionSimple>::iterator itUndo = historyForUndo.stack.find(index); + historyForUndo.stack.erase(itUndo, historyForUndo.stack.end()); + std::map<int, SelectionSimple>::iterator itRedo = historyForRedo.stack.find(index); + historyForRedo.stack.erase(itRedo, historyForRedo.stack.end()); +} + EditModel::EditModel() : braces{} { inOverstrike = false; xOffset = 0; @@ -131,3 +179,14 @@ InSelection EditModel::LineEndInSelection(Sci::Line lineDoc) const { int EditModel::GetMark(Sci::Line line) const { return pdoc->GetMark(line, FlagSet(changeHistoryOption, ChangeHistoryOption::Markers)); } + +void EditModel::EnsureModelState() { + if (!modelState && rememberingSelectionForUndo) { + if (ViewStateShared vss = pdoc->GetViewState(this)) { + modelState = std::dynamic_pointer_cast<ModelState>(vss); + } else { + modelState = std::make_shared<ModelState>(); + pdoc->SetViewState(this, std::static_pointer_cast<ViewState>(modelState)); + } + } +} diff --git a/src/EditModel.h b/src/EditModel.h index e36d26fb1..716fd5788 100644 --- a/src/EditModel.h +++ b/src/EditModel.h @@ -21,6 +21,41 @@ public: Caret() noexcept; }; +// Simplified version of selection which won't contain rectangular selection realized +// into ranges as too much data. +// Just a type and single range for now. + +struct SelectionSimple { + std::vector<SelectionRange> ranges; + SelectionRange rangeRectangular; + size_t mainRange = 0; + Selection::SelTypes selType = Selection::SelTypes::stream; + + SelectionSimple() = default; + explicit SelectionSimple(const Selection &sel); +}; + +enum class UndoRedo { undo, redo }; + +struct SelectionHistory { + int indexCurrent = 0; + SelectionSimple ssCurrent; + std::map<int, SelectionSimple> stack; +}; + +struct ModelState : ViewState { + SelectionHistory historyForUndo; + SelectionHistory historyForRedo; + void RememberSelectionForUndo(int index, const Selection &sel); + void ForgetSelectionForUndo() noexcept; + void RememberSelectionOntoStack(int index); + void RememberSelectionForRedoOntoStack(int index, const Selection &sel); + const SelectionSimple *SelectionFromStack(int index, UndoRedo history) const; + virtual void TruncateUndo(int index) final; +}; + +using ModelStateShared = std::shared_ptr<ModelState>; + class EditModel { public: bool inOverstrike; @@ -57,6 +92,10 @@ public: Document *pdoc; + bool rememberingSelectionForUndo = false; + bool needRedoRemembered = false; + ModelStateShared modelState; + EditModel(); // Deleted so EditModel objects can not be copied. EditModel(const EditModel &) = delete; @@ -75,6 +114,8 @@ public: const char *GetFoldDisplayText(Sci::Line lineDoc) const noexcept; InSelection LineEndInSelection(Sci::Line lineDoc) const; [[nodiscard]] int GetMark(Sci::Line line) const; + + void EnsureModelState(); }; } diff --git a/src/Editor.cxx b/src/Editor.cxx index b60d96643..8a7b15719 100644 --- a/src/Editor.cxx +++ b/src/Editor.cxx @@ -926,6 +926,37 @@ void Editor::SetLastXChosen() { lastXChosen = static_cast<int>(pt.x) + xOffset; } +void Editor::RememberSelectionForUndo(int index) { + EnsureModelState(); + if (modelState) { + modelState->RememberSelectionForUndo(index, sel); + needRedoRemembered = true; + // Remember selection at end of processing current message + } +} + +void Editor::RememberSelectionOntoStack(int index) { + EnsureModelState(); + if (modelState) { + // Is undo currently inside a group? + if (!pdoc->AfterUndoSequenceStart()) { + // Don't remember selections inside a grouped sequence as can only + // unto or redo to the start and end of the group. + modelState->RememberSelectionOntoStack(index); + } + } +} + +void Editor::RememberCurrentSelectionForRedoOntoStack() { + if (needRedoRemembered && (pdoc->UndoSequenceDepth() == 0)) { + EnsureModelState(); + if (modelState) { + modelState->RememberSelectionForRedoOntoStack(pdoc->UndoCurrent(), sel); + needRedoRemembered = false; + } + } +} + void Editor::ScrollTo(Sci::Line line, bool moveThumb) { const Sci::Line topLineNew = std::clamp<Sci::Line>(line, 0, MaxScrollPos()); if (topLineNew != topLine) { @@ -2085,13 +2116,13 @@ void Editor::InsertCharacter(std::string_view sv, CharacterSource charSource) { } } } + ThinRectangularRange(); } if (wrapOccurred) { SetScrollBars(); SetVerticalScrollPos(); Redraw(); } - ThinRectangularRange(); // If in wrap mode rewrap current line so EnsureCaretVisible has accurate information EnsureCaretVisible(); // Avoid blinking during rapid typing: @@ -2367,22 +2398,43 @@ void Editor::SelectAll() { Redraw(); } +void Editor::RestoreSelection(Sci::Position newPos, UndoRedo history) { + if (rememberingSelectionForUndo && modelState) { + // Undo wants the element after the current as it just undid it + const int index = pdoc->UndoCurrent() + (history == UndoRedo::undo ? 1 : 0); + const SelectionSimple *pss = modelState->SelectionFromStack(index, history); + if (pss) { + sel.selType = pss->selType; + if (sel.IsRectangular()) { + sel.Rectangular() = pss->rangeRectangular; + // Reconstitute ranges from rectangular range + SetRectangularRange(); + } else { + sel.SetRanges(pss->ranges); + } + // Unsure if this is safe with SetMain potentially failing if document doesn't appear the same. + // Maybe this can occur if the user changes font or wrap mode? + sel.SetMain(pss->mainRange); + newPos = -1; // Used selection from stack so don't use position returned from undo/redo. + } + } + if (newPos >= 0) + SetEmptySelection(newPos); + EnsureCaretVisible(); +} + void Editor::Undo() { if (pdoc->CanUndo()) { InvalidateCaret(); const Sci::Position newPos = pdoc->Undo(); - if (newPos >= 0) - SetEmptySelection(newPos); - EnsureCaretVisible(); + RestoreSelection(newPos, UndoRedo::undo); } } void Editor::Redo() { if (pdoc->CanRedo()) { const Sci::Position newPos = pdoc->Redo(); - if (newPos >= 0) - SetEmptySelection(newPos); - EnsureCaretVisible(); + RestoreSelection(newPos, UndoRedo::redo); } } @@ -2458,6 +2510,17 @@ void Editor::NotifyErrorOccurred(Document *, void *, Status status) { errorStatus = status; } +void Editor::NotifyGroupCompleted(Document *, void *) noexcept { + // RememberCurrentSelectionForRedoOntoStack may throw (for memory exhaustion) + // but this method may not as it is called in UndoGroup destructor so ignore + // exception. + try { + RememberCurrentSelectionForRedoOntoStack(); + } catch (...) { + // Ignore any exception + } +} + void Editor::NotifyChar(int ch, CharacterSource charSource) { NotificationData scn = {}; scn.nmhdr.code = Notification::CharAdded; @@ -2726,6 +2789,14 @@ void Editor::NotifyModified(Document *, DocModification mh, void *) { view.llc.Invalidate(LineLayout::ValidLevel::checkTextAndStyle); } } else { + if (rememberingSelectionForUndo && FlagSet(mh.modificationType, ModificationFlags::User)) { + if (FlagSet(mh.modificationType, ModificationFlags::BeforeInsert | ModificationFlags::BeforeDelete)) { + RememberSelectionForUndo(pdoc->UndoCurrent()); + } + if (FlagSet(mh.modificationType, ModificationFlags::InsertText | ModificationFlags::DeleteText)) { + RememberSelectionOntoStack(pdoc->UndoCurrent()); + } + } // Move selection and brace highlights if (FlagSet(mh.modificationType, ModificationFlags::InsertText)) { sel.MovePositions(true, mh.position, mh.length); @@ -5460,6 +5531,7 @@ void Editor::SetDocPointer(Document *document) { pdoc = document; } pdoc->AddRef(); + modelState.reset(); pcs = ContractionStateCreate(pdoc->IsLarge()); // Ensure all positions within document @@ -8615,6 +8687,13 @@ sptr_t Editor::WndProc(Message iMessage, uptr_t wParam, sptr_t lParam) { case Message::GetChangeHistory: return static_cast<sptr_t>(changeHistoryOption); + case Message::SetSelectionUndoHistory: + rememberingSelectionForUndo = wParam; + break; + + case Message::GetSelectionUndoHistory: + return rememberingSelectionForUndo; + case Message::SetExtraAscent: vs.extraAscent = static_cast<int>(wParam); InvalidateStyleRedraw(); @@ -9024,6 +9103,11 @@ sptr_t Editor::WndProc(Message iMessage, uptr_t wParam, sptr_t lParam) { default: return DefWndProc(iMessage, wParam, lParam); } + + // If there was a change that needs its selection saved and it wasn't explicity saved + // then do that here. + RememberCurrentSelectionForRedoOntoStack(); + //Platform::DebugPrintf("end wnd proc\n"); return 0; } diff --git a/src/Editor.h b/src/Editor.h index d95f552ad..655184a31 100644 --- a/src/Editor.h +++ b/src/Editor.h @@ -178,6 +178,8 @@ constexpr XYScrollOptions operator|(XYScrollOptions a, XYScrollOptions b) noexce return static_cast<XYScrollOptions>(static_cast<int>(a) | static_cast<int>(b)); } +struct SelectionStack; + /** */ class Editor : public EditModel, public DocWatcher { @@ -367,6 +369,9 @@ protected: // ScintillaBase subclass needs access to much of Editor SelectionPosition MovePositionSoVisible(Sci::Position pos, int moveDir); Point PointMainCaret(); void SetLastXChosen(); + void RememberSelectionForUndo(int index); + void RememberSelectionOntoStack(int index); + void RememberCurrentSelectionForRedoOntoStack(); void ScrollTo(Sci::Line line, bool moveThumb=true); virtual void ScrollText(Sci::Line linesToMove); @@ -443,6 +448,7 @@ protected: // ScintillaBase subclass needs access to much of Editor virtual void Paste() = 0; void Clear(); virtual void SelectAll(); + void RestoreSelection(Sci::Position newPos, UndoRedo history); virtual void Undo(); virtual void Redo(); void DelCharBack(bool allowLineStartDeletion); @@ -477,6 +483,7 @@ protected: // ScintillaBase subclass needs access to much of Editor void NotifyDeleted(Document *document, void *userData) noexcept override; void NotifyStyleNeeded(Document *doc, void *userData, Sci::Position endStyleNeeded) override; void NotifyErrorOccurred(Document *doc, void *userData, Scintilla::Status status) override; + void NotifyGroupCompleted(Document *, void *) noexcept override; void NotifyMacroRecord(Scintilla::Message iMessage, Scintilla::uptr_t wParam, Scintilla::sptr_t lParam); void ContainerNeedsUpdate(Scintilla::Update flags) noexcept; diff --git a/src/Selection.cxx b/src/Selection.cxx index 82614b040..a4531d65b 100644 --- a/src/Selection.cxx +++ b/src/Selection.cxx @@ -217,6 +217,10 @@ SelectionRange &Selection::Rectangular() noexcept { return rangeRectangular; } +SelectionRange Selection::RectangularCopy() const noexcept { + return rangeRectangular; +} + SelectionSegment Selection::Limits() const noexcept { PLATFORM_ASSERT(!ranges.empty()); SelectionSegment sr(ranges[0].anchor, ranges[0].caret); @@ -456,3 +460,6 @@ void Selection::RotateMain() noexcept { mainRange = (mainRange + 1) % ranges.size(); } +void Selection::SetRanges(const Ranges &rangesToSet) { + ranges = rangesToSet; +} diff --git a/src/Selection.h b/src/Selection.h index 28db77445..c0d785870 100644 --- a/src/Selection.h +++ b/src/Selection.h @@ -163,6 +163,7 @@ public: Sci::Position MainCaret() const noexcept; Sci::Position MainAnchor() const noexcept; SelectionRange &Rectangular() noexcept; + SelectionRange RectangularCopy() const noexcept; SelectionSegment Limits() const noexcept; // This is for when you want to move the caret in response to a // user direction command - for rectangular selections, use the range @@ -202,6 +203,7 @@ public: Ranges RangesCopy() const { return ranges; } + void SetRanges(const Ranges &rangesToSet); }; } diff --git a/src/UndoHistory.cxx b/src/UndoHistory.cxx index c0d7ba5f2..29a80c6d1 100644 --- a/src/UndoHistory.cxx +++ b/src/UndoHistory.cxx @@ -354,6 +354,14 @@ int UndoHistory::UndoSequenceDepth() const noexcept { return undoSequenceDepth; } +bool UndoHistory::AfterUndoSequenceStart() const noexcept { + if (currentAction == 0) { + return false; + } + // Count back to last sequence start? + return !actions.AtStart(currentAction-1); +} + void UndoHistory::DropUndoSequence() noexcept { undoSequenceDepth = 0; } diff --git a/src/UndoHistory.h b/src/UndoHistory.h index be639c541..299e28f58 100644 --- a/src/UndoHistory.h +++ b/src/UndoHistory.h @@ -102,6 +102,7 @@ public: void BeginUndoAction(bool mayCoalesce=false) noexcept; void EndUndoAction() noexcept; int UndoSequenceDepth() const noexcept; + bool AfterUndoSequenceStart() const noexcept; void DropUndoSequence() noexcept; void DeleteUndoHistory() noexcept; diff --git a/test/unit/testDocument.cxx b/test/unit/testDocument.cxx index c4985f330..e4b674987 100644 --- a/test/unit/testDocument.cxx +++ b/test/unit/testDocument.cxx @@ -8,6 +8,7 @@ #include <stdexcept> #include <string_view> #include <vector> +#include <map> #include <set> #include <optional> #include <algorithm> |