FakeVim: Use QTextDocument::revision() for undo/redo

Using QTextDocument::availableUndoSteps() to get revision of document is
very bad because it undo/redo stack starts to break when maximal number
of undo is reached.

On the other hand QTextDocument::revision() number is always increased,
even on QTextDocument::redo(), so external undo/redo can still break
undo/redo commands in FakeVim.

Change-Id: If1698df8f43a878295eebddd59aebe304cdf3081
Reviewed-by: hjk <hjk121@nokiamail.com>
This commit is contained in:
hluk
2013-04-08 21:25:29 +02:00
committed by hjk
parent 42c6f7cffd
commit b9cc16e405

View File

@@ -288,14 +288,16 @@ typedef QHashIterator<QChar, Mark> MarksIterator;
struct State struct State
{ {
State() : revision(-1), position(), marks(), lastVisualMode(NoVisualMode), State() : revisions(0), position(), marks(), lastVisualMode(NoVisualMode),
lastVisualModeInverted(false) {} lastVisualModeInverted(false) {}
State(int revision, const CursorPosition &position, const Marks &marks, State(const CursorPosition &position, const Marks &marks,
VisualMode lastVisualMode, bool lastVisualModeInverted) : revision(revision), VisualMode lastVisualMode, bool lastVisualModeInverted) : revisions(0),
position(position), marks(marks), lastVisualMode(lastVisualMode), position(position), marks(marks), lastVisualMode(lastVisualMode),
lastVisualModeInverted(lastVisualModeInverted) {} lastVisualModeInverted(lastVisualModeInverted) {}
int revision; bool isValid() const { return position.isValid(); }
int revisions;
CursorPosition position; CursorPosition position;
Marks marks; Marks marks;
VisualMode lastVisualMode; VisualMode lastVisualMode;
@@ -1661,13 +1663,16 @@ public:
{ return document()->characterAt(position()); } { return document()->characterAt(position()); }
int m_editBlockLevel; // current level of edit blocks int m_editBlockLevel; // current level of edit blocks
int m_largeEditBlockRevision; // current level of large edit block
void joinPreviousEditBlock(); void joinPreviousEditBlock();
void beginEditBlock(bool largeEditBlock = false); void beginEditBlock(bool largeEditBlock = false);
void beginLargeEditBlock() { beginEditBlock(true); } void beginLargeEditBlock() { beginEditBlock(true); }
void endEditBlock(); void endEditBlock();
void breakEditBlock() { m_breakEditBlock = true; } void breakEditBlock() { m_breakEditBlock = true; }
Q_SLOT void onContentsChanged(); Q_SLOT void onContentsChanged();
Q_SLOT void onUndoCommandAdded();
bool isInsertMode() const { return m_mode == InsertMode || m_mode == ReplaceMode; }
bool isVisualMode() const { return m_visualMode != NoVisualMode; } bool isVisualMode() const { return m_visualMode != NoVisualMode; }
bool isNoVisualMode() const { return m_visualMode == NoVisualMode; } bool isNoVisualMode() const { return m_visualMode == NoVisualMode; }
@@ -1787,8 +1792,7 @@ public:
QString guessInsertCommand(int pos1, int pos2, int len1, int len2); QString guessInsertCommand(int pos1, int pos2, int len1, int len2);
// undo handling // undo handling
int revision() const { return document()->availableUndoSteps(); } int revision() const { return document()->revision(); }
int lastUndoRevision() const { return m_undo.empty() ? -1 : m_undo.top().revision; }
void undoRedo(bool undo); void undoRedo(bool undo);
void undo(); void undo();
void redo(); void redo();
@@ -1796,6 +1800,7 @@ public:
// revision -> state // revision -> state
QStack<State> m_undo; QStack<State> m_undo;
QStack<State> m_redo; QStack<State> m_redo;
State m_undoState;
// extra data for '.' // extra data for '.'
void replay(const QString &text); void replay(const QString &text);
@@ -1956,8 +1961,10 @@ FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
q = parent; q = parent;
m_textedit = qobject_cast<QTextEdit *>(widget); m_textedit = qobject_cast<QTextEdit *>(widget);
m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget); m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget);
if (editor()) if (editor()) {
connect(EDITOR(document()), SIGNAL(contentsChanged()), SLOT(onContentsChanged())); connect(EDITOR(document()), SIGNAL(contentsChanged()), SLOT(onContentsChanged()));
connect(EDITOR(document()), SIGNAL(undoCommandAdded()), SLOT(onUndoCommandAdded()));
}
//new Highlighter(document(), &pythonRules); //new Highlighter(document(), &pythonRules);
init(); init();
} }
@@ -1996,7 +2003,6 @@ void FakeVimHandler::Private::init()
m_searchStartPosition = 0; m_searchStartPosition = 0;
m_searchFromScreenLine = 0; m_searchFromScreenLine = 0;
m_editBlockLevel = 0; m_editBlockLevel = 0;
m_largeEditBlockRevision = -1;
setupCharClass(); setupCharClass();
} }
@@ -2024,7 +2030,7 @@ void FakeVimHandler::Private::enterFakeVim()
if (m_oldPosition != -1 && lineForPosition(m_oldPosition) != lineForPosition(tc.position())) if (m_oldPosition != -1 && lineForPosition(m_oldPosition) != lineForPosition(tc.position()))
recordJump(m_oldPosition); recordJump(m_oldPosition);
setTargetColumn(); setTargetColumn();
if (atEndOfLine() && !isVisualMode() && m_mode != InsertMode && m_mode != ReplaceMode) if (atEndOfLine() && !isVisualMode() && !isInsertMode())
moveLeft(); moveLeft();
} }
@@ -2189,8 +2195,11 @@ void FakeVimHandler::Private::setupWidget()
recordJump(); recordJump();
setTargetColumn(); setTargetColumn();
if (atEndOfLine() && !isVisualMode() && m_mode != InsertMode && m_mode != ReplaceMode) if (atEndOfLine() && !isVisualMode() && !isInsertMode())
moveLeft(); moveLeft();
m_oldExternalAnchor = anchor();
m_oldExternalPosition = position();
} }
void FakeVimHandler::Private::exportSelection() void FakeVimHandler::Private::exportSelection()
@@ -2651,25 +2660,14 @@ bool FakeVimHandler::Private::isFirstNonBlankOnLine(int pos)
void FakeVimHandler::Private::pushUndoState(bool overwrite) void FakeVimHandler::Private::pushUndoState(bool overwrite)
{ {
if (m_editBlockLevel != 0 && m_largeEditBlockRevision == -1) if (m_editBlockLevel != 0 && m_undoState.isValid())
return; // No need to save undo state for inner edit blocks. return; // No need to save undo state for inner edit blocks.
// Use revision of large edit block if started. if (m_undoState.isValid() && !overwrite)
int rev; return;
if (m_largeEditBlockRevision != -1) {
rev = m_largeEditBlockRevision;
m_largeEditBlockRevision = -1;
} else {
rev = revision();
if (!overwrite && lastUndoRevision() == rev)
return;
}
while (lastUndoRevision() == rev)
m_undo.pop();
int pos = position(); int pos = position();
if (m_mode != InsertMode && m_mode != ReplaceMode) { if (!isInsertMode()) {
if (isVisualMode() || m_submode == DeleteSubMode) { if (isVisualMode() || m_submode == DeleteSubMode) {
pos = qMin(pos, anchor()); pos = qMin(pos, anchor());
if (isVisualLineMode()) if (isVisualLineMode())
@@ -2692,8 +2690,8 @@ void FakeVimHandler::Private::pushUndoState(bool overwrite)
setMark(QLatin1Char('<'), mark(QLatin1Char('<')).position); setMark(QLatin1Char('<'), mark(QLatin1Char('<')).position);
setMark(QLatin1Char('>'), mark(QLatin1Char('>')).position); setMark(QLatin1Char('>'), mark(QLatin1Char('>')).position);
} }
m_undo.push( m_undoState = State(m_lastChangePosition, m_marks, m_lastVisualMode,
State(rev, m_lastChangePosition, m_marks, m_lastVisualMode, m_lastVisualModeInverted)); m_lastVisualModeInverted);
} }
void FakeVimHandler::Private::moveDown(int n) void FakeVimHandler::Private::moveDown(int n)
@@ -2872,9 +2870,6 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommandMovement)
|| m_submode == InvertCaseSubMode || m_submode == InvertCaseSubMode
|| m_submode == DownCaseSubMode || m_submode == DownCaseSubMode
|| m_submode == UpCaseSubMode) { || m_submode == UpCaseSubMode) {
if (m_submode != YankSubMode)
beginEditBlock();
fixSelection(); fixSelection();
if (m_submode != InvertCaseSubMode if (m_submode != InvertCaseSubMode
@@ -2890,8 +2885,8 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommandMovement)
QString dotCommand; QString dotCommand;
if (m_submode == ChangeSubMode) { if (m_submode == ChangeSubMode) {
if (m_rangemode != RangeLineModeExclusive) pushUndoState();
pushUndoState(); beginEditBlock();
removeText(currentRange()); removeText(currentRange());
dotCommand = _("c"); dotCommand = _("c");
if (m_movetype == MoveLineWise) if (m_movetype == MoveLineWise)
@@ -2902,6 +2897,7 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommandMovement)
g.returnToMode = InsertMode; g.returnToMode = InsertMode;
} else if (m_submode == DeleteSubMode) { } else if (m_submode == DeleteSubMode) {
pushUndoState(); pushUndoState();
beginEditBlock();
const int pos = position(); const int pos = position();
// Always delete something (e.g. 'dw' on an empty line deletes the line). // Always delete something (e.g. 'dw' on an empty line deletes the line).
if (pos == anchor() && m_movetype == MoveInclusive) if (pos == anchor() && m_movetype == MoveInclusive)
@@ -2935,6 +2931,7 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommandMovement)
} else if (m_submode == InvertCaseSubMode } else if (m_submode == InvertCaseSubMode
|| m_submode == UpCaseSubMode || m_submode == UpCaseSubMode
|| m_submode == DownCaseSubMode) { || m_submode == DownCaseSubMode) {
beginEditBlock();
if (m_submode == InvertCaseSubMode) { if (m_submode == InvertCaseSubMode) {
invertCase(currentRange()); invertCase(currentRange());
dotCommand = QString::fromLatin1("g~"); dotCommand = QString::fromLatin1("g~");
@@ -3681,11 +3678,9 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input)
moveRight(); moveRight();
breakEditBlock(); breakEditBlock();
enterInsertMode(); enterInsertMode();
pushUndoState();
} else if (input.is('A')) { } else if (input.is('A')) {
breakEditBlock(); breakEditBlock();
moveBehindEndOfLine(); moveBehindEndOfLine();
pushUndoState();
setAnchor(); setAnchor();
enterInsertMode(); enterInsertMode();
setTargetColumn(); setTargetColumn();
@@ -5007,8 +5002,7 @@ bool FakeVimHandler::Private::handleExSubstituteCommand(const ExCommand &cmd)
} }
if (lastBlock.isValid()) { if (lastBlock.isValid()) {
State &state = m_undo.top(); m_undoState.position = CursorPosition(firstBlock.blockNumber(), 0);
state.position = CursorPosition(firstBlock.blockNumber(), 0);
leaveVisualMode(); leaveVisualMode();
setPosition(lastBlock.position()); setPosition(lastBlock.position());
@@ -6362,7 +6356,7 @@ void FakeVimHandler::Private::setCursorPosition(QTextCursor *tc, const CursorPos
int FakeVimHandler::Private::lastPositionInDocument(bool ignoreMode) const int FakeVimHandler::Private::lastPositionInDocument(bool ignoreMode) const
{ {
return document()->characterCount() return document()->characterCount()
- (ignoreMode || isVisualMode() || m_mode == InsertMode || m_mode == ReplaceMode ? 1 : 2); - (ignoreMode || isVisualMode() || isInsertMode() ? 1 : 2);
} }
QString FakeVimHandler::Private::selectText(const Range &range) const QString FakeVimHandler::Private::selectText(const Range &range) const
@@ -6974,7 +6968,7 @@ int FakeVimHandler::Private::lastPositionInLine(int line, bool onlyVisibleLines)
} }
const int position = block.position() + block.length() - 1; const int position = block.position() + block.length() - 1;
if (block.length() > 1 && !isVisualMode() && m_mode != InsertMode && m_mode != ReplaceMode) if (block.length() > 1 && !isVisualMode() && !isInsertMode())
return position - 1; return position - 1;
return position; return position;
} }
@@ -7033,7 +7027,7 @@ QWidget *FakeVimHandler::Private::editor() const
void FakeVimHandler::Private::joinPreviousEditBlock() void FakeVimHandler::Private::joinPreviousEditBlock()
{ {
UNDO_DEBUG("JOIN"); UNDO_DEBUG("JOIN");
if (m_breakEditBlock) { if (m_breakEditBlock && m_editBlockLevel == 0) {
beginEditBlock(); beginEditBlock();
QTextCursor tc(cursor()); QTextCursor tc(cursor());
tc.setPosition(tc.position()); tc.setPosition(tc.position());
@@ -7042,9 +7036,9 @@ void FakeVimHandler::Private::joinPreviousEditBlock()
tc.deletePreviousChar(); tc.deletePreviousChar();
tc.endEditBlock(); tc.endEditBlock();
} else { } else {
if (m_editBlockLevel == 0) if (m_editBlockLevel == 0 && !m_undo.empty())
m_cursor = cursor(); m_undoState = m_undo.pop();
++m_editBlockLevel; beginEditBlock();
} }
} }
@@ -7054,12 +7048,8 @@ void FakeVimHandler::Private::beginEditBlock(bool largeEditBlock)
if (m_editBlockLevel == 0) if (m_editBlockLevel == 0)
m_cursor = cursor(); m_cursor = cursor();
if (m_editBlockLevel == 0 || m_largeEditBlockRevision != -1) { if (!largeEditBlock && !m_undoState.isValid())
if (largeEditBlock) pushUndoState(false);
m_largeEditBlockRevision = revision();
else
pushUndoState(false);
}
++m_editBlockLevel; ++m_editBlockLevel;
m_breakEditBlock = false; m_breakEditBlock = false;
} }
@@ -7070,30 +7060,32 @@ void FakeVimHandler::Private::endEditBlock()
QTC_ASSERT(m_editBlockLevel > 0, QTC_ASSERT(m_editBlockLevel > 0,
qDebug() << "beginEditBlock() not called before endEditBlock()!"; return); qDebug() << "beginEditBlock() not called before endEditBlock()!"; return);
--m_editBlockLevel; --m_editBlockLevel;
if (m_editBlockLevel == 0) if (m_editBlockLevel == 0) {
setCursor(m_cursor); setCursor(m_cursor);
if (m_largeEditBlockRevision != -1) if (m_undoState.isValid()) {
m_largeEditBlockRevision = -1; if (m_undoState.revisions > 0) {
m_undo.push(m_undoState);
m_undoState = State();
}
}
}
} }
void FakeVimHandler::Private::onContentsChanged() void FakeVimHandler::Private::onContentsChanged()
{ {
if (!document()->isUndoAvailable()) if (!document()->isUndoAvailable())
m_undo.clear(); m_undo.clear();
else if (m_editBlockLevel == 0 && !m_undo.isEmpty() && !isInsertMode())
m_undo.push(State()); // Save undo state for external change.
}
const int rev = revision(); void FakeVimHandler::Private::onUndoCommandAdded()
if (lastUndoRevision() > rev) { {
m_redo.clear(); m_redo.clear();
while (lastUndoRevision() > rev) if (m_editBlockLevel == 0 && !m_undo.isEmpty() && isInsertMode())
m_undo.pop(); ++m_undo.top().revisions;
} else { else
while (!m_redo.empty() && m_redo.top().revision < rev) ++m_undoState.revisions;
m_redo.pop();
}
// External change.
if (m_editBlockLevel == 0 && document()->isUndoAvailable())
pushUndoState();
} }
char FakeVimHandler::Private::currentModeCode() const char FakeVimHandler::Private::currentModeCode() const
@@ -7118,58 +7110,49 @@ void FakeVimHandler::Private::undoRedo(bool undo)
QStack<State> &stack = undo ? m_undo : m_redo; QStack<State> &stack = undo ? m_undo : m_redo;
QStack<State> &stack2 = undo ? m_redo : m_undo; QStack<State> &stack2 = undo ? m_redo : m_undo;
bool validState = !stack.empty(); State state = !stack.empty() ? stack.pop() : State();
State state = validState ? stack.pop() : State();
++m_editBlockLevel;
const int firstLine = firstVisibleLine(); const int firstLine = firstVisibleLine();
CursorPosition lastPos(cursor()); CursorPosition lastPos(cursor());
const int current = revision(); const int current = revision();
const int prevRev = validState ? state.revision : current;
int rev = current; ++m_editBlockLevel;
int last;
if (undo) { // Do undo/redo [count] times to reach previous revision.
do { int count = m_undoState.isValid() ? m_undoState.revisions
last = rev; : state.isValid() ? state.revisions : 1;
while (count-- > 0) {
if (undo)
EDITOR(undo()); EDITOR(undo());
rev = revision(); else
} while (prevRev < rev && last != rev);
} else {
do {
last = rev;
EDITOR(redo()); EDITOR(redo());
rev = revision();
} while (prevRev > rev && last != rev);
} }
scrollToLine(firstLine);
--m_editBlockLevel; --m_editBlockLevel;
if (current == rev) { scrollToLine(firstLine);
if (current == revision()) {
const QString msg = undo ? FakeVimHandler::tr("Already at oldest change.") const QString msg = undo ? FakeVimHandler::tr("Already at oldest change.")
: FakeVimHandler::tr("Already at newest change."); : FakeVimHandler::tr("Already at newest change.");
showMessage(MessageInfo, msg); showMessage(MessageInfo, msg);
stack.push(state);
return; return;
} }
clearMessage(); clearMessage();
if (validState) { if (state.isValid()) {
if (state.revision == rev) { m_lastChangePosition = state.position;
m_lastChangePosition = state.position; Marks marks = m_marks;
Marks marks = m_marks; marks.swap(state.marks);
marks.swap(state.marks); updateMarks(marks);
updateMarks(marks); m_lastVisualMode = state.lastVisualMode;
m_lastVisualMode = state.lastVisualMode; m_lastVisualModeInverted = state.lastVisualModeInverted;
m_lastVisualModeInverted = state.lastVisualModeInverted; setMark(QLatin1Char('\''), lastPos);
setMark(QLatin1Char('\''), lastPos); setCursorPosition(m_lastChangePosition);
setCursorPosition(m_lastChangePosition); setAnchor();
setAnchor(); stack2.push(state);
state.revision = current;
stack2.push(state);
} else {
stack.push(state);
}
} }
setTargetColumn(); setTargetColumn();