From f626e27370bbc24b1750658dc3d7fd89116242f9 Mon Sep 17 00:00:00 2001 From: Tom Praschan <13141438+tom-anders@users.noreply.github.com> Date: Mon, 25 Jan 2021 21:06:53 +0100 Subject: [PATCH] FakeVim: Add emulation for vim-commentary Change-Id: I34f222182835ae160e6c4c66ad0bada79d8abeff Reviewed-by: hjk --- .../editors/creator-only/creator-fakevim.qdoc | 12 ++ src/plugins/fakevim/fakevim_test.cpp | 53 ++++++ src/plugins/fakevim/fakevimactions.cpp | 3 + src/plugins/fakevim/fakevimactions.h | 3 + src/plugins/fakevim/fakevimhandler.cpp | 103 ++++++++++- src/plugins/fakevim/fakevimoptions.ui | 171 ++++++++++-------- src/plugins/fakevim/fakevimplugin.cpp | 2 + src/plugins/fakevim/fakevimplugin.h | 4 + 8 files changed, 274 insertions(+), 77 deletions(-) diff --git a/doc/qtcreator/src/editors/creator-only/creator-fakevim.qdoc b/doc/qtcreator/src/editors/creator-only/creator-fakevim.qdoc index 6e14ef2fd65..922491a1d7f 100644 --- a/doc/qtcreator/src/editors/creator-only/creator-fakevim.qdoc +++ b/doc/qtcreator/src/editors/creator-only/creator-fakevim.qdoc @@ -110,6 +110,18 @@ \li \c :<, \c :> \endlist + \section2 Plugin Emulation + + FakeVim also emulates some popular vim plugins. To enable plugin emulation + for particular plugins, select \uicontrol Tools > \uicontrol Options > + \uicontrol FakeVim > \uicontrol General > \uicontrol {Plugin Emulation}. + + Currently emulated plugins: + \list + \li \l{https://github.com/tpope/vim-commentary}{vim-commentary}: \c gc + action to comment code regions. For example, \c gcc, \c gc2j, \c gcip + \endlist + \section2 Insert Mode \list diff --git a/src/plugins/fakevim/fakevim_test.cpp b/src/plugins/fakevim/fakevim_test.cpp index 7e5a43a01e9..dd7accfe1fd 100644 --- a/src/plugins/fakevim/fakevim_test.cpp +++ b/src/plugins/fakevim/fakevim_test.cpp @@ -4178,6 +4178,59 @@ void FakeVimPlugin::test_vim_visual_block_D() KEYS(".", X "a" N "g" N "" N "j"); } +void FakeVimPlugin::test_vim_commentary_emulation() +{ + TestData data; + setup(&data); + data.doCommand("set commentary"); + + // Commenting a single line + data.setText("abc" N "def"); + KEYS("gcc", X "// abc" N "def"); + KEYS("gcc", X "abc" N "def"); + KEYS(".", X "// abc" N "def"); + + // Multiple lines + data.setText("abc" N " def" N "ghi"); + KEYS("gcj", X "// abc" N " // def" N "ghi"); + KEYS("gcj", X "abc" N " def" N "ghi"); + KEYS("gc2j", X "// abc" N " // def" N "// ghi"); + KEYS("gcj", X "abc" N " def" N "// ghi"); + KEYS(".", X "// abc" N " // def" N "// ghi"); + + // Visual mode + data.setText("abc" N "def"); + KEYS("Vjgc", X "// abc" N "// def"); + KEYS(".", X "abc" N "def"); +} + +void FakeVimPlugin::test_vim_commentary_file_names() +{ + TestData data; + setup(&data); + data.doCommand("set commentary"); + + // Default is "//" + data.setText("abc"); + KEYS("gcc", X "// abc"); + + // pri and pro + data.handler->setCurrentFileName("Test.pri"); + data.setText("abc"); + KEYS("gcc", X "# abc"); + data.handler->setCurrentFileName("Test.pro"); + KEYS("gcc", X "abc"); + + // .h .hpp .cpp + data.handler->setCurrentFileName("Test.h"); + data.setText("abc"); + KEYS("gcc", X "// abc"); + data.handler->setCurrentFileName("Test.hpp"); + KEYS("gcc", X "abc"); + data.handler->setCurrentFileName("Test.cpp"); + KEYS("gcc", X "// abc"); +} + void FakeVimPlugin::test_macros() { TestData data; diff --git a/src/plugins/fakevim/fakevimactions.cpp b/src/plugins/fakevim/fakevimactions.cpp index 007d9fcf5ac..0368afd0f26 100644 --- a/src/plugins/fakevim/fakevimactions.cpp +++ b/src/plugins/fakevim/fakevimactions.cpp @@ -113,6 +113,9 @@ FakeVimSettings::FakeVimSettings() createAction(ConfigBackspace, QString("indent,eol,start"), "ConfigBackspace", "bs"); createAction(ConfigIsKeyword, QString("@,48-57,_,192-255,a-z,A-Z"), "IsKeyword", "isk"); createAction(ConfigClipboard, QString(), "Clipboard", "cb"); + + // Emulated plugins + createAction(ConfigEmulateVimCommentary, false, "commentary"); } FakeVimSettings::~FakeVimSettings() diff --git a/src/plugins/fakevim/fakevimactions.h b/src/plugins/fakevim/fakevimactions.h index 47d026b5f62..138023d2a63 100644 --- a/src/plugins/fakevim/fakevimactions.h +++ b/src/plugins/fakevim/fakevimactions.h @@ -108,6 +108,9 @@ enum FakeVimSettingsCode ConfigScrollOff, ConfigRelativeNumber, + // Plugin emulation + ConfigEmulateVimCommentary, + ConfigBlinkingCursor }; diff --git a/src/plugins/fakevim/fakevimhandler.cpp b/src/plugins/fakevim/fakevimhandler.cpp index b3b547c00e6..439c70b9e08 100644 --- a/src/plugins/fakevim/fakevimhandler.cpp +++ b/src/plugins/fakevim/fakevimhandler.cpp @@ -173,6 +173,7 @@ enum SubMode RegisterSubMode, // Used for " ShiftLeftSubMode, // Used for < ShiftRightSubMode, // Used for > + CommentSubMode, // Used for gc InvertCaseSubMode, // Used for g~ DownCaseSubMode, // Used for gu UpCaseSubMode, // Used for gU @@ -1342,6 +1343,8 @@ QString dotCommandFromSubMode(SubMode submode) return QLatin1String("c"); if (submode == DeleteSubMode) return QLatin1String("d"); + if (submode == CommentSubMode) + return QLatin1String("gc"); if (submode == InvertCaseSubMode) return QLatin1String("g~"); if (submode == DownCaseSubMode) @@ -1822,6 +1825,7 @@ public: bool handleChangeDeleteYankSubModes(const Input &); void handleChangeDeleteYankSubModes(); bool handleReplaceSubMode(const Input &); + bool handleCommentSubMode(const Input &); bool handleFilterSubMode(const Input &); bool handleRegisterSubMode(const Input &); bool handleShiftSubMode(const Input &); @@ -2084,6 +2088,7 @@ public: bool isOperatorPending() const { return g.submode == ChangeSubMode || g.submode == DeleteSubMode + || g.submode == CommentSubMode || g.submode == FilterSubMode || g.submode == IndentSubMode || g.submode == ShiftLeftSubMode @@ -2159,6 +2164,8 @@ public: void invertCase(const Range &range); + void toggleComment(const Range &range); + void upCase(const Range &range); void downCase(const Range &range); @@ -3582,6 +3589,7 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommandMovement) if (g.submode == ChangeSubMode || g.submode == DeleteSubMode + || g.submode == CommentSubMode || g.submode == YankSubMode || g.submode == InvertCaseSubMode || g.submode == DownCaseSubMode @@ -3608,6 +3616,11 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommandMovement) insertAutomaticIndentation(true); endEditBlock(); setTargetColumn(); + } else if (g.submode == CommentSubMode) { + pushUndoState(false); + beginEditBlock(); + toggleComment(currentRange()); + endEditBlock(); } else if (g.submode == DeleteSubMode) { pushUndoState(false); beginEditBlock(); @@ -4254,6 +4267,8 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) || g.submode == DeleteSubMode || g.submode == YankSubMode) { handled = handleChangeDeleteYankSubModes(input); + } else if (g.submode == CommentSubMode && hasConfig(ConfigEmulateVimCommentary)) { + handled = handleCommentSubMode(input); } else if (g.submode == ReplaceSubMode) { handled = handleReplaceSubMode(input); } else if (g.submode == FilterSubMode) { @@ -4395,6 +4410,31 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input) setTargetColumn(); } else if (input.isControl('a')) { changeNumberTextObject(count()); + } else if (g.gflag && input.is('c') && hasConfig(ConfigEmulateVimCommentary)) { + if (isVisualMode()) { + pushUndoState(); + + QTextCursor start(m_cursor); + QTextCursor end(start); + end.setPosition(end.anchor()); + + const int count = qAbs(start.blockNumber() - end.blockNumber()); + if (count == 0) { + dotCommand = "gcc"; + } else { + dotCommand = QString("gc%1j").arg(count); + } + + leaveVisualMode(); + toggleComment(currentRange()); + + g.submode = NoSubMode; + } else { + g.movetype = MoveLineWise; + g.submode = CommentSubMode; + pushUndoState(); + setAnchor(); + } } else if ((input.is('c') || input.is('d') || input.is('y')) && isNoVisualMode()) { setAnchor(); g.opcount = g.mvcount; @@ -4733,6 +4773,27 @@ bool FakeVimHandler::Private::handleReplaceSubMode(const Input &input) return handled; } +bool FakeVimHandler::Private::handleCommentSubMode(const Input &input) +{ + if (!input.is('c')) + return false; + + g.movetype = MoveLineWise; + + const int anc = firstPositionInLine(cursorLine() + 1); + moveDown(count() - 1); + const int pos = lastPositionInLine(cursorLine() + 1); + setAnchorAndPosition(anc, pos); + + setDotCommand(QString("%1gcc").arg(count())); + + finishMovement(); + + g.submode = NoSubMode; + + return true; +} + bool FakeVimHandler::Private::handleFilterSubMode(const Input &) { return false; @@ -7331,7 +7392,47 @@ void FakeVimHandler::Private::invertCase(const Range &range) result[i] = c.isUpper() ? c.toLower() : c.toUpper(); } return result; - }); + }); +} + +void FakeVimHandler::Private::toggleComment(const Range &range) +{ + static const QMap extensionToCommentString { + {"pri", "#"}, + {"pro", "#"}, + {"h", "//"}, + {"hpp", "//"}, + {"cpp", "//"}, + }; + const QString commentString = extensionToCommentString.value(QFileInfo(m_currentFileName).suffix(), "//"); + + transformText(range, + [&commentString] (const QString &text) -> QString { + + QStringList lines = text.split('\n'); + + const QRegExp checkForComment("^\\s*" + QRegExp::escape(commentString)); + + const bool firstLineIsComment + = !lines.empty() && lines.front().contains(checkForComment); + + for (auto& line : lines) { + if (!line.isEmpty()) { + if (firstLineIsComment) { + const bool hasSpaceAfterCommentString + = line.contains(QRegExp(checkForComment.pattern() + "\\s")); + const int sizeToReplace = hasSpaceAfterCommentString ? commentString.size() + 1 + : commentString.size(); + line.replace(line.indexOf(commentString), sizeToReplace, ""); + } else { + const int indexOfFirstNonSpace = line.indexOf(QRegExp("[^\\s]")); + line = line.left(indexOfFirstNonSpace) + commentString + " " + line.right(line.size() - indexOfFirstNonSpace); + } + } + } + + return lines.size() == 1 ? lines.front() : lines.join("\n"); + }); } void FakeVimHandler::Private::replaceText(const Range &range, const QString &str) diff --git a/src/plugins/fakevim/fakevimoptions.ui b/src/plugins/fakevim/fakevimoptions.ui index 82e72a1e074..8dd4b534db9 100644 --- a/src/plugins/fakevim/fakevimoptions.ui +++ b/src/plugins/fakevim/fakevimoptions.ui @@ -7,7 +7,7 @@ 0 0 580 - 474 + 531 @@ -26,52 +26,10 @@ - - + + - Start of line - - - - - - - Highlight search results - - - - - - - Smart indentation - - - - - - - Smart tabulators - - - - - - - Expand tabulators - - - - - - - Incremental search - - - - - - - Use search dialog + Use ignorecase @@ -82,10 +40,55 @@ - - + + - Use wrapscan + Start of line + + + + + + + Smart tabulators + + + + + + + Use search dialog + + + + + + + Displays line numbers relative to the line containing text cursor. + + + Show line numbers relative to cursor + + + + + + + Highlight search results + + + + + + + Incremental search + + + + + + + Show position of text marks @@ -106,13 +109,6 @@ - - - - Use ignorecase - - - @@ -120,6 +116,27 @@ + + + + Blinking cursor + + + + + + + Expand tabulators + + + + + + + Use wrapscan + + + @@ -130,32 +147,34 @@ - - + + - Blinking cursor - - - - - - - Show position of text marks - - - - - - - Displays line numbers relative to the line containing text cursor. - - - Show line numbers relative to cursor + Smart indentation + + + + Plugin Emulation + + + + + + Vim-commentary + + + false + + + + + + diff --git a/src/plugins/fakevim/fakevimplugin.cpp b/src/plugins/fakevim/fakevimplugin.cpp index 953c80f2f85..bd611bc14ed 100644 --- a/src/plugins/fakevim/fakevimplugin.cpp +++ b/src/plugins/fakevim/fakevimplugin.cpp @@ -429,6 +429,8 @@ QWidget *FakeVimOptionPage::widget() m_group.insert(theFakeVimSetting(ConfigRelativeNumber), m_ui.checkBoxRelativeNumber); m_group.insert(theFakeVimSetting(ConfigBlinkingCursor), m_ui.checkBoxBlinkingCursor); + m_group.insert(theFakeVimSetting(ConfigEmulateVimCommentary), m_ui.checkBoxVimCommentary); + connect(m_ui.pushButtonCopyTextEditorSettings, &QAbstractButton::clicked, this, &FakeVimOptionPage::copyTextEditorSettings); connect(m_ui.pushButtonSetQtStyle, &QAbstractButton::clicked, diff --git a/src/plugins/fakevim/fakevimplugin.h b/src/plugins/fakevim/fakevimplugin.h index 2dbcbfcb8a4..74ca665c9ee 100644 --- a/src/plugins/fakevim/fakevimplugin.h +++ b/src/plugins/fakevim/fakevimplugin.h @@ -157,6 +157,10 @@ private slots: void test_vim_Visual_d(); void test_vim_visual_block_D(); + // Plugin emulation + void test_vim_commentary_emulation(); + void test_vim_commentary_file_names(); + void test_macros(); void test_vim_qtcreator();