From 8d59bf9abb2699c4dc6e07eae339f989f701a065 Mon Sep 17 00:00:00 2001 From: Roy Qu Date: Wed, 6 Jul 2022 14:11:32 +0800 Subject: [PATCH] - redesign redo system - fix: correctly restore editor's modified status when undo/redo --- NEWS.md | 1 + RedPandaIDE/qsynedit/SynEdit.cpp | 187 ++++++++++++---------------- RedPandaIDE/qsynedit/SynEdit.h | 4 +- RedPandaIDE/qsynedit/TextBuffer.cpp | 147 ++++++++++++++++++---- RedPandaIDE/qsynedit/TextBuffer.h | 36 +++++- 5 files changed, 238 insertions(+), 137 deletions(-) diff --git a/NEWS.md b/NEWS.md index 93187408..fd815718 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,6 +11,7 @@ Red Panda C++ Version 1.1.4 - fix: undo chains - enhancement: prevent group undo when caret position changed - fix: undo link break may lose leading spaces + - fix: correctly restore editor's modified status when undo/redo Red Panda C++ Version 1.1.3 diff --git a/RedPandaIDE/qsynedit/SynEdit.cpp b/RedPandaIDE/qsynedit/SynEdit.cpp index b8150286..adab86d1 100644 --- a/RedPandaIDE/qsynedit/SynEdit.cpp +++ b/RedPandaIDE/qsynedit/SynEdit.cpp @@ -80,8 +80,8 @@ SynEdit::SynEdit(QWidget *parent) : QAbstractScrollArea(parent), mUndoList = std::make_shared(); mUndoList->connect(mUndoList.get(), &SynEditUndoList::addedUndo, this, &SynEdit::onUndoAdded); - mRedoList = std::make_shared(); - mRedoList->connect(mRedoList.get(), &SynEditUndoList::addedUndo, this, &SynEdit::onRedoAdded); + mRedoList = std::make_shared(); +// mRedoList->connect(mRedoList.get(), &SynEditUndoList::addedUndo, this, &SynEdit::onRedoAdded); mForegroundColor=palette().color(QPalette::Text); mBackgroundColor=palette().color(QPalette::Base); @@ -353,7 +353,7 @@ bool SynEdit::canUndo() const bool SynEdit::canRedo() const { - return !mReadOnly && mRedoList->canUndo(); + return !mReadOnly && mRedoList->canRedo(); } int SynEdit::maxScrollWidth() const @@ -3089,7 +3089,7 @@ void SynEdit::doPasteFromClipboard() BufferCoord vEndOfBlock = blockEnd(); mBlockBegin = vStartOfBlock; mBlockEnd = vEndOfBlock; - qDebug()<endBlock(); } @@ -3428,7 +3428,7 @@ void SynEdit::updateModifiedStatus() bool oldModified = mModified; mModified = !mUndoList->initialState(); setModified(mModified); - qDebug()<lastChangeReason() == SynChangeReason::GroupBreak) { - int OldBlockNumber = mRedoList->blockChangeNumber(); - auto action = finally([&,this]{ - mRedoList->setBlockChangeNumber(OldBlockNumber); - }); + while (mUndoList->lastChangeReason() == SynChangeReason::GroupBreak) { PSynEditUndoItem item = mUndoList->popItem(); - mRedoList->setBlockChangeNumber(item->changeNumber()); - mRedoList->addGroupBreak(); + mRedoList->addRedo(item); } PSynEditUndoItem item = mUndoList->peekItem(); if (item) { size_t oldChangeNumber = item->changeNumber(); - size_t saveChangeNumber = mRedoList->blockChangeNumber(); - mRedoList->setBlockChangeNumber(item->changeNumber()); { - auto action = finally([&,this] { - mRedoList->setBlockChangeNumber(saveChangeNumber); - }); - //skip group chain breakers - if (mUndoList->lastChangeReason()==SynChangeReason::GroupBreak) { - while (!mUndoList->isEmpty() && mUndoList->lastChangeReason()==SynChangeReason::GroupBreak) { - doUndoItem(); - } - } SynChangeReason lastChange = mUndoList->lastChangeReason(); bool keepGoing; do { @@ -4339,6 +4323,8 @@ void SynEdit::doUndo() } while (keepGoing); } } + updateModifiedStatus(); + onChanged(); } void SynEdit::doUndoItem() @@ -4359,43 +4345,47 @@ void SynEdit::doUndoItem() mOptions.setFlag(eoScrollPastEol); switch(item->changeReason()) { case SynChangeReason::Caret: - mRedoList->addChange( + mRedoList->addRedo( item->changeReason(), caretXY(), caretXY(), QStringList(), - item->changeSelMode()); + item->changeSelMode(), + item->changeNumber()); internalSetCaretXY(item->changeStartPos()); break; case SynChangeReason::LeftTop: BufferCoord p; p.ch = leftChar(); p.line = topLine(); - mRedoList->addChange( + mRedoList->addRedo( item->changeReason(), p, p, QStringList(), - item->changeSelMode()); + item->changeSelMode(), + item->changeNumber()); setLeftChar(item->changeStartPos().ch); setTopLine(item->changeStartPos().line); break; case SynChangeReason::Selection: - mRedoList->addChange( + mRedoList->addRedo( item->changeReason(), mBlockBegin, mBlockEnd, QStringList(), - item->changeSelMode()); + item->changeSelMode(), + item->changeNumber()); setCaretAndSelection(caretXY(), item->changeStartPos(), item->changeEndPos()); break; case SynChangeReason::Insert: { QStringList tmpText = getContent(item->changeStartPos(),item->changeEndPos(),item->changeSelMode()); doDeleteText(item->changeStartPos(),item->changeEndPos(),item->changeSelMode()); - mRedoList->addChange( + mRedoList->addRedo( item->changeReason(), item->changeStartPos(), item->changeEndPos(), tmpText, - item->changeSelMode()); + item->changeSelMode(), + item->changeNumber()); internalSetCaretXY(item->changeStartPos()); break; } @@ -4403,23 +4393,25 @@ void SynEdit::doUndoItem() setBlockBegin(BufferCoord{item->changeStartPos().ch, item->changeStartPos().line-1}); setBlockEnd(BufferCoord{item->changeEndPos().ch, item->changeEndPos().line-1}); doMoveSelDown(); - mRedoList->addChange( + mRedoList->addRedo( item->changeReason(), item->changeStartPos(), item->changeEndPos(), item->changeText(), - item->changeSelMode()); + item->changeSelMode(), + item->changeNumber()); break; case SynChangeReason::MoveSelectionDown: setBlockBegin(BufferCoord{item->changeStartPos().ch, item->changeStartPos().line+1}); setBlockEnd(BufferCoord{item->changeEndPos().ch, item->changeEndPos().line+1}); doMoveSelUp(); - mRedoList->addChange( + mRedoList->addRedo( item->changeReason(), item->changeStartPos(), item->changeEndPos(), item->changeText(), - item->changeSelMode()); + item->changeSelMode(), + item->changeNumber()); break; case SynChangeReason::Delete: { // If there's no selection, we have to set @@ -4431,12 +4423,13 @@ void SynEdit::doUndoItem() item->changeStartPos().line, item->changeEndPos().line); internalSetCaretXY(item->changeEndPos()); - mRedoList->addChange( + mRedoList->addRedo( item->changeReason(), item->changeStartPos(), item->changeEndPos(), item->changeText(), - item->changeSelMode()); + item->changeSelMode(), + item->changeNumber()); ensureCursorPosVisible(); break; } @@ -4456,12 +4449,13 @@ void SynEdit::doUndoItem() mDocument->deleteAt(mCaretY); doLinesDeleted(mCaretY, 1); } - mRedoList->addChange( + mRedoList->addRedo( item->changeReason(), item->changeStartPos(), item->changeEndPos(), item->changeText(), - item->changeSelMode()); + item->changeSelMode(), + item->changeNumber()); break; } default: @@ -4479,51 +4473,38 @@ void SynEdit::doRedo() if (!item) return; size_t oldChangeNumber = item->changeNumber(); - size_t saveChangeNumber = mUndoList->blockChangeNumber(); - mUndoList->setBlockChangeNumber(item->changeNumber()); - { - auto action = finally([&,this]{ - mUndoList->setBlockChangeNumber(saveChangeNumber); - }); - //skip group chain breakers - if (mRedoList->lastChangeReason()==SynChangeReason::GroupBreak) { - while (!mRedoList->isEmpty() && mRedoList->lastChangeReason()==SynChangeReason::GroupBreak) { - doRedoItem(); - } - } - SynChangeReason lastChange = mRedoList->lastChangeReason(); - bool keepGoing; - do { - doRedoItem(); - item = mRedoList->peekItem(); - if (!item) - keepGoing = false; - else { - if (item->changeNumber() == oldChangeNumber) - keepGoing = true; - else { - keepGoing = (mOptions.testFlag(eoGroupUndo) && - (lastChange == item->changeReason())); - } - oldChangeNumber=item->changeNumber(); - lastChange = item->changeReason(); - } - } while (keepGoing); + //skip group chain breakers + while (mRedoList->lastChangeReason()==SynChangeReason::GroupBreak) { + PSynEditUndoItem item = mRedoList->popItem(); + mUndoList->restoreChange(item); } - //Remove Group Break - if (mRedoList->lastChangeReason() == SynChangeReason::GroupBreak) { - int OldBlockNumber = mUndoList->blockChangeNumber(); - item = mRedoList->popItem(); - { - auto action2=finally([&,this]{ - mUndoList->setBlockChangeNumber(OldBlockNumber); - }); - mUndoList->setBlockChangeNumber(item->changeNumber()); - mUndoList->addGroupBreak(); + SynChangeReason lastChange = mRedoList->lastChangeReason(); + bool keepGoing; + do { + doRedoItem(); + item = mRedoList->peekItem(); + if (!item) + keepGoing = false; + else { + if (item->changeNumber() == oldChangeNumber) + keepGoing = true; + else { + keepGoing = (mOptions.testFlag(eoGroupUndo) && + (lastChange == item->changeReason())); } - updateModifiedStatus(); + oldChangeNumber=item->changeNumber(); + lastChange = item->changeReason(); + } + } while (keepGoing); + + //restore Group Break + while (mRedoList->lastChangeReason()==SynChangeReason::GroupBreak) { + PSynEditUndoItem item = mRedoList->popItem(); + mUndoList->restoreChange(item); } + updateModifiedStatus(); + onChanged(); } void SynEdit::doRedoItem() @@ -4545,33 +4526,36 @@ void SynEdit::doRedoItem() }); switch(item->changeReason()) { case SynChangeReason::Caret: - mUndoList->addChange( + mUndoList->restoreChange( item->changeReason(), caretXY(), caretXY(), QStringList(), - mActiveSelectionMode); + mActiveSelectionMode, + item->changeNumber()); internalSetCaretXY(item->changeStartPos()); break; case SynChangeReason::LeftTop: BufferCoord p; p.ch = leftChar(); p.line = topLine(); - mUndoList->addChange( + mUndoList->restoreChange( item->changeReason(), p, p, QStringList(), - item->changeSelMode()); + item->changeSelMode(), + item->changeNumber()); setLeftChar(item->changeStartPos().ch); setTopLine(item->changeStartPos().line); break; case SynChangeReason::Selection: - mUndoList->addChange( + mUndoList->restoreChange( item->changeReason(), mBlockBegin, mBlockEnd, QStringList(), - mActiveSelectionMode); + mActiveSelectionMode, + item->changeNumber()); setCaretAndSelection( caretXY(), item->changeStartPos(), @@ -4581,23 +4565,25 @@ void SynEdit::doRedoItem() setBlockBegin(BufferCoord{item->changeStartPos().ch, item->changeStartPos().line}); setBlockEnd(BufferCoord{item->changeEndPos().ch, item->changeEndPos().line}); doMoveSelUp(); - mUndoList->addChange( + mUndoList->restoreChange( item->changeReason(), item->changeStartPos(), item->changeEndPos(), item->changeText(), - item->changeSelMode()); + item->changeSelMode(), + item->changeNumber()); break; case SynChangeReason::MoveSelectionDown: setBlockBegin(BufferCoord{item->changeStartPos().ch, item->changeStartPos().line}); setBlockEnd(BufferCoord{item->changeEndPos().ch, item->changeEndPos().line}); doMoveSelDown(); - mUndoList->addChange( + mUndoList->restoreChange( item->changeReason(), item->changeStartPos(), item->changeEndPos(), item->changeText(), - item->changeSelMode()); + item->changeSelMode(), + item->changeNumber()); break; case SynChangeReason::Insert: setCaretAndSelection( @@ -4608,25 +4594,26 @@ void SynEdit::doRedoItem() item->changeStartPos().line, item->changeEndPos().line); internalSetCaretXY(item->changeEndPos()); - mUndoList->addChange(item->changeReason(), + mUndoList->restoreChange(item->changeReason(), item->changeStartPos(), item->changeEndPos(), QStringList(), - item->changeSelMode()); + item->changeSelMode(), + item->changeNumber()); break; case SynChangeReason::Delete: { doDeleteText(item->changeStartPos(),item->changeEndPos(),item->changeSelMode()); - mUndoList->addChange(item->changeReason(), item->changeStartPos(), + mUndoList->restoreChange(item->changeReason(), item->changeStartPos(), item->changeEndPos(),item->changeText(), - item->changeSelMode()); + item->changeSelMode(),item->changeNumber()); internalSetCaretXY(item->changeStartPos()); break; }; case SynChangeReason::LineBreak: { BufferCoord CaretPt = item->changeStartPos(); - mUndoList->addChange(item->changeReason(), item->changeStartPos(), + mUndoList->restoreChange(item->changeReason(), item->changeStartPos(), item->changeEndPos(),item->changeText(), - item->changeSelMode()); + item->changeSelMode(),item->changeNumber()); setCaretAndSelection(CaretPt, CaretPt, CaretPt); commandProcessor(SynEditorCommand::ecLineBreak); break; @@ -6591,8 +6578,6 @@ int SynEdit::charWidth() const void SynEdit::setUndoLimit(int size) { mUndoList->setMaxUndoActions(size); - - mRedoList->setMaxUndoActions(size); } int SynEdit::charsInWindow() const @@ -6909,12 +6894,6 @@ void SynEdit::setTopLine(int Value) } } -void SynEdit::onRedoAdded() -{ - updateModifiedStatus(); - onChanged(); -} - void SynEdit::onGutterChanged() { if (mGutter.showLineNumbers() && mGutter.autoSize()) diff --git a/RedPandaIDE/qsynedit/SynEdit.h b/RedPandaIDE/qsynedit/SynEdit.h index 5cc23748..1e871e84 100644 --- a/RedPandaIDE/qsynedit/SynEdit.h +++ b/RedPandaIDE/qsynedit/SynEdit.h @@ -633,7 +633,7 @@ private slots: void onLinesDeleted(int index, int count); void onLinesInserted(int index, int count); void onLinesPutted(int index, int count); - void onRedoAdded(); + //void onRedoAdded(); void onScrollTimeout(); void onDraggingScrollTimeout(); void onUndoAdded(); @@ -687,7 +687,7 @@ private: bool mCaretUseTextColor; QColor mActiveLineColor; PSynEditUndoList mUndoList; - PSynEditUndoList mRedoList; + PSynEditRedoList mRedoList; QPoint mMouseDownPos; bool mHideSelection; int mMouseWheelAccumulator; diff --git a/RedPandaIDE/qsynedit/TextBuffer.cpp b/RedPandaIDE/qsynedit/TextBuffer.cpp index 4ffe95f4..4503feca 100644 --- a/RedPandaIDE/qsynedit/TextBuffer.cpp +++ b/RedPandaIDE/qsynedit/TextBuffer.cpp @@ -840,16 +840,17 @@ SynEditUndoList::SynEditUndoList():QObject() mInsideRedo = false; mBlockChangeNumber=0; - mBlockLockCount=0; + mBlockLock=0; mFullUndoImposible=false; mBlockCount=0; mLastPoppedItemChangeNumber=0; mInitialChangeNumber = 0; + mLastRestoredItemChangeNumber=0; } -void SynEditUndoList::addChange(SynChangeReason AReason, const BufferCoord &AStart, - const BufferCoord &AEnd, const QStringList& ChangeText, - SynSelectionMode SelMode) +void SynEditUndoList::addChange(SynChangeReason reason, const BufferCoord &startPos, + const BufferCoord &endPos, const QStringList& changeText, + SynSelectionMode selMode) { int changeNumber; if (inBlock()) { @@ -857,15 +858,41 @@ void SynEditUndoList::addChange(SynChangeReason AReason, const BufferCoord &ASta } else { changeNumber = getNextChangeNumber(); } + PSynEditUndoItem newItem = std::make_shared( + reason, + selMode,startPos,endPos,changeText, + changeNumber); +// qDebug()<<"add change"<(AReason, SelMode,AStart,AEnd,ChangeText, changeNumber); - mItems.append(newItem); + restoreChange(newItem); +} + +void SynEditUndoList::restoreChange(PSynEditUndoItem item) +{ + size_t changeNumber = item->changeNumber(); + mItems.append(item); ensureMaxEntries(); - if (AReason!=SynChangeReason::GroupBreak && !inBlock()) { + if (changeNumber>mNextChangeNumber) + mNextChangeNumber=changeNumber; + if (changeNumber!=mLastRestoredItemChangeNumber) { +// qDebug()<<"restore"< 0) { - mBlockLockCount--; - if (mBlockLockCount == 0) { +// qDebug()<<"end block"; + if (mBlockLock > 0) { + mBlockLock--; + if (mBlockLock == 0) { size_t iBlockID = mBlockChangeNumber; mBlockChangeNumber = 0; if (mItems.count() > 0 && peekItem()->changeNumber() == iBlockID) { mBlockCount++; +// qDebug()<<"end block"<0; + return mBlockLock>0; } unsigned int SynEditUndoList::getNextChangeNumber() @@ -946,10 +979,12 @@ PSynEditUndoItem SynEditUndoList::popItem() return PSynEditUndoItem(); else { PSynEditUndoItem item = mItems.last(); - if (mLastPoppedItemChangeNumber!=item->changeNumber()) { +// qDebug()<<"popped"<changeNumber()<changeText()<<(int)item->changeReason()<changeNumber() && item->changeReason()!=SynChangeReason::GroupBreak) { mBlockCount--; +// qDebug()<<"pop"<changeNumber(); } -int SynEditUndoList::blockChangeNumber() const -{ - return mBlockChangeNumber; -} - -void SynEditUndoList::setBlockChangeNumber(int blockChangeNumber) -{ - mBlockChangeNumber = blockChangeNumber; -} - bool SynEditUndoList::insideRedo() const { return mInsideRedo; @@ -1030,10 +1055,12 @@ void SynEditUndoList::ensureMaxEntries() mFullUndoImposible = true; while (mBlockCount > mMaxUndoActions && !mItems.isEmpty()) { //remove all undo item in block - size_t changeNumber = mItems.front()->changeNumber(); + PSynEditUndoItem item = mItems.front(); + size_t changeNumber = item->changeNumber(); while (mItems.count()>0 && mItems.front()->changeNumber() == changeNumber) mItems.removeFirst(); - mBlockCount--; + if (item->changeReason()!=SynChangeReason::GroupBreak) + mBlockCount--; } } } @@ -1079,3 +1106,69 @@ SynChangeReason SynEditUndoItem::changeReason() const { return mChangeReason; } + +SynEditRedoList::SynEditRedoList() +{ + +} + +void SynEditRedoList::addRedo(SynChangeReason AReason, const BufferCoord &AStart, const BufferCoord &AEnd, const QStringList &ChangeText, SynSelectionMode SelMode, size_t changeNumber) +{ + PSynEditUndoItem newItem = std::make_shared( + AReason, + SelMode,AStart,AEnd,ChangeText, + changeNumber); + mItems.append(newItem); +} + +void SynEditRedoList::addRedo(PSynEditUndoItem item) +{ + mItems.append(item); +} + +void SynEditRedoList::clear() +{ + mItems.clear(); +} + +SynChangeReason SynEditRedoList::lastChangeReason() +{ + if (mItems.count() == 0) + return SynChangeReason::Nothing; + else + return mItems.last()->changeReason(); +} + +bool SynEditRedoList::isEmpty() +{ + return mItems.isEmpty(); +} + +PSynEditUndoItem SynEditRedoList::peekItem() +{ + if (mItems.count() == 0) + return PSynEditUndoItem(); + else + return mItems.last(); +} + +PSynEditUndoItem SynEditRedoList::popItem() +{ + if (mItems.count() == 0) + return PSynEditUndoItem(); + else { + PSynEditUndoItem item = mItems.last(); + mItems.removeLast(); + return item; + } +} + +bool SynEditRedoList::canRedo() +{ + return mItems.count()>0; +} + +int SynEditRedoList::itemCount() +{ + return mItems.count(); +} diff --git a/RedPandaIDE/qsynedit/TextBuffer.h b/RedPandaIDE/qsynedit/TextBuffer.h index 06a98c5f..4eee01f6 100644 --- a/RedPandaIDE/qsynedit/TextBuffer.h +++ b/RedPandaIDE/qsynedit/TextBuffer.h @@ -196,6 +196,7 @@ public: QStringList changeText() const; size_t changeNumber() const; }; + using PSynEditUndoItem = std::shared_ptr; class SynEditUndoList : public QObject { @@ -206,6 +207,11 @@ public: void addChange(SynChangeReason AReason, const BufferCoord& AStart, const BufferCoord& AEnd, const QStringList& ChangeText, SynSelectionMode SelMode); + void restoreChange(SynChangeReason AReason, const BufferCoord& AStart, const BufferCoord& AEnd, + const QStringList& ChangeText, SynSelectionMode SelMode, size_t changeNumber); + + void restoreChange(PSynEditUndoItem item); + void addGroupBreak(); void beginBlock(); void endBlock(); @@ -224,9 +230,6 @@ public: bool initialState(); void setInitialState(); - int blockChangeNumber() const; - void setBlockChangeNumber(int blockChangeNumber); - bool insideRedo() const; void setInsideRedo(bool insideRedo); @@ -240,9 +243,10 @@ protected: unsigned int getNextChangeNumber(); protected: size_t mBlockChangeNumber; - int mBlockLockCount; + int mBlockLock; int mBlockCount; // count of action blocks; size_t mLastPoppedItemChangeNumber; + size_t mLastRestoredItemChangeNumber; bool mFullUndoImposible; QVector mItems; int mMaxUndoActions; @@ -251,6 +255,30 @@ protected: bool mInsideRedo; }; +class SynEditRedoList : public QObject { + Q_OBJECT +public: + explicit SynEditRedoList(); + + void addRedo(SynChangeReason AReason, const BufferCoord& AStart, const BufferCoord& AEnd, + const QStringList& ChangeText, SynSelectionMode SelMode, size_t changeNumber); + void addRedo(PSynEditUndoItem item); + + void clear(); + SynChangeReason lastChangeReason(); + bool isEmpty(); + PSynEditUndoItem peekItem(); + PSynEditUndoItem popItem(); + + bool canRedo(); + int itemCount(); + +protected: + QVector mItems; +}; + + using PSynEditUndoList = std::shared_ptr; +using PSynEditRedoList = std::shared_ptr; #endif // SYNEDITSTRINGLIST_H