From e5c323e83aaca3f536cabd1852104256da267df5 Mon Sep 17 00:00:00 2001 From: hluk Date: Tue, 9 Jul 2013 19:39:31 +0200 Subject: [PATCH] FakeVim: Monitor inserted text using signals from QTextDocument Use QTextDocument::onContentsChanged() to monitor text inserted and removed in insert mode for dot command (or just repeat with 2i, 3o etc.) to re-insert same text. Works even if auto-completion is replacing '.' with '->' for pointers. Change-Id: Ie39edcdc9ec60bcf6c7d10f021248c3a0aee76b6 Reviewed-by: hjk --- src/plugins/fakevim/fakevim_test.cpp | 46 ++- src/plugins/fakevim/fakevimhandler.cpp | 526 ++++++++++++------------- 2 files changed, 300 insertions(+), 272 deletions(-) diff --git a/src/plugins/fakevim/fakevim_test.cpp b/src/plugins/fakevim/fakevim_test.cpp index e946ae20dad..2df0cb19903 100644 --- a/src/plugins/fakevim/fakevim_test.cpp +++ b/src/plugins/fakevim/fakevim_test.cpp @@ -3292,8 +3292,16 @@ void FakeVimPlugin::test_vim_qtcreator() ""); // Record long insert mode. - KEYS("qb" "4s" "bool" "" "Q_ASSERT" "" "2" - "2w1" ":s/true/false" "q", + KEYS("qb" + "4s" "bool" // 1 + "" + "Q_ASSERT" // 2 + "" + "2" // 3 + "2w" + "1" // 4 + ":s/true/false" // 5 + "q", "bool f(int arg1, int arg2 = 0) {" N " Q_ASSERT(arg1 >= 0);" N " if (arg1 > 0) return true;" N @@ -3301,35 +3309,35 @@ void FakeVimPlugin::test_vim_qtcreator() "}" N ""); - KEYS("u", + KEYS("u", // 5 "bool f(int arg1, int arg2 = 0) {" N " Q_ASSERT(arg1 >= 0);" N " if (arg1 > 0) return true;" N X " if (arg2 > 1) return true;" N "}" N ""); - KEYS("u", + KEYS("u", // 4 "bool f(int arg1, int arg2 = 0) {" N " Q_ASSERT(arg1 >= 0);" N " if (arg1 > 0) return true;" N " if (arg2 > " X "0) return true;" N "}" N ""); - KEYS("u", + KEYS("u", // 3 "bool f(int arg1, int arg2 = 0) {" N " Q_ASSERT(arg1 >= 0);" N " if (arg1 > 0) return true;" N " if (arg1" X " > 0) return true;" N "}" N ""); - KEYS("u", + KEYS("u", // 2 "bool f(int arg1, int arg2 = 0) {" N " " X "assert(arg1 >= 0);" N " if (arg1 > 0) return true;" N " if (arg1 > 0) return true;" N "}" N ""); - KEYS("u", + KEYS("u", // 1 X "void f(int arg1, int arg2 = 0) {" N " assert(arg1 >= 0);" N " if (arg1 > 0) return true;" N @@ -3374,4 +3382,28 @@ void FakeVimPlugin::test_vim_qtcreator() " if (arg2 > 1) return false;" N "}" N ""); + + // Macros + data.setText( + "void f(int arg1) {" N + "}" N + ""); + KEYS("2o" "#ifdef HAS_FEATUREdoSomething();" + "#else" + "doSomethingElse2();" + "#endif" + "", + "void f(int arg1) {" N + "#ifdef HAS_FEATURE" N + " doSomething();" N + "#else" N + " doSomething2();" N + "#endif" N + "#ifdef HAS_FEATURE" N + " doSomething();" N + "#else" N + " doSomething2();" N + "#endi" X "f" N + "}" N + ""); } diff --git a/src/plugins/fakevim/fakevimhandler.cpp b/src/plugins/fakevim/fakevimhandler.cpp index 3dc666f939b..e1fc84fe136 100644 --- a/src/plugins/fakevim/fakevimhandler.cpp +++ b/src/plugins/fakevim/fakevimhandler.cpp @@ -1458,8 +1458,9 @@ public: void clearPendingInput(); void waitForMapping(); EventResult stopWaitForMapping(bool hasInput); - EventResult handleInsertMode(const Input &); - EventResult handleReplaceMode(const Input &); + EventResult handleInsertOrReplaceMode(const Input &); + void handleInsertMode(const Input &); + void handleReplaceMode(const Input &); EventResult handleCommandMode(const Input &); @@ -1525,6 +1526,7 @@ public: int lastPositionInLine(int line, bool onlyVisibleLines = true) const; // 1 based line, 0 based pos int lineForPosition(int pos) const; // 1 based line, 0 based pos QString lineContents(int line) const; // 1 based line + QString textAt(int from, int to) const; void setLineContents(int line, const QString &contents); // 1 based line int blockBoundary(const QString &left, const QString &right, bool end, int count) const; // end or start position of current code block @@ -1651,9 +1653,10 @@ public: bool handleFfTt(QString key); - void enterInsertMode(); void initVisualInsertMode(QChar command); void enterReplaceMode(); + void enterInsertMode(); + void enterInsertOrReplaceMode(Mode mode); void enterCommandMode(Mode returnToMode = CommandMode); void enterExMode(const QString &contents = QString()); void showMessage(MessageLevel level, const QString &msg); @@ -1675,7 +1678,7 @@ public: void endEditBlock(); void breakEditBlock() { m_breakEditBlock = true; } - Q_SLOT void onContentsChanged(); + Q_SLOT void onContentsChanged(int position, int charsRemoved, int charsAdded); Q_SLOT void onUndoCommandAdded(); bool isInsertMode() const { return g.mode == InsertMode || g.mode == ReplaceMode; } @@ -1700,7 +1703,10 @@ public: Q_SLOT void importSelection(); void exportSelection(); - void recordInsertion(const QString &insert = QString()); + void commitInsertState(); + void invalidateInsertState(); + bool isInsertStateValid() const; + void clearLastInsertion(); void ensureCursorVisible(); void insertInInsertMode(const QString &text); @@ -1721,10 +1727,22 @@ public: int m_oldInternalPosition; // copy from last event to check for external changes int m_oldInternalAnchor; int m_oldPosition; // FIXME: Merge with above. - int m_oldDocumentLength; int m_register; bool m_visualBlockInsert; + // Insert state to get last inserted text. + struct InsertState { + int pos1; + int pos2; + int backspaces; + int deletes; + QSet spaces; + bool insertingSpaces; + QString textBeforeCursor; + bool newLineBefore; + bool newLineAfter; + } m_insertState; + bool m_fakeEnd; bool m_anchorPastEnd; bool m_positionPastEnd; // '$' & 'l' in visual mode can move past eol @@ -1781,10 +1799,8 @@ public: void insertNewLine(); - bool handleInsertInEditor(const Input &input, QString *insert); + bool handleInsertInEditor(const Input &input); bool passEventToEditor(QEvent &event); // Pass event to editor widget without filtering. Returns true if event was processed. - // Guess insert command for text modification which happened externally (e.g. code-completion). - QString guessInsertCommand(int pos1, int pos2, int len1, int len2); // undo handling int revision() const { return document()->revision(); } @@ -1799,7 +1815,7 @@ public: int m_lastUndoSteps; // extra data for '.' - void replay(const QString &text); + void replay(const QString &text, int repeat = 1); void setDotCommand(const QString &cmd) { g.dotCommand = cmd; } void setDotCommand(const QString &cmd, int n) { g.dotCommand = cmd.arg(n); } QString visualDotCommand() const; @@ -1832,9 +1848,7 @@ public: QString tabExpand(int len) const; Column indentation(const QString &line) const; void insertAutomaticIndentation(bool goingDown, bool forceAutoIndent = false); - bool removeAutomaticIndentation(); // true if something removed // number of autoindented characters - int m_justAutoIndented; void handleStartOfLine(); // register handling @@ -1992,7 +2006,8 @@ FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget) m_textedit = qobject_cast(widget); m_plaintextedit = qobject_cast(widget); if (editor()) { - connect(EDITOR(document()), SIGNAL(contentsChanged()), SLOT(onContentsChanged())); + connect(EDITOR(document()), SIGNAL(contentsChange(int,int,int)), + SLOT(onContentsChanged(int,int,int))); connect(EDITOR(document()), SIGNAL(undoCommandAdded()), SLOT(onUndoCommandAdded())); m_lastUndoSteps = document()->availableUndoSteps(); } @@ -2012,14 +2027,13 @@ void FakeVimHandler::Private::init() m_lastVisualModeInverted = false; m_targetColumn = 0; m_visualTargetColumn = 0; - m_justAutoIndented = 0; m_ctrlVActive = false; m_oldInternalAnchor = -1; m_oldInternalPosition = -1; m_oldExternalAnchor = -1; m_oldExternalPosition = -1; m_oldPosition = -1; - m_oldDocumentLength = -1; + m_insertState = InsertState(); m_breakEditBlock = false; m_searchStartPosition = 0; m_searchFromScreenLine = 0; @@ -2032,8 +2046,10 @@ void FakeVimHandler::Private::init() void FakeVimHandler::Private::focus() { stopIncrementalFind(); - leaveVisualMode(); - resetCommandMode(); + if (!isInsertMode()) { + leaveVisualMode(); + resetCommandMode(); + } updateCursorShape(); if (!g.inFakeVim || g.mode != CommandMode) updateMiniBuffer(); @@ -2298,31 +2314,62 @@ void FakeVimHandler::Private::exportSelection() m_oldExternalAnchor = anchor(); } -void FakeVimHandler::Private::recordInsertion(const QString &insert) +void FakeVimHandler::Private::commitInsertState() { - const int pos = position(); + if (!isInsertStateValid()) + return; - if (insert.isNull()) { - const int dist = pos - m_oldPosition; + // Get raw inserted text. + m_lastInsertion = textAt(m_insertState.pos1, m_insertState.pos2); - if (dist > 0) { - Range range(m_oldPosition, pos); - QString text = selectText(range); - // escape text like - text.replace(_("<"), _("")); - m_lastInsertion.append(text); - } else if (dist < 0) { - m_lastInsertion.resize(m_lastInsertion.size() + dist); - } - } else { - m_lastInsertion += insert; + // Escape special characters and spaces inserted by user (not by auto-indentation). + for (int i = m_lastInsertion.size() - 1; i >= 0; --i) { + const int pos = m_insertState.pos1 + i; + const ushort c = document()->characterAt(pos).unicode(); + if (c == '<') + m_lastInsertion.replace(i, 1, _("")); + else if ((c == ' ' || c == '\t') && m_insertState.spaces.contains(pos)) + m_lastInsertion.replace(i, 1, _(c == ' ' ? "" : "")); } - if (m_oldPosition != pos) { - m_oldPosition = pos; - setTargetColumn(); - } - m_oldDocumentLength = document()->characterCount(); + // Remove unnecessary backspaces. + while (m_insertState.backspaces > 0 && !m_lastInsertion.isEmpty() && m_lastInsertion[0].isSpace()) + --m_insertState.backspaces; + + // backspaces in front of inserted text + m_lastInsertion.prepend(QString(_("")).repeated(m_insertState.backspaces)); + // deletes after inserted text + m_lastInsertion.prepend(QString(_("")).repeated(m_insertState.deletes)); + + // Remove indentation. + m_lastInsertion.replace(QRegExp(_("(^|\n)[\\t ]+")), _("\\1")); +} + +void FakeVimHandler::Private::invalidateInsertState() +{ + m_oldPosition = position(); + m_insertState.pos1 = -1; + m_insertState.pos2 = m_oldPosition; + m_insertState.backspaces = 0; + m_insertState.deletes = 0; + m_insertState.spaces.clear(); + m_insertState.insertingSpaces = false; + m_insertState.textBeforeCursor = textAt(document()->findBlock(m_oldPosition).position(), + m_oldPosition); + m_insertState.newLineBefore = false; + m_insertState.newLineAfter = false; +} + +bool FakeVimHandler::Private::isInsertStateValid() const +{ + return m_insertState.pos1 != -1; +} + +void FakeVimHandler::Private::clearLastInsertion() +{ + invalidateInsertState(); + m_lastInsertion.clear(); + m_insertState.pos1 = m_insertState.pos2; } void FakeVimHandler::Private::ensureCursorVisible() @@ -2469,10 +2516,8 @@ EventResult FakeVimHandler::Private::handleDefaultKey(const Input &input) return handleSearchSubSubMode(input); else if (g.mode == CommandMode) return handleCommandMode(input); - else if (g.mode == InsertMode) - return handleInsertMode(input); - else if (g.mode == ReplaceMode) - return handleReplaceMode(input); + else if (g.mode == InsertMode || g.mode == ReplaceMode) + return handleInsertOrReplaceMode(input); else if (g.mode == ExMode) return handleExMode(input); return EventUnhandled; @@ -2965,8 +3010,6 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommandMovement) insertAutomaticIndentation(true); endEditBlock(); setTargetColumn(); - m_lastInsertion.clear(); - g.returnToMode = InsertMode; } else if (g.submode == DeleteSubMode) { pushUndoState(false); beginEditBlock(); @@ -3037,7 +3080,13 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommandMovement) if (!dotCommand.isEmpty() && !dotCommandMovement.isEmpty()) setDotCommand(dotCommand + dotCommandMovement); - resetCommandMode(); + // Change command continues in insert mode. + if (g.submode == ChangeSubMode) { + clearCommandMode(); + enterInsertMode(); + } else { + resetCommandMode(); + } } void FakeVimHandler::Private::resetCommandMode() @@ -3052,8 +3101,8 @@ void FakeVimHandler::Private::resetCommandMode() else enterReplaceMode(); moveToTargetColumn(); + invalidateInsertState(); m_lastInsertion = lastInsertion; - m_oldPosition = position(); } if (isNoVisualMode()) setAnchor(); @@ -3901,19 +3950,20 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input) } const int line = lineNumber(block()); - enterInsertMode(); beginEditBlock(); + enterInsertMode(); + setPosition(appendLine ? lastPositionInLine(line) : firstPositionInLine(line)); + clearLastInsertion(); + setAnchor(); + insertNewLine(); if (appendLine) { - setPosition(lastPositionInLine(line)); - setAnchor(); - insertNewLine(); + m_insertState.newLineBefore = true; } else { - setPosition(firstPositionInLine(line)); - setAnchor(); - insertNewLine(); moveUp(); + m_oldPosition = position(); + m_insertState.pos1 = m_oldPosition; + m_insertState.newLineAfter = true; } - recordInsertion(QString::fromLatin1("\n")); setTargetColumn(); endEditBlock(); @@ -4365,44 +4415,55 @@ bool FakeVimHandler::Private::handleMacroExecuteSubMode(const Input &input) return result; } -EventResult FakeVimHandler::Private::handleReplaceMode(const Input &input) +EventResult FakeVimHandler::Private::handleInsertOrReplaceMode(const Input &input) { - bool clearLastInsertion = m_breakEditBlock; - if (m_oldPosition != position()) { - if (clearLastInsertion) { - clearLastInsertion = false; - m_lastInsertion = _(""); - } - recordInsertion(); + if (position() < m_insertState.pos1 || position() > m_insertState.pos2) { + commitInsertState(); + invalidateInsertState(); } + if (g.mode == InsertMode) + handleInsertMode(input); + else + handleReplaceMode(input); + + if (!isInsertMode() || m_breakEditBlock + || position() < m_insertState.pos1 || position() > m_insertState.pos2) { + commitInsertState(); + invalidateInsertState(); + breakEditBlock(); + } else if (m_oldPosition == position()) { + setTargetColumn(); + } + + updateMiniBuffer(); + + // We don't want fancy stuff in insert mode. + return EventHandled; +} + +void FakeVimHandler::Private::handleReplaceMode(const Input &input) +{ if (input.isEscape()) { + commitInsertState(); moveLeft(qMin(1, leftDist())); enterCommandMode(); - g.dotCommand += m_lastInsertion; - g.dotCommand += QChar(27); + g.dotCommand.append(m_lastInsertion + _("")); } else if (input.isKey(Key_Left)) { - breakEditBlock(); - moveLeft(1); + moveLeft(); setTargetColumn(); } else if (input.isKey(Key_Right)) { - breakEditBlock(); - moveRight(1); + moveRight(); setTargetColumn(); } else if (input.isKey(Key_Up)) { - breakEditBlock(); - moveUp(1); + moveUp(); } else if (input.isKey(Key_Down)) { - breakEditBlock(); - moveDown(1); + moveDown(); } else if (input.isKey(Key_Insert)) { g.mode = InsertMode; - recordInsertion(_("")); } else if (input.isControl('o')) { enterCommandMode(ReplaceMode); } else { - if (clearLastInsertion) - m_lastInsertion = _(""); joinPreviousEditBlock(); if (!atEndOfLine()) { setAnchor(); @@ -4413,38 +4474,34 @@ EventResult FakeVimHandler::Private::handleReplaceMode(const Input &input) setAnchor(); insertText(text); endEditBlock(); - recordInsertion(); } - m_oldPosition = position(); - updateMiniBuffer(); - - return EventHandled; } -EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) +void FakeVimHandler::Private::handleInsertMode(const Input &input) { - bool clearLastInsertion = m_breakEditBlock && !m_lastInsertion.isEmpty(); - int pos2 = position(); - int len2 = document()->characterCount(); - if (m_oldPosition != pos2 || m_oldDocumentLength != len2) { - if (clearLastInsertion) { - clearLastInsertion = false; - m_lastInsertion.clear(); - g.dotCommand = _("i"); - } - recordInsertion(guessInsertCommand(m_oldPosition, pos2, m_oldDocumentLength, len2)); - } - - QString insert; if (input.isEscape()) { + bool newLineAfter = m_insertState.newLineAfter; + bool newLineBefore = m_insertState.newLineBefore; + // Repeat insertion [count] times. // One instance was already physically inserted while typing. - if (!m_breakEditBlock && !m_lastInsertion.isEmpty()) { - const QString text = m_lastInsertion; - const int repeat = count(); + if (!m_breakEditBlock && isInsertStateValid()) { + commitInsertState(); + + QString text = m_lastInsertion; + const QString dotCommand = g.dotCommand; + const int repeat = count() - 1; m_lastInsertion.clear(); joinPreviousEditBlock(); - replay(text.repeated(repeat - 1)); + + if (newLineAfter) { + text.chop(1); + text.prepend(_("\n")); + } else if (newLineBefore) { + text.prepend(_("")); + } + + replay(text, repeat); if (m_visualBlockInsert && !text.contains(QLatin1Char('\n'))) { const CursorPosition lastAnchor = mark(QLatin1Char('<')).position; @@ -4452,7 +4509,7 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) CursorPosition startPos(lastAnchor.line, qMin(lastPosition.column, lastAnchor.column)); CursorPosition pos = startPos; - if (g.dotCommand.endsWith(QLatin1Char('A'))) + if (dotCommand.endsWith(QLatin1Char('A'))) pos.column = qMax(lastPosition.column, lastAnchor.column) + 1; while (pos.line < lastPosition.line) { ++pos.line; @@ -4462,7 +4519,7 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) break; m_cursor = tc; if (tc.positionInBlock() == pos.column) - replay(text.repeated(repeat)); + replay(text, repeat + 1); } setCursorPosition(startPos); @@ -4474,14 +4531,14 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) breakEditBlock(); m_lastInsertion = text; + g.dotCommand = dotCommand; } else { moveLeft(qMin(1, leftDist())); } - // If command is 'o' or 'O' don't include the first line feed in dot command. - if (g.dotCommand.endsWith(QLatin1Char('o'), Qt::CaseInsensitive)) - m_lastInsertion.remove(0, 1); - g.dotCommand += m_lastInsertion + _(""); + if (newLineBefore || newLineAfter) + m_lastInsertion.remove(0, m_lastInsertion.indexOf(QLatin1Char('\n')) + 1); + g.dotCommand.append(m_lastInsertion + _("")); enterCommandMode(); setTargetColumn(); @@ -4493,7 +4550,6 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) enterCommandMode(InsertMode); } else if (input.isControl('v')) { m_ctrlVActive = true; - insert = _(""); } else if (input.isControl('w')) { const int blockNumber = m_cursor.blockNumber(); const int endPos = position(); @@ -4503,10 +4559,8 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) const int beginPos = position(); Range range(beginPos, endPos, RangeCharMode); removeText(range); - insert = _(""); } else if (input.isKey(Key_Insert)) { g.mode = ReplaceMode; - insert = _(""); } else if (input.isKey(Key_Left)) { moveLeft(); setTargetColumn(); @@ -4514,11 +4568,9 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) moveToNextWordStart(1, false, false); setTargetColumn(); } else if (input.isKey(Key_Down)) { - //removeAutomaticIndentation(); g.submode = NoSubMode; moveDown(); } else if (input.isKey(Key_Up)) { - //removeAutomaticIndentation(); g.submode = NoSubMode; moveUp(); } else if (input.isKey(Key_Right)) { @@ -4536,17 +4588,15 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) setTargetColumn(); m_targetColumn = -1; } else if (input.isReturn() || input.isControl('j') || input.isControl('m')) { - if (!input.isReturn() || !handleInsertInEditor(input, &insert)) { + if (!input.isReturn() || !handleInsertInEditor(input)) { joinPreviousEditBlock(); g.submode = NoSubMode; insertNewLine(); - insert = _("\n"); endEditBlock(); } } else if (input.isBackspace()) { - if (!handleInsertInEditor(input, &insert)) { + if (!handleInsertInEditor(input)) { joinPreviousEditBlock(); - m_justAutoIndented = 0; if (!m_lastInsertion.isEmpty() || hasConfig(ConfigBackspace, "start") || hasConfig(ConfigBackspace, "2")) { @@ -4567,34 +4617,29 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) m_cursor.deletePreviousChar(); } } - insert = _(""); endEditBlock(); } } else if (input.isKey(Key_Delete)) { - if (!handleInsertInEditor(input, &insert)) { + if (!handleInsertInEditor(input)) { joinPreviousEditBlock(); m_cursor.deleteChar(); - insert = _(""); endEditBlock(); } } else if (input.isKey(Key_PageDown) || input.isControl('f')) { - removeAutomaticIndentation(); movePageDown(); } else if (input.isKey(Key_PageUp) || input.isControl('b')) { - removeAutomaticIndentation(); movePageUp(); } else if (input.isKey(Key_Tab)) { - m_justAutoIndented = 0; + m_insertState.insertingSpaces = true; if (hasConfig(ConfigExpandTab)) { const int ts = config(ConfigTabStop).toInt(); const int col = logicalCursorColumn(); QString str = QString(ts - col % ts, QLatin1Char(' ')); - m_lastInsertion.append(str); insertText(str); } else { insertInInsertMode(input.raw()); } - insert = _("\t"); + m_insertState.insertingSpaces = false; } else if (input.isControl('d')) { // remove one level of indentation from the current line int shift = config(ConfigShiftWidth).toInt(); @@ -4613,60 +4658,33 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) break; } removeText(Range(pos, pos+i)); - insert = _(""); - //} else if (key >= control('a') && key <= control('z')) { - // // ignore these } else if (input.isControl('p') || input.isControl('n')) { QTextCursor tc = m_cursor; moveToNextWordStart(1, false, false); QString str = selectText(Range(position(), tc.position())); m_cursor = tc; emit q->simpleCompletionRequested(str, input.isControl('n')); - if (input.isControl('p')) - insert = _(""); - else - insert = _(""); } else if (input.isShift(Qt::Key_Insert)) { // Insert text from clipboard. QClipboard *clipboard = QApplication::clipboard(); const QMimeData *data = clipboard->mimeData(); if (data && data->hasText()) insertInInsertMode(data->text()); - insert = _(""); } else { - if (!handleInsertInEditor(input, &insert)) { - insert = input.text(); - if (!insert.isEmpty()) { - insertInInsertMode(insert); - insert.replace(_("<"), _("")); - } else { - // We don't want fancy stuff in insert mode. - return EventHandled; - } + m_insertState.insertingSpaces = input.isKey(Key_Space); + if (!handleInsertInEditor(input)) { + const QString toInsert = input.text(); + if (toInsert.isEmpty()) + return; + insertInInsertMode(toInsert); } + m_insertState.insertingSpaces = false; } - - if (insert.isNull()) { - breakEditBlock(); - m_oldPosition = position(); - m_visualBlockInsert = false; - resetCount(); - } else { - if (clearLastInsertion) { - m_lastInsertion.clear(); - g.dotCommand = _("i"); - } - recordInsertion(insert); - } - updateMiniBuffer(); - - return EventHandled; } void FakeVimHandler::Private::insertInInsertMode(const QString &text) { joinPreviousEditBlock(); - m_justAutoIndented = 0; insertText(text); if (hasConfig(ConfigSmartIndent) && isElectricCharacter(text.at(0))) { const QString leftText = block().text() @@ -5947,7 +5965,11 @@ void FakeVimHandler::Private::indentText(const Range &range, QChar typedChar) int endBlock = document()->findBlock(range.endPos).blockNumber(); if (beginBlock > endBlock) qSwap(beginBlock, endBlock); + + // Don't remember current indentation in last text insertion. + const QString lastInsertion = m_lastInsertion; emit q->indentRegion(beginBlock, endBlock, typedChar); + m_lastInsertion = lastInsertion; } bool FakeVimHandler::Private::isElectricCharacter(QChar c) const @@ -6841,7 +6863,7 @@ void FakeVimHandler::Private::joinLines(int count, bool preserveSpace) void FakeVimHandler::Private::insertNewLine() { - if ( hasConfig(ConfigPassKeys) ) { + if ( m_editBlockLevel <= 1 && hasConfig(ConfigPassKeys) ) { QKeyEvent event(QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier, QLatin1String("\n")); if (passEventToEditor(event)) return; @@ -6851,27 +6873,19 @@ void FakeVimHandler::Private::insertNewLine() insertAutomaticIndentation(true); } -bool FakeVimHandler::Private::handleInsertInEditor(const Input &input, QString *insert) +bool FakeVimHandler::Private::handleInsertInEditor(const Input &input) { if (m_editBlockLevel > 0 || !hasConfig(ConfigPassKeys)) return false; joinPreviousEditBlock(); - const int pos1 = position(); - const int len1 = lastPositionInDocument(); - QKeyEvent event(QEvent::KeyPress, input.key(), static_cast(input.modifiers()), input.text()); setAnchor(); if (!passEventToEditor(event)) return false; - const int pos2 = position(); - const int len2 = lastPositionInDocument(); - - *insert = guessInsertCommand(pos1, pos2, len1, len2); - endEditBlock(); return true; @@ -6898,75 +6912,19 @@ bool FakeVimHandler::Private::passEventToEditor(QEvent &event) return accepted; } -QString FakeVimHandler::Private::guessInsertCommand(int pos1, int pos2, int len1, int len2) -{ - QString insert; - - // Guess the inserted/deleted text. - if (len1 > len2) { - // Text deleted. - if (pos1 == pos2) { - // Text after cursor deleted. - insert = QString(_("%1x")).arg(len1 - len2); - } else if (pos1 > pos2) { - // Text in front of cursor deleted. - const int backspaces = pos1 - pos2; - insert = QString(_("")).repeated(backspaces); - // Some text after cursor may have beed deleted too. - const int deletes = len1 - len2 - backspaces; - if (deletes > 0) - insert.append(QString(_("%1x")).arg(deletes)); - } - } else if (len1 < len2) { - // Text inserted. - if (pos1 < pos2) { - QTextCursor tc = m_cursor; - tc.setPosition(pos1); - tc.setPosition(pos2, KeepAnchor); - insert = QString(tc.selectedText()).replace(_("<"), _("")); - - const int textLen = pos2 - pos1; - const int rest = len2 - len1 - textLen; - if (rest > 0) { - // Text inserted after new cursor position. - // On dot command, cursor must insert the same text and move in front of it. - tc.setPosition(pos2); - tc.setPosition(pos2 + rest, KeepAnchor); - insert.append(QString(tc.selectedText()).replace(_("<"), _(""))); - - const int up = document()->findBlock(pos2).blockNumber() - - document()->findBlock(pos1).blockNumber(); - if (up > 0) { - insert.append(QString(_("")).repeated(up)); - insert.append(_("")); - const int right = rightDist(); - if (right > 0) - insert.append(QString(_("")).repeated(right)); - } else { - insert.append(QString(_("")).repeated(rest)); - } - } - } - } else { - // Document length is unchanged so assume that no text inserted or deleted. - // Check if cursor moved. - const int right = pos2 - pos1; - if (right > 0) - insert = QString(_("")).repeated(right); - else if (right < 0) - insert = QString(_("")).repeated(-right); - else - insert = _(""); // Empty non-null string. - } - - return insert; -} - QString FakeVimHandler::Private::lineContents(int line) const { return document()->findBlockByLineNumber(line - 1).text(); } +QString FakeVimHandler::Private::textAt(int from, int to) const +{ + QTextCursor tc(document()); + tc.setPosition(from); + tc.setPosition(to, KeepAnchor); + return tc.selectedText().replace(ParagraphSeparator, QLatin1Char('\n')); +} + void FakeVimHandler::Private::setLineContents(int line, const QString &contents) { QTextBlock block = document()->findBlockByLineNumber(line - 1); @@ -7155,7 +7113,7 @@ QWidget *FakeVimHandler::Private::editor() const void FakeVimHandler::Private::joinPreviousEditBlock() { UNDO_DEBUG("JOIN"); - if (m_breakEditBlock && m_editBlockLevel == 0) { + if (m_breakEditBlock) { beginEditBlock(); QTextCursor tc(m_cursor); tc.setPosition(tc.position()); @@ -7163,6 +7121,7 @@ void FakeVimHandler::Private::joinPreviousEditBlock() tc.insertText(_("X")); tc.deletePreviousChar(); tc.endEditBlock(); + m_breakEditBlock = false; } else { if (m_editBlockLevel == 0 && !m_undo.empty()) m_undoState = m_undo.pop(); @@ -7175,8 +7134,9 @@ void FakeVimHandler::Private::beginEditBlock(bool largeEditBlock) UNDO_DEBUG("BEGIN EDIT BLOCK"); if (!largeEditBlock && !m_undoState.isValid()) pushUndoState(false); + if (m_editBlockLevel == 0) + m_breakEditBlock = true; ++m_editBlockLevel; - m_breakEditBlock = false; } void FakeVimHandler::Private::endEditBlock() @@ -7189,10 +7149,54 @@ void FakeVimHandler::Private::endEditBlock() m_undo.push(m_undoState); m_undoState = State(); } + if (m_editBlockLevel == 0) + m_breakEditBlock = false; } -void FakeVimHandler::Private::onContentsChanged() +void FakeVimHandler::Private::onContentsChanged(int position, int charsRemoved, int charsAdded) { + // Record inserted and deleted text in insert mode. + if (isInsertMode() && (charsAdded > 0 || charsRemoved > 0)) { + if (!isInsertStateValid()) { + m_insertState.pos1 = m_oldPosition; + g.dotCommand = _("i"); + resetCount(); + } + + // Ignore changes outside inserted text (e.g. renaming other occurrences of a variable). + if (position + charsRemoved >= m_insertState.pos1 && position <= m_insertState.pos2) { + if (charsRemoved > 0) { + if (position < m_insertState.pos1) { + // backspaces + const int bs = m_insertState.pos1 - position; + const QString inserted = textAt(position, position + charsAdded); + const QString removed = m_insertState.textBeforeCursor.right(bs); + // Ignore backspaces if same text was just inserted. + if ( !inserted.startsWith(removed) ) { + m_insertState.backspaces += bs; + m_insertState.pos1 = position; + m_insertState.pos2 = qMax(position, m_insertState.pos2 - bs); + } + } else if (position + charsRemoved > m_insertState.pos2) { + // deletes + m_insertState.deletes += position + charsRemoved - m_insertState.pos2; + } + } else if (charsAdded > 0 && m_insertState.insertingSpaces) { + for (int i = position; i < position + charsAdded; ++i) { + const QChar c = document()->characterAt(i); + if (c.unicode() == ' ' || c.unicode() == '\t') + m_insertState.spaces.insert(i); + } + } + + m_insertState.pos2 = qMax(m_insertState.pos2 + charsAdded - charsRemoved, + position + charsAdded); + m_oldPosition = position + charsAdded; + m_insertState.textBeforeCursor = textAt(document()->findBlock(m_oldPosition).position(), + m_oldPosition); + } + } + if (!document()->isUndoAvailable()) m_undo.clear(); @@ -7321,28 +7325,31 @@ void FakeVimHandler::Private::updateCursorShape() void FakeVimHandler::Private::enterReplaceMode() { - g.mode = ReplaceMode; - g.submode = NoSubMode; - g.subsubmode = NoSubSubMode; - m_lastInsertion.clear(); - m_oldPosition = position(); - g.returnToMode = ReplaceMode; + enterInsertOrReplaceMode(ReplaceMode); } void FakeVimHandler::Private::enterInsertMode() { - g.mode = InsertMode; - g.submode = NoSubMode; - g.subsubmode = NoSubSubMode; - m_lastInsertion.clear(); - m_oldPosition = position(); - m_oldDocumentLength = document()->characterCount(); - if (g.returnToMode != InsertMode) { - g.returnToMode = InsertMode; + enterInsertOrReplaceMode(InsertMode); +} + +void FakeVimHandler::Private::enterInsertOrReplaceMode(Mode mode) +{ + QTC_ASSERT(mode == InsertMode || mode == ReplaceMode, return); + if (g.mode == mode) + return; + + if (mode == InsertMode && g.returnToMode != InsertMode) { // If entering insert mode from command mode, m_targetColumn shouldn't be -1 (end of line). if (m_targetColumn == -1) setTargetColumn(); } + + g.mode = mode; + g.submode = NoSubMode; + g.subsubmode = NoSubSubMode; + g.returnToMode = mode; + clearLastInsertion(); } void FakeVimHandler::Private::initVisualInsertMode(QChar command) @@ -7473,9 +7480,7 @@ void FakeVimHandler::Private::insertAutomaticIndentation(bool goingDown, bool fo if (hasConfig(ConfigSmartIndent)) { QTextBlock bl = block(); Range range(bl.position(), bl.position()); - const int oldSize = bl.text().size(); indentText(range, QLatin1Char('\n')); - m_justAutoIndented = bl.text().size() - oldSize; } else { QTextBlock bl = goingDown ? block().previous() : block().next(); QString text = bl.text(); @@ -7486,37 +7491,28 @@ void FakeVimHandler::Private::insertAutomaticIndentation(bool goingDown, bool fo text.truncate(pos); // FIXME: handle 'smartindent' and 'cindent' insertText(text); - m_justAutoIndented = text.size(); } } -bool FakeVimHandler::Private::removeAutomaticIndentation() -{ - if (!hasConfig(ConfigAutoIndent) || m_justAutoIndented == 0) - return false; -/* - m_tc.movePosition(StartOfLine, KeepAnchor); - m_tc.removeSelectedText(); - m_lastInsertion.chop(m_justAutoIndented); -*/ - m_justAutoIndented = 0; - return true; -} - void FakeVimHandler::Private::handleStartOfLine() { if (hasConfig(ConfigStartOfLine)) moveToFirstNonBlankOnLine(); } -void FakeVimHandler::Private::replay(const QString &command) +void FakeVimHandler::Private::replay(const QString &command, int repeat) { + if (repeat <= 0) + return; + //qDebug() << "REPLAY: " << quoteUnprintable(command); clearCommandMode(); Inputs inputs(command); - foreach (const Input &in, inputs) { - if (handleDefaultKey(in) != EventHandled) - break; + for (int i = 0; i < repeat; ++i) { + foreach (const Input &in, inputs) { + if (handleDefaultKey(in) != EventHandled) + return; + } } }