Commit 12deff2f authored by hluk's avatar hluk Committed by hjk
Browse files

FakeVim: Reworked undo/redo



Don't use QTextCursor::beginEditBlock() and others since it can change
the behavior of underlying editor widget. Use these methods only for
interrupting current edit block.

Change-Id: I848d6287faeeaad9c04a76758bd0b2cc673ad2ef
Reviewed-by: default avatarhjk <hjk121@nokiamail.com>
parent bea40d99
......@@ -1957,7 +1957,8 @@ void FakeVimPlugin::test_vim_substitute()
data.setText("abc" N "def" N "ghi" N "jkl");
KEYS("jVj:s/^/*<CR>", "abc" N "*def" N X "*ghi" N "jkl");
COMMAND("'<,'>s/^/*", "abc" N "**def" N X "**ghi" N "jkl");
KEYS("ugv:s/^/+<CR>", "abc" N "+*def" N X "+*ghi" N "jkl");
KEYS("u", "abc" N X "*def" N "*ghi" N "jkl");
KEYS("gv:s/^/+<CR>", "abc" N "+*def" N X "+*ghi" N "jkl");
}
void FakeVimPlugin::test_vim_ex_yank()
......
......@@ -1661,11 +1661,13 @@ public:
{ return document()->characterAt(position()); }
int m_editBlockLevel; // current level of edit blocks
int m_largeEditBlockRevision; // current level of large edit block
void joinPreviousEditBlock();
void beginEditBlock(bool rememberPosition = true);
void beginLargeEditBlock() { beginEditBlock(false); }
void beginEditBlock(bool largeEditBlock = false);
void beginLargeEditBlock() { beginEditBlock(true); }
void endEditBlock();
void breakEditBlock() { m_breakEditBlock = true; }
Q_SLOT void onContentsChanged();
bool isVisualMode() const { return m_visualMode != NoVisualMode; }
bool isNoVisualMode() const { return m_visualMode == NoVisualMode; }
......@@ -1785,10 +1787,11 @@ public:
// undo handling
int revision() const { return document()->availableUndoSteps(); }
int lastUndoRevision() const { return m_undo.empty() ? -1 : m_undo.top().revision; }
void undoRedo(bool undo);
void undo();
void redo();
void setUndoPosition(bool overwrite = true);
void pushUndoState(bool overwrite = true);
// revision -> state
QStack<State> m_undo;
QStack<State> m_redo;
......@@ -1952,6 +1955,8 @@ FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
q = parent;
m_textedit = qobject_cast<QTextEdit *>(widget);
m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget);
if (editor())
connect(EDITOR(document()), SIGNAL(contentsChanged()), SLOT(onContentsChanged()));
//new Highlighter(document(), &pythonRules);
init();
}
......@@ -1990,6 +1995,7 @@ void FakeVimHandler::Private::init()
m_searchStartPosition = 0;
m_searchFromScreenLine = 0;
m_editBlockLevel = 0;
m_largeEditBlockRevision = -1;
setupCharClass();
}
......@@ -2642,11 +2648,24 @@ bool FakeVimHandler::Private::isFirstNonBlankOnLine(int pos)
return true;
}
void FakeVimHandler::Private::setUndoPosition(bool overwrite)
void FakeVimHandler::Private::pushUndoState(bool overwrite)
{
const int rev = revision();
if (!overwrite && !m_undo.empty() && m_undo.top().revision >= rev)
return;
if (m_editBlockLevel != 0 && m_largeEditBlockRevision == -1)
return; // No need to save undo state for inner edit blocks.
// Use revision of large edit block if started.
int rev;
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();
if (m_mode != InsertMode && m_mode != ReplaceMode) {
......@@ -2667,8 +2686,6 @@ void FakeVimHandler::Private::setUndoPosition(bool overwrite)
}
m_redo.clear();
while (!m_undo.empty() && m_undo.top().revision >= rev)
m_undo.pop();
m_lastChangePosition = CursorPosition(document(), pos);
if (isVisualMode()) {
setMark(QLatin1Char('<'), mark(QLatin1Char('<')).position);
......@@ -2873,7 +2890,7 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommandMovement)
QString dotCommand;
if (m_submode == ChangeSubMode) {
if (m_rangemode != RangeLineModeExclusive)
setUndoPosition();
pushUndoState();
removeText(currentRange());
dotCommand = _("c");
if (m_movetype == MoveLineWise)
......@@ -2883,7 +2900,7 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommandMovement)
m_lastInsertion.clear();
g.returnToMode = InsertMode;
} else if (m_submode == DeleteSubMode) {
setUndoPosition();
pushUndoState();
const int pos = position();
// Always delete something (e.g. 'dw' on an empty line deletes the line).
if (pos == anchor() && m_movetype == MoveInclusive)
......@@ -2934,7 +2951,7 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommandMovement)
|| m_submode == ShiftRightSubMode
|| m_submode == ShiftLeftSubMode) {
recordJump();
setUndoPosition();
pushUndoState();
if (m_submode == IndentSubMode) {
indentSelectedText();
dotCommand = _("=");
......@@ -3663,11 +3680,11 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input)
moveRight();
breakEditBlock();
enterInsertMode();
setUndoPosition();
pushUndoState();
} else if (input.is('A')) {
breakEditBlock();
moveBehindEndOfLine();
setUndoPosition();
pushUndoState();
setAnchor();
enterInsertMode();
setTargetColumn();
......@@ -3702,7 +3719,7 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input)
leaveVisualMode();
} else if ((input.is('d') || input.is('x') || input.isKey(Key_Delete))
&& isVisualMode()) {
setUndoPosition();
pushUndoState();
setDotCommand(visualDotCommand() + QLatin1Char('x'));
if (isVisualCharMode()) {
leaveVisualMode();
......@@ -3720,7 +3737,7 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input)
setPosition(qMin(position(), anchor()));
}
} else if (input.is('D') && isNoVisualMode()) {
setUndoPosition();
pushUndoState();
if (atEndOfLine())
moveLeft();
m_submode = DeleteSubMode;
......@@ -3770,14 +3787,14 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input)
moveToFirstNonBlankOnLine();
//m_tc.clearSelection();
}
setUndoPosition();
pushUndoState();
breakEditBlock();
enterInsertMode();
setTargetColumn();
} else if (input.isControl('i')) {
jump(count());
} else if (input.is('J')) {
setUndoPosition();
pushUndoState();
moveBehindEndOfLine();
beginEditBlock();
if (m_submode == NoSubMode)
......@@ -3798,7 +3815,7 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input)
} else if (input.is('o') || input.is('O')) {
bool insertAfter = input.is('o');
setDotCommand(_(insertAfter ? "%1o" : "%1O"), count());
setUndoPosition();
pushUndoState();
// Prepend line only if on the first line and command is 'O'.
bool appendLine = true;
......@@ -3851,7 +3868,7 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input)
} else if (input.is('r')) {
m_submode = ReplaceSubMode;
} else if (!isVisualMode() && input.is('R')) {
setUndoPosition();
pushUndoState();
breakEditBlock();
enterReplaceMode();
} else if (input.isControl('r')) {
......@@ -3861,13 +3878,13 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input)
} else if (input.is('s') && isVisualBlockMode()) {
m_opcount.clear();
m_mvcount.clear();
setUndoPosition();
pushUndoState();
beginEditBlock();
initVisualBlockInsertMode(QLatin1Char('s'));
endEditBlock();
enterInsertMode();
} else if (input.is('s')) {
setUndoPosition();
pushUndoState();
leaveVisualMode();
if (atEndOfLine())
moveLeft();
......@@ -3879,7 +3896,7 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input)
finishMovement();
} else if (input.is('S')) {
m_movetype = MoveLineWise;
setUndoPosition();
pushUndoState();
if (!isVisualMode()) {
const int line = cursorLine() + 1;
const int anc = firstPositionInLine(line);
......@@ -3988,7 +4005,7 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input)
m_submode = UpCaseSubMode;
finishMovement();
} else if (m_gflag || (input.is('~') && hasConfig(ConfigTildeOp))) {
setUndoPosition();
pushUndoState();
if (atEndOfLine())
moveLeft();
setAnchor();
......@@ -4042,7 +4059,7 @@ bool FakeVimHandler::Private::handleChangeDeleteSubModes(const Input &input)
if ((m_submode == ChangeSubMode && input.is('c'))
|| (m_submode == DeleteSubMode && input.is('d'))) {
m_movetype = MoveLineWise;
setUndoPosition();
pushUndoState();
const int line = cursorLine() + 1;
const int anc = firstPositionInLine(line);
const int pos = lastPositionInLine(line + count() - 1);
......@@ -4067,7 +4084,7 @@ bool FakeVimHandler::Private::handleReplaceSubMode(const Input &input)
setDotCommand(visualDotCommand() + QLatin1Char('r') + input.asChar());
if (isVisualMode()) {
setUndoPosition();
pushUndoState();
if (isVisualLineMode())
m_rangemode = RangeLineMode;
else if (isVisualBlockMode())
......@@ -4082,7 +4099,7 @@ bool FakeVimHandler::Private::handleReplaceSubMode(const Input &input)
&FakeVimHandler::Private::replaceByCharTransform;
transformText(range, tr, input.asChar());
} else if (count() <= rightDist()) {
setUndoPosition();
pushUndoState();
setAnchor();
moveRight(count());
Range range = currentRange();
......@@ -4133,7 +4150,7 @@ bool FakeVimHandler::Private::handleShiftSubMode(const Input &input)
|| (m_submode == ShiftRightSubMode && input.is('>'))
|| (m_submode == IndentSubMode && input.is('='))) {
m_movetype = MoveLineWise;
setUndoPosition();
pushUndoState();
moveDown(count() - 1);
setDotCommand(QString::fromLatin1("%2%1%1").arg(input.asChar()), count());
finishMovement();
......@@ -4156,7 +4173,7 @@ bool FakeVimHandler::Private::handleChangeCaseSubMode(const Input &input)
moveToFirstNonBlankOnLine();
}
setTargetColumn();
setUndoPosition();
pushUndoState();
setAnchor();
setPosition(lastPositionInLine(cursorLine() + count()) + 1);
finishMovement(QString::fromLatin1("%1%2").arg(count()).arg(input.raw()));
......@@ -5216,7 +5233,7 @@ bool FakeVimHandler::Private::handleExYankDeleteCommand(const ExCommand &cmd)
if (remove) {
leaveVisualMode();
setPosition(range.beginPos);
setUndoPosition();
pushUndoState();
setCurrentRange(range);
removeText(currentRange());
}
......@@ -5265,7 +5282,7 @@ bool FakeVimHandler::Private::handleExMoveCommand(const ExCommand &cmd)
recordJump();
setPosition(cmd.range.beginPos);
setUndoPosition();
pushUndoState();
setCurrentRange(cmd.range);
QString text = selectText(cmd.range);
......@@ -5326,7 +5343,7 @@ bool FakeVimHandler::Private::handleExJoinCommand(const ExCommand &cmd)
}
moveToStartOfLine();
setUndoPosition();
pushUndoState();
joinLines(count, cmd.hasBang);
moveToFirstNonBlankOnLine();
......@@ -6706,9 +6723,11 @@ void FakeVimHandler::Private::insertNewLine()
bool FakeVimHandler::Private::handleInsertInEditor(const Input &input, QString *insert)
{
if (!hasConfig(ConfigPassKeys) || m_editBlockLevel > 0)
if (m_editBlockLevel > 0 || !hasConfig(ConfigPassKeys))
return false;
joinPreviousEditBlock();
const int pos1 = position();
const int len1 = lastPositionInDocument();
......@@ -6722,6 +6741,8 @@ bool FakeVimHandler::Private::handleInsertInEditor(const Input &input, QString *
*insert = guessInsertCommand(pos1, pos2, len1, len2);
endEditBlock();
return true;
}
......@@ -6730,7 +6751,6 @@ bool FakeVimHandler::Private::passEventToEditor(QEvent &event)
removeEventFilter();
QTextCursor tc = m_cursor;
tc.setPosition(tc.position());
EDITOR(setTextCursor(tc));
bool accepted = QApplication::sendEvent(editor(), &event);
......@@ -6985,23 +7005,31 @@ void FakeVimHandler::Private::joinPreviousEditBlock()
UNDO_DEBUG("JOIN");
if (m_breakEditBlock) {
beginEditBlock();
QTextCursor tc(document());
tc.beginEditBlock();
tc.insertText(_("X"));
tc.deletePreviousChar();
tc.endEditBlock();
} else {
if (m_editBlockLevel == 0)
m_cursor = cursor();
++m_editBlockLevel;
cursor().joinPreviousEditBlock();
}
}
void FakeVimHandler::Private::beginEditBlock(bool rememberPosition)
void FakeVimHandler::Private::beginEditBlock(bool largeEditBlock)
{
UNDO_DEBUG("BEGIN EDIT BLOCK");
if (m_editBlockLevel == 0)
m_cursor = cursor();
if (m_editBlockLevel == 0 || m_largeEditBlockRevision != -1) {
if (largeEditBlock)
m_largeEditBlockRevision = revision();
else
pushUndoState(false);
}
++m_editBlockLevel;
cursor().beginEditBlock();
if (rememberPosition)
setUndoPosition(false);
m_breakEditBlock = false;
}
......@@ -7011,9 +7039,30 @@ void FakeVimHandler::Private::endEditBlock()
QTC_ASSERT(m_editBlockLevel > 0,
qDebug() << "beginEditBlock() not called before endEditBlock()!"; return);
--m_editBlockLevel;
cursor().endEditBlock();
if (m_editBlockLevel == 0)
setCursor(m_cursor);
if (m_largeEditBlockRevision != -1)
m_largeEditBlockRevision = -1;
}
void FakeVimHandler::Private::onContentsChanged()
{
if (!document()->isUndoAvailable())
m_undo.clear();
const int rev = revision();
if (lastUndoRevision() > rev) {
m_redo.clear();
while (lastUndoRevision() > rev)
m_undo.pop();
} else {
while (!m_redo.empty() && m_redo.top().revision < rev)
m_redo.pop();
}
// External change.
if (m_editBlockLevel == 0 && document()->isUndoAvailable())
pushUndoState();
}
char FakeVimHandler::Private::currentModeCode() const
......@@ -7038,17 +7087,33 @@ void FakeVimHandler::Private::undoRedo(bool undo)
QStack<State> &stack = undo ? m_undo : m_redo;
QStack<State> &stack2 = undo ? m_redo : m_undo;
bool validState = !stack.empty();
State state = validState ? stack.pop() : State();
++m_editBlockLevel;
const int firstLine = firstVisibleLine();
CursorPosition lastPos(cursor());
const int current = revision();
if (undo)
EDITOR(undo());
else
EDITOR(redo());
const int rev = revision();
const int prevRev = validState ? state.revision : current;
int rev = current;
int last;
if (undo) {
do {
last = rev;
EDITOR(undo());
rev = revision();
} while (prevRev < rev && last != rev);
} else {
do {
last = rev;
EDITOR(redo());
rev = revision();
} while (prevRev > rev && last != rev);
}
// rewind/forward to last saved revision
while (!stack.empty() && stack.top().revision > rev)
stack.pop();
scrollToLine(firstLine);
--m_editBlockLevel;
if (current == rev) {
const QString msg = undo ? FakeVimHandler::tr("Already at oldest change.")
......@@ -7058,8 +7123,7 @@ void FakeVimHandler::Private::undoRedo(bool undo)
}
clearMessage();
if (!stack.empty()) {
State &state = stack.top();
if (validState) {
if (state.revision == rev) {
m_lastChangePosition = state.position;
Marks marks = m_marks;
......@@ -7071,7 +7135,9 @@ void FakeVimHandler::Private::undoRedo(bool undo)
setCursorPosition(m_lastChangePosition);
setAnchor();
state.revision = current;
stack2.push(stack.pop());
stack2.push(state);
} else {
stack.push(state);
}
}
......@@ -7507,7 +7573,7 @@ bool FakeVimHandler::Private::changeNumberTextObject(int count)
repl.prepend(prefix);
pos += block.position();
setUndoPosition();
pushUndoState();
setAnchorAndPosition(pos, pos + len);
replaceText(currentRange(), repl);
setPosition(pos + repl.size() - 1);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment