From 0f25f0c89b4b241e09e83eac93b67e14757b6d40 Mon Sep 17 00:00:00 2001 From: hluk <hluk@email.cz> Date: Sun, 17 Mar 2013 12:41:18 +0100 Subject: [PATCH] FakeVim: Initial support for Vim macro recording and replaying Record macro with 'q{0-9a-zA-Z"}' command and replay it with '@{0-9a-z".*+}'. Replaying with prompt ('=' instead of register) not supported. Change-Id: I49965a99d10910e8df09e62020ed496542e3b249 Reviewed-by: hjk <hjk121@nokiamail.com> --- src/plugins/fakevim/fakevim_test.cpp | 37 +++++++ src/plugins/fakevim/fakevimhandler.cpp | 127 ++++++++++++++++++++++--- src/plugins/fakevim/fakevimplugin.cpp | 2 +- src/plugins/fakevim/fakevimplugin.h | 2 + 4 files changed, 156 insertions(+), 12 deletions(-) diff --git a/src/plugins/fakevim/fakevim_test.cpp b/src/plugins/fakevim/fakevim_test.cpp index ba4ba26d84b..1997abae86c 100644 --- a/src/plugins/fakevim/fakevim_test.cpp +++ b/src/plugins/fakevim/fakevim_test.cpp @@ -2889,3 +2889,40 @@ void FakeVimPlugin::test_vim_Visual_d() KEYS("Vkx", '|' + lmid(4)); KEYS("P", '|' + lmid(0,1)+'\n' + lmid(3)); } + +void FakeVimPlugin::test_macros() +{ + TestData data; + setup(&data); + + // execute register content + data.setText("r1" N "r2r3"); + KEYS("\"xy$", X "r1" N "r2r3"); + KEYS("@x", X "11" N "r2r3"); + INTEGRITY(false); + + data.doKeys("j\"xy$"); + KEYS("@x", "11" N X "32r3"); + INTEGRITY(false); + + data.setText("3<C-A>"); + KEYS("\"xy$", X "3<C-A>"); + KEYS("@x", X "6<C-A>"); + KEYS("@x", X "9<C-A>"); + KEYS("2@x", "1" X "5<C-A>"); + KEYS("2@@", "2" X "1<C-A>"); + KEYS("@@", "2" X "4<C-A>"); + +// Raw characters for macro recording. +#define ESC "\x1b" +#define ENTER "\n" + + // record + data.setText("abc" N "def"); + KEYS("qx" "A" ENTER "- xyz" ESC "rZjI- opq" ENTER ESC "q" , "abc" N "- xyZ" N "- opq" N X "def"); + KEYS("@x" , "abc" N "- xyZ" N "- opq" N "def" N "- opq" N X "- xyZ"); + + data.setText(" 1 2 3" N " 4 5 6" N " 7 8 9"); + KEYS("qx" "wrXj" "q", " X 2 3" N " 4 5 6" N " 7 8 9"); + KEYS("2@x", " X 2 3" N " 4 X 6" N " 7 8 X"); +} diff --git a/src/plugins/fakevim/fakevimhandler.cpp b/src/plugins/fakevim/fakevimhandler.cpp index aead79f14c8..c4a13496d44 100644 --- a/src/plugins/fakevim/fakevimhandler.cpp +++ b/src/plugins/fakevim/fakevimhandler.cpp @@ -178,7 +178,9 @@ enum SubMode YankSubMode, // Used for y ZSubMode, // Used for z CapitalZSubMode, // Used for Z - ReplaceSubMode // Used for r + ReplaceSubMode, // Used for r + MacroRecordSubMode, // Used for q + MacroExecuteSubMode // Used for @ }; /*! A \e SubSubMode is used for things that require one more data item @@ -937,13 +939,16 @@ public: int key() const { return m_key; } + // Return raw character for macro recording or dot command. QChar raw() const { if (m_key == Key_Tab) return QLatin1Char('\t'); if (m_key == Key_Return) return QLatin1Char('\n'); - return m_key; + if (m_key == Key_Escape) + return QChar(27); + return m_xkey; } QString toString() const @@ -1438,6 +1443,8 @@ public: bool handleYankSubMode(const Input &); bool handleZSubMode(const Input &); bool handleCapitalZSubMode(const Input &); + bool handleMacroRecordSubMode(const Input &); + bool handleMacroExecuteSubMode(const Input &); bool handleCount(const Input &); // Handle count for commands (return false if input isn't count). bool handleMovement(const Input &); @@ -1659,6 +1666,12 @@ public: void ensureCursorVisible(); void insertInInsertMode(const QString &text); + // Macro recording + bool startRecording(const Input &input); + void record(const Input &input); + void stopRecording(); + bool executeRegister(int register); + public: QTextEdit *m_textedit; QPlainTextEdit *m_plaintextedit; @@ -1848,7 +1861,7 @@ public: GlobalData() : mappings(), currentMap(&mappings), inputTimer(-1), mapDepth(0), currentMessageLevel(MessageInfo), lastSearchForward(false), findPending(false), - returnToMode(CommandMode) + returnToMode(CommandMode), currentRegister(0), lastExecutedRegister(0) { commandBuffer.setPrompt(QLatin1Char(':')); } @@ -1892,6 +1905,11 @@ public: // Return to insert/replace mode after single command (<C-O>). Mode returnToMode; + + // Currently recorded macro (not recording if null string). + QString recording; + int currentRegister; + int lastExecutedRegister; } g; }; @@ -2316,8 +2334,10 @@ EventResult FakeVimHandler::Private::handleKey(const Input &input) // Waiting on input to complete mapping? EventResult r = stopWaitForMapping(hasInput); - if (hasInput) + if (hasInput) { + record(input); g.pendingInput.append(input); + } // Process pending input. // Note: Pending input is global state and can be extended by: @@ -2973,25 +2993,28 @@ void FakeVimHandler::Private::updateMiniBuffer() messageLevel = MessageShowCmd; } else if (m_mode == CommandMode && isVisualMode()) { if (isVisualCharMode()) - msg = _("VISUAL"); + msg = _("-- VISUAL --"); else if (isVisualLineMode()) - msg = _("VISUAL LINE"); + msg = _("-- VISUAL LINE --"); else if (isVisualBlockMode()) msg = _("VISUAL BLOCK"); } else if (m_mode == InsertMode) { - msg = _("INSERT"); + msg = _("-- INSERT --"); } else if (m_mode == ReplaceMode) { - msg = _("REPLACE"); + msg = _("-- REPLACE --"); } else { QTC_CHECK(m_mode == CommandMode && m_subsubmode != SearchSubSubMode); if (g.returnToMode == CommandMode) - msg = _("COMMAND"); + msg = _("-- COMMAND --"); else if (g.returnToMode == InsertMode) - msg = _("(insert)"); + msg = _("-- (insert) --"); else - msg = _("(replace)"); + msg = _("-- (replace) --"); } + if (!g.recording.isNull() && msg.startsWith(_("--"))) + msg.append(_("recording")); + emit q->commandBufferChanged(msg, cursorPos, anchorPos, messageLevel, q); int linesInDoc = linesInDocument(); @@ -3467,6 +3490,10 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) handled = handleZSubMode(input); } else if (m_submode == CapitalZSubMode) { handled = handleCapitalZSubMode(input); + } else if (m_submode == MacroRecordSubMode) { + handled = handleMacroRecordSubMode(input); + } else if (m_submode == MacroExecuteSubMode) { + handled = handleMacroExecuteSubMode(input); } else if (m_submode == ShiftLeftSubMode || m_submode == ShiftRightSubMode || m_submode == IndentSubMode) { @@ -3756,6 +3783,16 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input) setTargetColumn(); setDotCommand(_("%1p"), count()); finishMovement(); + } else if (input.is('q')) { + if (g.recording.isNull()) { + // Recording shouldn't work in mapping or while executing register. + handled = g.mapStates.empty(); + if (handled) + m_submode = MacroRecordSubMode; + } else { + // Stop recording. + stopRecording(); + } } else if (input.is('r')) { m_submode = ReplaceSubMode; } else if (!isVisualMode() && input.is('R')) { @@ -3924,6 +3961,8 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input) setDotCommand(QString::fromLatin1("%1%2").arg(count()).arg(input.raw())); endEditBlock(); } + } else if (input.is('@')) { + m_submode = MacroExecuteSubMode; } else if (input.isKey(Key_Delete)) { setAnchor(); moveRight(qMin(1, rightDist())); @@ -4161,6 +4200,24 @@ bool FakeVimHandler::Private::handleCapitalZSubMode(const Input &input) return handled; } +bool FakeVimHandler::Private::handleMacroRecordSubMode(const Input &input) +{ + m_submode = NoSubMode; + return startRecording(input); +} + +bool FakeVimHandler::Private::handleMacroExecuteSubMode(const Input &input) +{ + m_submode = NoSubMode; + + bool result = true; + int repeat = count(); + while (result && --repeat >= 0) + result = executeRegister(input.asChar().unicode()); + + return result; +} + EventResult FakeVimHandler::Private::handleReplaceMode(const Input &input) { bool clearLastInsertion = m_breakEditBlock; @@ -4466,6 +4523,54 @@ void FakeVimHandler::Private::insertInInsertMode(const QString &text) m_ctrlVActive = false; } +bool FakeVimHandler::Private::startRecording(const Input &input) +{ + QChar reg = input.asChar(); + if (reg == QLatin1Char('"') || reg.isLetterOrNumber()) { + g.currentRegister = reg.unicode(); + g.recording = QLatin1String(""); + return true; + } + + return false; +} + +void FakeVimHandler::Private::record(const Input &input) +{ + if ( !g.recording.isNull() ) + g.recording.append(input.raw()); +} + +void FakeVimHandler::Private::stopRecording() +{ + // Remove q from end (stop recording command). + g.recording.remove(g.recording.size() - 1, 1); + setRegister(g.currentRegister, g.recording, m_rangemode); + g.currentRegister = 0; + g.recording = QString(); +} + +bool FakeVimHandler::Private::executeRegister(int reg) +{ + QChar regChar(reg); + + // TODO: Prompt for an expression to execute if register is '='. + if (reg == '@' && g.lastExecutedRegister != 0) + reg = g.lastExecutedRegister; + else if (QString::fromLatin1("\".*+").contains(regChar) || regChar.isLetterOrNumber()) + g.lastExecutedRegister = reg; + else + return false; + + // FIXME: In Vim it's possible to interrupt recursive macro with <C-c>. + // One solution may be to call QApplication::processEvents() and check if <C-c> was + // used when a mapping is active. + // According to Vim, register is executed like mapping. + prependMapping(Inputs(registerContents(reg))); + + return true; +} + EventResult FakeVimHandler::Private::handleExMode(const Input &input) { if (input.isEscape()) { diff --git a/src/plugins/fakevim/fakevimplugin.cpp b/src/plugins/fakevim/fakevimplugin.cpp index f85a76c6581..91a21b0b5ec 100644 --- a/src/plugins/fakevim/fakevimplugin.cpp +++ b/src/plugins/fakevim/fakevimplugin.cpp @@ -151,7 +151,7 @@ public: hide(); } else { show(); - m_label->setText(messageLevel == MessageMode ? _("-- ") + contents + _(" --") : contents); + m_label->setText(contents); QString css; if (messageLevel == MessageError) { diff --git a/src/plugins/fakevim/fakevimplugin.h b/src/plugins/fakevim/fakevimplugin.h index 2ade5e54fce..9078acce4b3 100644 --- a/src/plugins/fakevim/fakevimplugin.h +++ b/src/plugins/fakevim/fakevimplugin.h @@ -140,6 +140,8 @@ private slots: void test_vim_visual_d(); void test_vim_Visual_d(); + void test_macros(); + // special tests void test_i_cw_i(); -- GitLab