forked from qt-creator/qt-creator
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>
This commit is contained in:
@@ -2889,3 +2889,40 @@ void FakeVimPlugin::test_vim_Visual_d()
|
|||||||
KEYS("Vkx", '|' + lmid(4));
|
KEYS("Vkx", '|' + lmid(4));
|
||||||
KEYS("P", '|' + lmid(0,1)+'\n' + lmid(3));
|
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");
|
||||||
|
}
|
||||||
|
|||||||
@@ -178,7 +178,9 @@ enum SubMode
|
|||||||
YankSubMode, // Used for y
|
YankSubMode, // Used for y
|
||||||
ZSubMode, // Used for z
|
ZSubMode, // Used for z
|
||||||
CapitalZSubMode, // 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
|
/*! A \e SubSubMode is used for things that require one more data item
|
||||||
@@ -937,13 +939,16 @@ public:
|
|||||||
|
|
||||||
int key() const { return m_key; }
|
int key() const { return m_key; }
|
||||||
|
|
||||||
|
// Return raw character for macro recording or dot command.
|
||||||
QChar raw() const
|
QChar raw() const
|
||||||
{
|
{
|
||||||
if (m_key == Key_Tab)
|
if (m_key == Key_Tab)
|
||||||
return QLatin1Char('\t');
|
return QLatin1Char('\t');
|
||||||
if (m_key == Key_Return)
|
if (m_key == Key_Return)
|
||||||
return QLatin1Char('\n');
|
return QLatin1Char('\n');
|
||||||
return m_key;
|
if (m_key == Key_Escape)
|
||||||
|
return QChar(27);
|
||||||
|
return m_xkey;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString toString() const
|
QString toString() const
|
||||||
@@ -1438,6 +1443,8 @@ public:
|
|||||||
bool handleYankSubMode(const Input &);
|
bool handleYankSubMode(const Input &);
|
||||||
bool handleZSubMode(const Input &);
|
bool handleZSubMode(const Input &);
|
||||||
bool handleCapitalZSubMode(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 handleCount(const Input &); // Handle count for commands (return false if input isn't count).
|
||||||
bool handleMovement(const Input &);
|
bool handleMovement(const Input &);
|
||||||
@@ -1659,6 +1666,12 @@ public:
|
|||||||
void ensureCursorVisible();
|
void ensureCursorVisible();
|
||||||
void insertInInsertMode(const QString &text);
|
void insertInInsertMode(const QString &text);
|
||||||
|
|
||||||
|
// Macro recording
|
||||||
|
bool startRecording(const Input &input);
|
||||||
|
void record(const Input &input);
|
||||||
|
void stopRecording();
|
||||||
|
bool executeRegister(int register);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QTextEdit *m_textedit;
|
QTextEdit *m_textedit;
|
||||||
QPlainTextEdit *m_plaintextedit;
|
QPlainTextEdit *m_plaintextedit;
|
||||||
@@ -1848,7 +1861,7 @@ public:
|
|||||||
GlobalData()
|
GlobalData()
|
||||||
: mappings(), currentMap(&mappings), inputTimer(-1), mapDepth(0),
|
: mappings(), currentMap(&mappings), inputTimer(-1), mapDepth(0),
|
||||||
currentMessageLevel(MessageInfo), lastSearchForward(false), findPending(false),
|
currentMessageLevel(MessageInfo), lastSearchForward(false), findPending(false),
|
||||||
returnToMode(CommandMode)
|
returnToMode(CommandMode), currentRegister(0), lastExecutedRegister(0)
|
||||||
{
|
{
|
||||||
commandBuffer.setPrompt(QLatin1Char(':'));
|
commandBuffer.setPrompt(QLatin1Char(':'));
|
||||||
}
|
}
|
||||||
@@ -1892,6 +1905,11 @@ public:
|
|||||||
|
|
||||||
// Return to insert/replace mode after single command (<C-O>).
|
// Return to insert/replace mode after single command (<C-O>).
|
||||||
Mode returnToMode;
|
Mode returnToMode;
|
||||||
|
|
||||||
|
// Currently recorded macro (not recording if null string).
|
||||||
|
QString recording;
|
||||||
|
int currentRegister;
|
||||||
|
int lastExecutedRegister;
|
||||||
} g;
|
} g;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2316,8 +2334,10 @@ EventResult FakeVimHandler::Private::handleKey(const Input &input)
|
|||||||
// Waiting on input to complete mapping?
|
// Waiting on input to complete mapping?
|
||||||
EventResult r = stopWaitForMapping(hasInput);
|
EventResult r = stopWaitForMapping(hasInput);
|
||||||
|
|
||||||
if (hasInput)
|
if (hasInput) {
|
||||||
|
record(input);
|
||||||
g.pendingInput.append(input);
|
g.pendingInput.append(input);
|
||||||
|
}
|
||||||
|
|
||||||
// Process pending input.
|
// Process pending input.
|
||||||
// Note: Pending input is global state and can be extended by:
|
// Note: Pending input is global state and can be extended by:
|
||||||
@@ -2973,25 +2993,28 @@ void FakeVimHandler::Private::updateMiniBuffer()
|
|||||||
messageLevel = MessageShowCmd;
|
messageLevel = MessageShowCmd;
|
||||||
} else if (m_mode == CommandMode && isVisualMode()) {
|
} else if (m_mode == CommandMode && isVisualMode()) {
|
||||||
if (isVisualCharMode())
|
if (isVisualCharMode())
|
||||||
msg = _("VISUAL");
|
msg = _("-- VISUAL --");
|
||||||
else if (isVisualLineMode())
|
else if (isVisualLineMode())
|
||||||
msg = _("VISUAL LINE");
|
msg = _("-- VISUAL LINE --");
|
||||||
else if (isVisualBlockMode())
|
else if (isVisualBlockMode())
|
||||||
msg = _("VISUAL BLOCK");
|
msg = _("VISUAL BLOCK");
|
||||||
} else if (m_mode == InsertMode) {
|
} else if (m_mode == InsertMode) {
|
||||||
msg = _("INSERT");
|
msg = _("-- INSERT --");
|
||||||
} else if (m_mode == ReplaceMode) {
|
} else if (m_mode == ReplaceMode) {
|
||||||
msg = _("REPLACE");
|
msg = _("-- REPLACE --");
|
||||||
} else {
|
} else {
|
||||||
QTC_CHECK(m_mode == CommandMode && m_subsubmode != SearchSubSubMode);
|
QTC_CHECK(m_mode == CommandMode && m_subsubmode != SearchSubSubMode);
|
||||||
if (g.returnToMode == CommandMode)
|
if (g.returnToMode == CommandMode)
|
||||||
msg = _("COMMAND");
|
msg = _("-- COMMAND --");
|
||||||
else if (g.returnToMode == InsertMode)
|
else if (g.returnToMode == InsertMode)
|
||||||
msg = _("(insert)");
|
msg = _("-- (insert) --");
|
||||||
else
|
else
|
||||||
msg = _("(replace)");
|
msg = _("-- (replace) --");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!g.recording.isNull() && msg.startsWith(_("--")))
|
||||||
|
msg.append(_("recording"));
|
||||||
|
|
||||||
emit q->commandBufferChanged(msg, cursorPos, anchorPos, messageLevel, q);
|
emit q->commandBufferChanged(msg, cursorPos, anchorPos, messageLevel, q);
|
||||||
|
|
||||||
int linesInDoc = linesInDocument();
|
int linesInDoc = linesInDocument();
|
||||||
@@ -3467,6 +3490,10 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input)
|
|||||||
handled = handleZSubMode(input);
|
handled = handleZSubMode(input);
|
||||||
} else if (m_submode == CapitalZSubMode) {
|
} else if (m_submode == CapitalZSubMode) {
|
||||||
handled = handleCapitalZSubMode(input);
|
handled = handleCapitalZSubMode(input);
|
||||||
|
} else if (m_submode == MacroRecordSubMode) {
|
||||||
|
handled = handleMacroRecordSubMode(input);
|
||||||
|
} else if (m_submode == MacroExecuteSubMode) {
|
||||||
|
handled = handleMacroExecuteSubMode(input);
|
||||||
} else if (m_submode == ShiftLeftSubMode
|
} else if (m_submode == ShiftLeftSubMode
|
||||||
|| m_submode == ShiftRightSubMode
|
|| m_submode == ShiftRightSubMode
|
||||||
|| m_submode == IndentSubMode) {
|
|| m_submode == IndentSubMode) {
|
||||||
@@ -3756,6 +3783,16 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input)
|
|||||||
setTargetColumn();
|
setTargetColumn();
|
||||||
setDotCommand(_("%1p"), count());
|
setDotCommand(_("%1p"), count());
|
||||||
finishMovement();
|
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')) {
|
} else if (input.is('r')) {
|
||||||
m_submode = ReplaceSubMode;
|
m_submode = ReplaceSubMode;
|
||||||
} else if (!isVisualMode() && input.is('R')) {
|
} 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()));
|
setDotCommand(QString::fromLatin1("%1%2").arg(count()).arg(input.raw()));
|
||||||
endEditBlock();
|
endEditBlock();
|
||||||
}
|
}
|
||||||
|
} else if (input.is('@')) {
|
||||||
|
m_submode = MacroExecuteSubMode;
|
||||||
} else if (input.isKey(Key_Delete)) {
|
} else if (input.isKey(Key_Delete)) {
|
||||||
setAnchor();
|
setAnchor();
|
||||||
moveRight(qMin(1, rightDist()));
|
moveRight(qMin(1, rightDist()));
|
||||||
@@ -4161,6 +4200,24 @@ bool FakeVimHandler::Private::handleCapitalZSubMode(const Input &input)
|
|||||||
return handled;
|
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)
|
EventResult FakeVimHandler::Private::handleReplaceMode(const Input &input)
|
||||||
{
|
{
|
||||||
bool clearLastInsertion = m_breakEditBlock;
|
bool clearLastInsertion = m_breakEditBlock;
|
||||||
@@ -4466,6 +4523,54 @@ void FakeVimHandler::Private::insertInInsertMode(const QString &text)
|
|||||||
m_ctrlVActive = false;
|
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)
|
EventResult FakeVimHandler::Private::handleExMode(const Input &input)
|
||||||
{
|
{
|
||||||
if (input.isEscape()) {
|
if (input.isEscape()) {
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ public:
|
|||||||
hide();
|
hide();
|
||||||
} else {
|
} else {
|
||||||
show();
|
show();
|
||||||
m_label->setText(messageLevel == MessageMode ? _("-- ") + contents + _(" --") : contents);
|
m_label->setText(contents);
|
||||||
|
|
||||||
QString css;
|
QString css;
|
||||||
if (messageLevel == MessageError) {
|
if (messageLevel == MessageError) {
|
||||||
|
|||||||
@@ -140,6 +140,8 @@ private slots:
|
|||||||
void test_vim_visual_d();
|
void test_vim_visual_d();
|
||||||
void test_vim_Visual_d();
|
void test_vim_Visual_d();
|
||||||
|
|
||||||
|
void test_macros();
|
||||||
|
|
||||||
// special tests
|
// special tests
|
||||||
void test_i_cw_i();
|
void test_i_cw_i();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user