- redesign redo system

- fix: correctly restore editor's modified status when undo/redo
This commit is contained in:
Roy Qu 2022-07-06 14:11:32 +08:00
parent 10631f54d7
commit 8d59bf9abb
5 changed files with 238 additions and 137 deletions

View File

@ -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

View File

@ -80,8 +80,8 @@ SynEdit::SynEdit(QWidget *parent) : QAbstractScrollArea(parent),
mUndoList = std::make_shared<SynEditUndoList>();
mUndoList->connect(mUndoList.get(), &SynEditUndoList::addedUndo, this, &SynEdit::onUndoAdded);
mRedoList = std::make_shared<SynEditUndoList>();
mRedoList->connect(mRedoList.get(), &SynEditUndoList::addedUndo, this, &SynEdit::onRedoAdded);
mRedoList = std::make_shared<SynEditRedoList>();
// 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()<<textToLines(text);
// qDebug()<<textToLines(text);
setSelTextPrimitive(splitStrings(text));
mUndoList->endBlock();
}
@ -3428,7 +3428,7 @@ void SynEdit::updateModifiedStatus()
bool oldModified = mModified;
mModified = !mUndoList->initialState();
setModified(mModified);
qDebug()<<mModified<<oldModified;
// qDebug()<<mModified<<oldModified;
if (oldModified!=mModified)
emit statusChanged(SynStatusChange::scModifyChanged);
}
@ -4294,31 +4294,15 @@ void SynEdit::doUndo()
return;
//Remove Group Break;
if (mUndoList->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())

View File

@ -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;

View File

@ -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<SynEditUndoItem>(
reason,
selMode,startPos,endPos,changeText,
changeNumber);
// qDebug()<<"add change"<<changeNumber<<(int)reason;
mItems.append(newItem);
ensureMaxEntries();
if (reason!=SynChangeReason::GroupBreak && !inBlock()) {
mBlockCount++;
// qDebug()<<"add"<<mBlockCount;
emit addedUndo();
}
}
void SynEditUndoList::restoreChange(SynChangeReason AReason, const BufferCoord &AStart, const BufferCoord &AEnd, const QStringList &ChangeText, SynSelectionMode SelMode, size_t changeNumber)
{
PSynEditUndoItem newItem = std::make_shared<SynEditUndoItem>(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"<<mBlockCount;
mBlockCount++;
emit addedUndo();
}
mLastRestoredItemChangeNumber=changeNumber;
}
void SynEditUndoList::addGroupBreak()
@ -880,8 +907,11 @@ void SynEditUndoList::addGroupBreak()
void SynEditUndoList::beginBlock()
{
mBlockLockCount++;
mBlockChangeNumber = getNextChangeNumber();
// qDebug()<<"begin block";
if (mBlockLock==0)
mBlockChangeNumber = getNextChangeNumber();
mBlockLock++;
}
void SynEditUndoList::clear()
@ -890,19 +920,22 @@ void SynEditUndoList::clear()
mFullUndoImposible = false;
mInitialChangeNumber=0;
mLastPoppedItemChangeNumber=0;
mLastRestoredItemChangeNumber=0;
mBlockCount=0;
mBlockLockCount=0;
mBlockLock=0;
}
void SynEditUndoList::endBlock()
{
if (mBlockLockCount > 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"<<mBlockCount;
emit addedUndo();
}
}
@ -911,7 +944,7 @@ void SynEditUndoList::endBlock()
bool SynEditUndoList::inBlock()
{
return mBlockLockCount>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"<<item->changeNumber()<<item->changeText()<<(int)item->changeReason()<<mLastPoppedItemChangeNumber;
if (mLastPoppedItemChangeNumber!=item->changeNumber() && item->changeReason()!=SynChangeReason::GroupBreak) {
mBlockCount--;
// qDebug()<<"pop"<<mBlockCount;
if (mBlockCount<0) {
qDebug()<<"block count calculation error";
// qDebug()<<"block count calculation error";
mBlockCount=0;
}
}
@ -999,16 +1034,6 @@ void SynEditUndoList::setInitialState()
mInitialChangeNumber = peekItem()->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<SynEditUndoItem>(
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();
}

View File

@ -196,6 +196,7 @@ public:
QStringList changeText() const;
size_t changeNumber() const;
};
using PSynEditUndoItem = std::shared_ptr<SynEditUndoItem>;
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<PSynEditUndoItem> 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<PSynEditUndoItem> mItems;
};
using PSynEditUndoList = std::shared_ptr<SynEditUndoList>;
using PSynEditRedoList = std::shared_ptr<SynEditRedoList>;
#endif // SYNEDITSTRINGLIST_H