summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Praschan <tom@praschan.de>2021-01-30 14:16:02 +0100
committerTom Praschan <tom@praschan.de>2021-02-17 08:50:21 +0000
commit1085d6b508b13faf8c9cc73b6d80fa18d6757c29 (patch)
treeb83f702a26b6811611c9a2bc3fdaf111bb51787d
parentb3686d410dc02db3461e1b2514df65dae76c1278 (diff)
downloadqt-creator-1085d6b508b13faf8c9cc73b6d80fa18d6757c29.tar.gz
FakeVim: Add emulation for vim-surround plugin
Change-Id: If450d04dd89a1707ab05806522fbf4cc987d454b Reviewed-by: hjk <hjk@qt.io>
-rw-r--r--doc/qtcreator/src/editors/creator-only/creator-fakevim.qdoc5
-rw-r--r--src/plugins/fakevim/fakevim_test.cpp70
-rw-r--r--src/plugins/fakevim/fakevimactions.cpp1
-rw-r--r--src/plugins/fakevim/fakevimactions.h1
-rw-r--r--src/plugins/fakevim/fakevimhandler.cpp243
-rw-r--r--src/plugins/fakevim/fakevimoptions.ui29
-rw-r--r--src/plugins/fakevim/fakevimplugin.cpp1
-rw-r--r--src/plugins/fakevim/fakevimplugin.h1
8 files changed, 328 insertions, 23 deletions
diff --git a/doc/qtcreator/src/editors/creator-only/creator-fakevim.qdoc b/doc/qtcreator/src/editors/creator-only/creator-fakevim.qdoc
index fc6838b161..120411684d 100644
--- a/doc/qtcreator/src/editors/creator-only/creator-fakevim.qdoc
+++ b/doc/qtcreator/src/editors/creator-only/creator-fakevim.qdoc
@@ -128,11 +128,12 @@
\li \c [count]["x]gr{motion} to replace \c {motion} with the contents of
register \c x.
\li ["x]grr to replace the current line.
- \l{https://github.com/tommcdo/vim-exchange}{vim-exchange}
\endlist
+ \li \l{https://github.com/tommcdo/vim-exchange}{vim-exchange}
\li \l{https://github.com/vim-scripts/argtextobj.vim}{argtextobj.vim}:
Defines the \c ia and \c aa text objects for function parameters.
- \endlist
+ \li \l{https://github.com/tpope/vim-surround}{vim-surround}:
+ Adds mappings for deleting, adding and changing surroundings.
\endlist
\section2 Insert Mode
diff --git a/src/plugins/fakevim/fakevim_test.cpp b/src/plugins/fakevim/fakevim_test.cpp
index 6048e083f7..2335288734 100644
--- a/src/plugins/fakevim/fakevim_test.cpp
+++ b/src/plugins/fakevim/fakevim_test.cpp
@@ -4341,6 +4341,76 @@ void FakeVimPlugin::test_vim_arg_text_obj_emulation()
KEYS("dia", "foo()");
}
+void FakeVimPlugin::test_vim_surround_emulation()
+{
+ TestData data;
+ setup(&data);
+ data.doCommand("set surround");
+
+ // ys and ds
+ data.setText("abc");
+ KEYS(R"(ysawb)", R"((abc))");
+ KEYS(R"(ysabB)", R"({(abc)})");
+ KEYS(R"(ysaB])", R"([{(abc)}])");
+ KEYS(R"(ysa]>)", R"(<[{(abc)}]>)");
+ KEYS(R"(ysa>")", R"("<[{(abc)}]>")");
+ KEYS(R"(ysa"')", R"('"<[{(abc)}]>"')");
+ KEYS(R"(ds')", R"("<[{(abc)}]>")");
+ KEYS(R"(ds")", R"(<[{(abc)}]>)");
+ KEYS(R"(ds>)", R"([{(abc)}])");
+ KEYS(R"(ds])", R"({(abc)})");
+ KEYS(R"(ds})", R"((abc))");
+ KEYS(R"(ds))", R"(abc)");
+
+ data.setText("abc d|ef ghi");
+ KEYS("ysiWb", "abc (def) ghi");
+ KEYS(".", "abc ((def)) ghi");
+ KEYS("dsb", "abc (def) ghi");
+ KEYS(".", "abc def ghi");
+ KEYS("ysaWb", "abc (def) ghi");
+ KEYS(".", "abc ((def)) ghi");
+ KEYS("dsb", "abc (def) ghi");
+ KEYS(".", "abc def ghi");
+
+ // yss
+ data.setText("\t" "abc");
+ KEYS("yssb", "\t" "(abc)");
+ KEYS(".", "\t" "((abc))");
+
+ // Surround with function
+ data.setText("abc");
+ KEYS("ysiWftest<CR>", "test(abc)");
+ KEYS(".", "test(test(abc))");
+
+ // yS puts text on a new line
+ data.setText("abc");
+ KEYS("ySsB", "{" N
+ "abc" N
+ "}");
+
+ // cs
+ data.setText("(abc)");
+ KEYS(R"(csbB)", R"({abc})");
+ KEYS(R"(csB])", R"([abc])");
+ KEYS(R"(cs]>)", R"(<abc>)");
+ KEYS(R"(cs>")", R"("abc")");
+ KEYS(R"(cs"')", R"('abc')");
+
+ // Visual line mode
+ data.setText("abc" N);
+ KEYS("VSB", "{" N
+ "abc" N
+ "}" N);
+
+ // Visual char mode
+ data.setText("abc");
+ KEYS("vlSB", "{ab}c");
+
+ // Visual block mode
+ data.setText("abc" N "def");
+ KEYS("<C-v>ljSB", "{ab}c" N "{de}f");
+}
+
void FakeVimPlugin::test_macros()
{
TestData data;
diff --git a/src/plugins/fakevim/fakevimactions.cpp b/src/plugins/fakevim/fakevimactions.cpp
index 62ab12d709..3d549c176c 100644
--- a/src/plugins/fakevim/fakevimactions.cpp
+++ b/src/plugins/fakevim/fakevimactions.cpp
@@ -120,6 +120,7 @@ FakeVimSettings::FakeVimSettings()
createAction(ConfigEmulateReplaceWithRegister, false, "ReplaceWithRegister");
createAction(ConfigEmulateExchange, false, "exchange");
createAction(ConfigEmulateArgTextObj, false, "argtextobj");
+ createAction(ConfigEmulateSurround, false, "surround");
}
FakeVimSettings::~FakeVimSettings()
diff --git a/src/plugins/fakevim/fakevimactions.h b/src/plugins/fakevim/fakevimactions.h
index ad78761ae5..419232aeb8 100644
--- a/src/plugins/fakevim/fakevimactions.h
+++ b/src/plugins/fakevim/fakevimactions.h
@@ -114,6 +114,7 @@ enum FakeVimSettingsCode
ConfigEmulateReplaceWithRegister,
ConfigEmulateExchange,
ConfigEmulateArgTextObj,
+ ConfigEmulateSurround,
ConfigBlinkingCursor
};
diff --git a/src/plugins/fakevim/fakevimhandler.cpp b/src/plugins/fakevim/fakevimhandler.cpp
index fe3fc44f1a..8e90b0ad5b 100644
--- a/src/plugins/fakevim/fakevimhandler.cpp
+++ b/src/plugins/fakevim/fakevimhandler.cpp
@@ -171,6 +171,9 @@ enum SubMode
ChangeSubMode, // Used for c
DeleteSubMode, // Used for d
ExchangeSubMode, // Used for cx
+ DeleteSurroundingSubMode, // Used for ds
+ ChangeSurroundingSubMode, // Used for cs
+ AddSurroundingSubMode, // Used for ys
FilterSubMode, // Used for !
IndentSubMode, // Used for =
RegisterSubMode, // Used for "
@@ -198,16 +201,18 @@ enum SubMode
enum SubSubMode
{
NoSubSubMode,
- FtSubSubMode, // Used for f, F, t, T.
- MarkSubSubMode, // Used for m.
- BackTickSubSubMode, // Used for `.
- TickSubSubMode, // Used for '.
- TextObjectSubSubMode, // Used for thing like iw, aW, as etc.
- ZSubSubMode, // Used for zj, zk
- OpenSquareSubSubMode, // Used for [{, {(, [z
- CloseSquareSubSubMode, // Used for ]}, ]), ]z
- SearchSubSubMode,
- CtrlVUnicodeSubSubMode // Used for Ctrl-v based unicode input
+ FtSubSubMode, // Used for f, F, t, T.
+ MarkSubSubMode, // Used for m.
+ BackTickSubSubMode, // Used for `.
+ TickSubSubMode, // Used for '.
+ TextObjectSubSubMode, // Used for thing like iw, aW, as etc.
+ ZSubSubMode, // Used for zj, zk
+ OpenSquareSubSubMode, // Used for [{, {(, [z
+ CloseSquareSubSubMode, // Used for ]}, ]), ]z
+ SearchSubSubMode, // Used for /, ?
+ SurroundSubSubMode, // Used for cs, ds, ys
+ SurroundWithFunctionSubSubMode, // Used for ys{motion}f
+ CtrlVUnicodeSubSubMode // Used for Ctrl-v based unicode input
};
enum VisualMode
@@ -1349,6 +1354,12 @@ QString dotCommandFromSubMode(SubMode submode)
return QLatin1String("d");
if (submode == CommentSubMode)
return QLatin1String("gc");
+ if (submode == DeleteSurroundingSubMode)
+ return QLatin1String("ds");
+ if (submode == ChangeSurroundingSubMode)
+ return QLatin1String("c");
+ if (submode == AddSurroundingSubMode)
+ return QLatin1String("y");
if (submode == ExchangeSubMode)
return QLatin1String("cx");
if (submode == ReplaceWithRegisterSubMode)
@@ -1836,6 +1847,8 @@ public:
bool handleCommentSubMode(const Input &);
bool handleReplaceWithRegisterSubMode(const Input &);
bool handleExchangeSubMode(const Input &);
+ bool handleDeleteChangeSurroundingSubMode(const Input &);
+ bool handleAddSurroundingSubMode(const Input &);
bool handleFilterSubMode(const Input &);
bool handleRegisterSubMode(const Input &);
bool handleShiftSubMode(const Input &);
@@ -2101,6 +2114,7 @@ public:
|| g.submode == ExchangeSubMode
|| g.submode == CommentSubMode
|| g.submode == ReplaceWithRegisterSubMode
+ || g.submode == AddSurroundingSubMode
|| g.submode == FilterSubMode
|| g.submode == IndentSubMode
|| g.submode == ShiftLeftSubMode
@@ -2183,6 +2197,8 @@ public:
void replaceWithRegister(const Range &range);
+ void surroundCurrentRange(const Input &input, const QString &prefix = {});
+
void upCase(const Range &range);
void downCase(const Range &range);
@@ -2423,6 +2439,9 @@ public:
// If empty, cx{motion} will store the range defined by {motion} here.
// If non-empty, cx{motion} replaces the {motion} with selectText(*exchangeData)
Utils::optional<Range> exchangeRange;
+
+ bool surroundUpperCaseS; // True for yS and cS, false otherwise
+ QString surroundFunction; // Used for storing the function name provided to ys{motion}f
} g;
};
@@ -3613,6 +3632,7 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommandMovement)
|| g.submode == CommentSubMode
|| g.submode == ExchangeSubMode
|| g.submode == ReplaceWithRegisterSubMode
+ || g.submode == AddSurroundingSubMode
|| g.submode == YankSubMode
|| g.submode == InvertCaseSubMode
|| g.submode == DownCaseSubMode
@@ -3644,6 +3664,15 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommandMovement)
beginEditBlock();
toggleComment(currentRange());
endEditBlock();
+ } else if (g.submode == AddSurroundingSubMode) {
+ g.subsubmode = SurroundSubSubMode;
+ g.dotCommand = dotCommandMovement;
+
+ // We now only know the region that should be surrounded, but not the actual
+ // character that should surround it. We thus do NOT want to finish the
+ // movement yet here, so we return early.
+ // The next character entered will be used by the SurroundSubSubMode.
+ return;
} else if (g.submode == ExchangeSubMode) {
exchangeRange(currentRange());
} else if (g.submode == ReplaceWithRegisterSubMode
@@ -3749,6 +3778,8 @@ void FakeVimHandler::Private::clearCurrentMode()
g.subsubmode = NoSubSubMode;
g.movetype = MoveInclusive;
g.gflag = false;
+ g.surroundUpperCaseS = false;
+ g.surroundFunction.clear();
m_register = '"';
g.rangemode = RangeCharMode;
g.currentCommand.clear();
@@ -3908,6 +3939,11 @@ bool FakeVimHandler::Private::handleCommandSubSubMode(const Input &input)
.arg(g.semicolonKey));
}
} else if (g.subsubmode == TextObjectSubSubMode) {
+ // vim-surround treats aw and aW the same as iw and iW, respectively
+ if ((input.is('w') || input.is('W'))
+ && g.submode == AddSurroundingSubMode && g.subsubdata.is('a'))
+ g.subsubdata = Input('i');
+
if (input.is('w'))
selectWordTextObject(g.subsubdata.is('i'));
else if (input.is('W'))
@@ -3987,6 +4023,37 @@ bool FakeVimHandler::Private::handleCommandSubSubMode(const Input &input)
.arg(g.subsubmode == OpenSquareSubSubMode ? '[' : ']')
.arg(input.text()));
}
+ } else if (g.subsubmode == SurroundWithFunctionSubSubMode) {
+ if (input.isReturn()) {
+ pushUndoState(false);
+ beginEditBlock();
+
+ const QString dotCommand = "ys" + g.dotCommand + "f" + g.surroundFunction + "<CR>";
+
+ surroundCurrentRange(Input(')'), g.surroundFunction);
+
+ g.dotCommand = dotCommand;
+
+ endEditBlock();
+ leaveCurrentMode();
+ } else {
+ g.surroundFunction += input.asChar();
+ }
+ return true;
+ } else if (g.subsubmode == SurroundSubSubMode) {
+ if (input.is('f') && g.submode == AddSurroundingSubMode) {
+ g.subsubmode = SurroundWithFunctionSubSubMode;
+ g.commandBuffer.setContents("");
+ return true;
+ }
+
+ pushUndoState(false);
+ beginEditBlock();
+
+ surroundCurrentRange(input);
+
+ endEditBlock();
+ leaveCurrentMode();
} else {
handled = false;
}
@@ -4306,6 +4373,25 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input)
// Exchange submode is "cx", so we need to switch over from ChangeSubMode here
g.submode = ExchangeSubMode;
handled = true;
+ } else if (g.submode == DeleteSurroundingSubMode
+ || g.submode == ChangeSurroundingSubMode) {
+ handled = handleDeleteChangeSurroundingSubMode(input);
+ } else if (g.submode == AddSurroundingSubMode) {
+ handled = handleAddSurroundingSubMode(input);
+ } else if (g.submode == ChangeSubMode && (input.is('s') || input.is('S'))
+ && hasConfig(ConfigEmulateSurround)) {
+ g.submode = ChangeSurroundingSubMode;
+ g.surroundUpperCaseS = input.is('S');
+ handled = true;
+ } else if (g.submode == DeleteSubMode && input.is('s') && hasConfig(ConfigEmulateSurround)) {
+ g.submode = DeleteSurroundingSubMode;
+ handled = true;
+ } else if (g.submode == YankSubMode && (input.is('s') || input.is('S'))
+ && hasConfig(ConfigEmulateSurround)) {
+ g.submode = AddSurroundingSubMode;
+ g.movetype = MoveInclusive;
+ g.surroundUpperCaseS = input.is('S');
+ handled = true;
} else if (g.submode == ChangeSubMode
|| g.submode == DeleteSubMode
|| g.submode == YankSubMode) {
@@ -4640,6 +4726,9 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input)
int repeat = count();
while (--repeat >= 0)
redo();
+ } else if (input.is('S') && isVisualMode() && hasConfig(ConfigEmulateSurround)) {
+ g.submode = AddSurroundingSubMode;
+ g.subsubmode = SurroundSubSubMode;
} else if (input.is('s')) {
handleAs("c%1l");
} else if (input.is('S')) {
@@ -4891,6 +4980,69 @@ bool FakeVimHandler::Private::handleExchangeSubMode(const Input &input)
return false;
}
+bool FakeVimHandler::Private::handleDeleteChangeSurroundingSubMode(const Input &input)
+{
+ if (g.submode != ChangeSurroundingSubMode && g.submode != DeleteSurroundingSubMode)
+ return false;
+
+ bool handled = false;
+
+ if (input.is('(') || input.is(')') || input.is('b')) {
+ handled = selectBlockTextObject(false, '(', ')');
+ } else if (input.is('{') || input.is('}') || input.is('B')) {
+ handled = selectBlockTextObject(false, '{', '}');
+ } else if (input.is('[') || input.is(']')) {
+ handled = selectBlockTextObject(false, '[', ']');
+ } else if (input.is('<') || input.is('>') || input.is('t')) {
+ handled = selectBlockTextObject(false, '<', '>');
+ } else if (input.is('"') || input.is('\'') || input.is('`')) {
+ handled = selectQuotedStringTextObject(false, input.asChar());
+ }
+
+ if (handled) {
+ if (g.submode == DeleteSurroundingSubMode) {
+ pushUndoState(false);
+ beginEditBlock();
+
+ // Surround is always one character, so just delete the first and last one
+ transformText(currentRange(), [](const QString &text) {
+ return text.mid(1, text.size() - 2);
+ });
+
+ endEditBlock();
+ clearCurrentMode();
+
+ g.dotCommand = "ds" + input.asChar();
+ } else if (g.submode == ChangeSurroundingSubMode) {
+ g.subsubmode = SurroundSubSubMode;
+ }
+ }
+
+ return handled;
+}
+
+bool FakeVimHandler::Private::handleAddSurroundingSubMode(const Input &input)
+{
+ if (!input.is('s'))
+ return false;
+
+ g.subsubmode = SurroundSubSubMode;
+
+ int anc = firstPositionInLine(cursorLine() + 1);
+ const int pos = lastPositionInLine(cursorLine() + 1);
+
+ // Ignore leading spaces
+ while ((characterAt(anc) == " " || characterAt(anc) == "\t") && anc != pos) {
+ anc++;
+ }
+
+ setAnchorAndPosition(anc, pos);
+
+ finishMovement("s");
+
+ return true;
+}
+
bool FakeVimHandler::Private::handleFilterSubMode(const Input &)
{
return false;
@@ -7564,6 +7716,77 @@ void FakeVimHandler::Private::replaceWithRegister(const Range &range)
replaceText(range, registerContents(m_register));
}
+void FakeVimHandler::Private::surroundCurrentRange(const Input &input, const QString &prefix)
+{
+ QString dotCommand;
+ if (isVisualMode())
+ dotCommand = visualDotCommand() + "S" + input.asChar();
+
+ const bool wasVisualCharMode = isVisualCharMode();
+ const bool wasVisualLineMode = isVisualLineMode();
+ leaveVisualMode();
+
+ if (dotCommand.isEmpty()) { // i.e. we came from normal mode
+ dotCommand = dotCommandFromSubMode(g.submode) + (g.surroundUpperCaseS ? "S" : "s")
+ + g.dotCommand + input.asChar();
+ }
+
+ if (wasVisualCharMode)
+ setPosition(position() + 1);
+
+ QString newFront, newBack;
+
+ if (input.is('(') || input.is(')') || input.is('b')) {
+ newFront = '(';
+ newBack = ')';
+ } else if (input.is('{') || input.is('}') || input.is('B')) {
+ newFront = '{';
+ newBack = '}';
+ } else if (input.is('[') || input.is(']')) {
+ newFront = '[';
+ newBack = ']';
+ } else if (input.is('<') || input.is('>') || input.is('t')) {
+ newFront = '<';
+ newBack = '>';
+ } else if (input.is('"') || input.is('\'') || input.is('`')) {
+ newFront = input.asChar();
+ newBack = input.asChar();
+ }
+
+ if (g.surroundUpperCaseS || wasVisualLineMode) {
+ // yS and cS add a new line before and after the surrounded text
+ newFront += "\n";
+ if (wasVisualLineMode)
+ newBack += "\n";
+ else
+ newBack = "\n" + newBack;
+ } else if (input.is('(') || input.is('{') || input.is('[') || input.is('[')) {
+ // Opening characters add an extra space
+ newFront = newFront + " ";
+ newBack = " " + newBack;
+ }
+
+
+ if (!newFront.isEmpty()) {
+ transformText(currentRange(), [&](QString text) -> QString {
+ if (newFront == QChar())
+ return text.mid(1, text.size() - 2);
+
+ const QString newMiddle = (g.submode == ChangeSurroundingSubMode) ?
+ text.mid(1, text.size() - 2) : text;
+
+ return prefix + newFront + newMiddle + newBack;
+ });
+ }
+
+ // yS, cS and VS also indent the surrounded text
+ if (g.surroundUpperCaseS || wasVisualLineMode)
+ replay("=a" + input.asChar());
+
+ // Indenting has changed the dotCommand, so now set it back to the correct one
+ g.dotCommand = dotCommand;
+}
+
void FakeVimHandler::Private::replaceText(const Range &range, const QString &str)
{
transformText(range, [&str](const QString &) { return str; } );
diff --git a/src/plugins/fakevim/fakevimoptions.ui b/src/plugins/fakevim/fakevimoptions.ui
index 7bbdb72e61..b9f7a1845f 100644
--- a/src/plugins/fakevim/fakevimoptions.ui
+++ b/src/plugins/fakevim/fakevimoptions.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>580</width>
- <height>543</height>
+ <height>568</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
@@ -162,6 +162,20 @@
<string>Plugin Emulation</string>
</property>
<layout class="QGridLayout" name="gridLayout">
+ <item row="3" column="0">
+ <widget class="QCheckBox" name="checkBoxExchange">
+ <property name="text">
+ <string>vim-exchange</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="checkBoxArgTextObj">
+ <property name="text">
+ <string>argtextobj.vim</string>
+ </property>
+ </widget>
+ </item>
<item row="1" column="0">
<widget class="QCheckBox" name="checkBoxReplaceWithRegister">
<property name="text">
@@ -179,17 +193,10 @@
</property>
</widget>
</item>
- <item row="2" column="0">
- <widget class="QCheckBox" name="checkBoxArgTextObj">
+ <item row="4" column="0">
+ <widget class="QCheckBox" name="checkBoxVimSurround">
<property name="text">
- <string>argtextobj.vim</string>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QCheckBox" name="checkBoxExchange">
- <property name="text">
- <string>vim-exchange</string>
+ <string>vim-surround</string>
</property>
</widget>
</item>
diff --git a/src/plugins/fakevim/fakevimplugin.cpp b/src/plugins/fakevim/fakevimplugin.cpp
index 4712b6da69..c14927769a 100644
--- a/src/plugins/fakevim/fakevimplugin.cpp
+++ b/src/plugins/fakevim/fakevimplugin.cpp
@@ -433,6 +433,7 @@ QWidget *FakeVimOptionPage::widget()
m_group.insert(theFakeVimSetting(ConfigEmulateReplaceWithRegister), m_ui.checkBoxReplaceWithRegister);
m_group.insert(theFakeVimSetting(ConfigEmulateExchange), m_ui.checkBoxExchange);
m_group.insert(theFakeVimSetting(ConfigEmulateArgTextObj), m_ui.checkBoxArgTextObj);
+ m_group.insert(theFakeVimSetting(ConfigEmulateSurround), m_ui.checkBoxVimSurround);
connect(m_ui.pushButtonCopyTextEditorSettings, &QAbstractButton::clicked,
this, &FakeVimOptionPage::copyTextEditorSettings);
diff --git a/src/plugins/fakevim/fakevimplugin.h b/src/plugins/fakevim/fakevimplugin.h
index 84414c20e0..5539fa79eb 100644
--- a/src/plugins/fakevim/fakevimplugin.h
+++ b/src/plugins/fakevim/fakevimplugin.h
@@ -163,6 +163,7 @@ private slots:
void test_vim_replace_with_register_emulation();
void test_vim_exchange_emulation();
void test_vim_arg_text_obj_emulation();
+ void test_vim_surround_emulation();
void test_macros();