aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/autocmd.txt8
-rw-r--r--runtime/doc/eval.txt6
-rw-r--r--runtime/doc/intro.txt4
-rw-r--r--runtime/doc/repeat.txt3
-rw-r--r--runtime/doc/vim_diff.txt1
-rw-r--r--src/nvim/auevents.lua4
-rw-r--r--src/nvim/eval.lua1
-rw-r--r--src/nvim/eval/funcs.c5
-rw-r--r--src/nvim/globals.h1
-rw-r--r--src/nvim/normal.c21
-rw-r--r--src/nvim/ops.c21
-rw-r--r--test/functional/autocmd/recording_spec.lua52
-rw-r--r--test/functional/editor/macro_spec.lua27
13 files changed, 132 insertions, 22 deletions
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
index 242631d98c..3cdf979e54 100644
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -828,6 +828,14 @@ RemoteReply When a reply from a Vim that functions as
SearchWrapped After making a search with |n| or |N| if the
search wraps around the document back to
the start/finish respectively.
+ *RecordingEnter*
+RecordingEnter When a macro starts to be recorded.
+ The pattern is the current file name, and
+ |reg_recording()| is the current register that
+ is used.
+ *RecordinLeave*
+RecordingLeave When the is the end of a macro recording.
+ The pattern is the current file name.
*SessionLoadPost*
SessionLoadPost After loading the session file created using
the |:mksession| command.
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 75b782fbff..0e002179e3 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -2588,6 +2588,7 @@ readdir({dir} [, {expr}]) List file names in {dir} selected by {expr}
readfile({fname} [, {type} [, {max}]])
List get list of lines from file {fname}
reg_executing() String get the executing register name
+reg_recorded() String get the last recorded register name
reg_recording() String get the recording register name
reltime([{start} [, {end}]]) List get time value
reltimefloat({time}) Float turn the time value into a Float
@@ -7825,6 +7826,11 @@ reg_executing() *reg_executing()*
Returns an empty string when no register is being executed.
See |@|.
+reg_recorded() *reg_recorded()*
+ Returns the single letter name of the last recorded register.
+ Returns an empty string string when nothing was recorded yet.
+ See |q|.
+
reg_recording() *reg_recording()*
Returns the single letter name of the register being recorded.
Returns an empty string string when not recording. See |q|.
diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt
index a89263861b..0e0156ac6b 100644
--- a/runtime/doc/intro.txt
+++ b/runtime/doc/intro.txt
@@ -563,8 +563,8 @@ The command CTRL-\ CTRL-G or <C-\><C-G> can be used to go to Insert mode when
make sure Vim is in the mode indicated by 'insertmode', without knowing in
what mode Vim currently is.
- *gQ* *Q* *mode-Ex* *Ex-mode* *Ex* *EX* *E501*
-Q or gQ Switch to Ex mode. This is like typing ":" commands
+ *gQ* *mode-Ex* *Ex-mode* *Ex* *EX* *E501*
+gQ Switch to Ex mode. This is like typing ":" commands
one after another, except:
- You don't have to keep pressing ":".
- The screen doesn't get updated after each command.
diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt
index 7e8d93aa71..60e9b6bed4 100644
--- a/runtime/doc/repeat.txt
+++ b/runtime/doc/repeat.txt
@@ -147,6 +147,9 @@ q Stops recording.
*@@* *E748*
@@ Repeat the previous @{0-9a-z":*} [count] times.
+ *Q*
+Q Repeat the last recorded register [count] times.
+
*:@*
:[addr]@{0-9a-z".=*+} Execute the contents of register {0-9a-z".=*+} as an Ex
command. First set cursor at line [addr] (default is
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index bc59ea785e..154b18add7 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -218,6 +218,7 @@ Input/Mappings:
Normal commands:
|gO| shows a filetype-defined "outline" of the current buffer.
+ |Q| replays the last recorded macro.
Options:
'cpoptions' flags: |cpo-_|
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index 6227b08b26..761e385a73 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -75,6 +75,8 @@ return {
'QuickFixCmdPost', -- after :make, :grep etc.
'QuickFixCmdPre', -- before :make, :grep etc.
'QuitPre', -- before :quit
+ 'RecordingEnter', -- when starting to record a macro
+ 'RecordingLeave', -- when stopping to record a macro
'RemoteReply', -- upon string reception from a remote vim
'SearchWrapped', -- after the search wrapped around
'SessionLoadPost', -- after loading a session file
@@ -131,6 +133,8 @@ return {
BufModifiedSet=true,
DiagnosticChanged=true,
DirChanged=true,
+ RecordingEnter=true,
+ RecordingLeave=true,
Signal=true,
TabClosed=true,
TabNew=true,
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 9a76b67de6..9507a12a02 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -277,6 +277,7 @@ return {
readfile={args={1, 3}, base=1},
reg_executing={},
reg_recording={},
+ reg_recorded={},
reltime={args={0, 2}, base=1},
reltimefloat={args=1, base=1},
reltimestr={args=1, base=1},
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 5252c940f7..d43eeb4a15 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -7398,6 +7398,11 @@ static void f_reg_recording(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return_register(reg_recording, rettv);
}
+static void f_reg_recorded(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ return_register(reg_recorded, rettv);
+}
+
/// list2proftime - convert a List to proftime_T
///
/// @param arg The input list, must be of type VAR_LIST and have
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index dfbc80066e..697d4b11a7 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -632,6 +632,7 @@ EXTERN bool ex_no_reprint INIT(=false); // No need to print after z or p.
EXTERN int reg_recording INIT(= 0); // register for recording or zero
EXTERN int reg_executing INIT(= 0); // register being executed or zero
+EXTERN int reg_recorded INIT(= 0); // last recorded register or zero
EXTERN int no_mapping INIT(= false); // currently no mapping allowed
EXTERN int no_zero_mapping INIT(= 0); // mapping zero not allowed
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 95a521c0d8..3246596f16 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -229,7 +229,7 @@ static const struct nv_cmd {
{ 'N', nv_next, 0, SEARCH_REV },
{ 'O', nv_open, 0, 0 },
{ 'P', nv_put, 0, 0 },
- { 'Q', nv_exmode, NV_NCW, 0 },
+ { 'Q', nv_regreplay, 0, 0 },
{ 'R', nv_Replace, 0, false },
{ 'S', nv_subst, NV_KEEPREG, 0 },
{ 'T', nv_csearch, NV_NCH_ALW|NV_LANG, BACKWARD },
@@ -4028,15 +4028,18 @@ dozet:
/*
* "Q" command.
*/
-static void nv_exmode(cmdarg_T *cap)
+static void nv_regreplay(cmdarg_T *cap)
{
- /*
- * Ignore 'Q' in Visual mode, just give a beep.
- */
- if (VIsual_active) {
- vim_beep(BO_EX);
- } else if (!checkclearop(cap->oap)) {
- do_exmode();
+ if (checkclearop(cap->oap)) {
+ return;
+ }
+
+ while (cap->count1-- && !got_int) {
+ if (do_execreg(reg_recorded, false, false, false) == false) {
+ clearopbeep(cap->oap);
+ break;
+ }
+ line_breakcheck();
}
}
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 9bc63477e9..c6f9c5f04f 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -912,13 +912,14 @@ int do_record(int c)
showmode();
regname = c;
retval = OK;
- }
- } else { // stop recording
- /*
- * Get the recorded key hits. K_SPECIAL and CSI will be escaped, this
- * needs to be removed again to put it in a register. exec_reg then
- * adds the escaping back later.
- */
+ apply_autocmds(EVENT_RECORDINGENTER, NULL, NULL, false, curbuf);
+ }
+ } else { // stop recording
+ // Get the recorded key hits. K_SPECIAL and CSI will be escaped, this
+ // needs to be removed again to put it in a register. exec_reg then
+ // adds the escaping back later.
+ apply_autocmds(EVENT_RECORDINGLEAVE, NULL, NULL, false, curbuf);
+ reg_recorded = reg_recording;
reg_recording = 0;
if (ui_has(kUIMessages)) {
showmode();
@@ -932,10 +933,8 @@ int do_record(int c)
// Remove escaping for CSI and K_SPECIAL in multi-byte chars.
vim_unescape_csi(p);
- /*
- * We don't want to change the default register here, so save and
- * restore the current register name.
- */
+ // We don't want to change the default register here, so save and
+ // restore the current register name.
old_y_previous = y_previous;
retval = stuff_yank(regname, p);
diff --git a/test/functional/autocmd/recording_spec.lua b/test/functional/autocmd/recording_spec.lua
new file mode 100644
index 0000000000..81152758de
--- /dev/null
+++ b/test/functional/autocmd/recording_spec.lua
@@ -0,0 +1,52 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local clear = helpers.clear
+local eq = helpers.eq
+local eval = helpers.eval
+local source_vim = helpers.source
+
+describe('RecordingEnter', function()
+ before_each(clear)
+ it('works', function()
+ source_vim [[
+ let g:recorded = 0
+ autocmd RecordingEnter * let g:recorded += 1
+ execute "normal! qqyyq"
+ ]]
+ eq(1, eval('g:recorded'))
+ end)
+
+ it('gives a correct reg_recording()', function()
+ source_vim [[
+ let g:recording = ''
+ autocmd RecordingEnter * let g:recording = reg_recording()
+ execute "normal! qqyyq"
+ ]]
+ eq('q', eval('g:recording'))
+ end)
+end)
+
+describe('RecordingLeave', function()
+ before_each(clear)
+ it('works', function()
+ source_vim [[
+ let g:recorded = 0
+ autocmd RecordingLeave * let g:recorded += 1
+ execute "normal! qqyyq"
+ ]]
+ eq(1, eval('g:recorded'))
+ end)
+
+ it('gives the correct reg_recorded()', function()
+ source_vim [[
+ let g:recorded = 'a'
+ let g:recording = ''
+ autocmd RecordingLeave * let g:recording = reg_recording()
+ autocmd RecordingLeave * let g:recorded = reg_recorded()
+ execute "normal! qqyyq"
+ ]]
+ eq('q', eval 'g:recording')
+ eq('', eval 'g:recorded')
+ eq('q', eval 'reg_recorded()')
+ end)
+end)
diff --git a/test/functional/editor/macro_spec.lua b/test/functional/editor/macro_spec.lua
index 102d8fc723..c0c9256af2 100644
--- a/test/functional/editor/macro_spec.lua
+++ b/test/functional/editor/macro_spec.lua
@@ -6,6 +6,8 @@ local feed = helpers.feed
local clear = helpers.clear
local expect = helpers.expect
local command = helpers.command
+local insert = helpers.insert
+local curbufmeths = helpers.curbufmeths
describe('macros', function()
before_each(clear)
@@ -27,4 +29,29 @@ describe('macros', function()
expect('llllll')
eq(eval('@i'), 'lxxx')
end)
+
+ it('can be replayed with Q', function()
+ insert [[hello
+hello
+hello]]
+ feed [[gg]]
+
+ feed [[qqAFOO<esc>q]]
+ eq({'helloFOO', 'hello', 'hello'}, curbufmeths.get_lines(0, -1, false))
+
+ feed[[Q]]
+ eq({'helloFOOFOO', 'hello', 'hello'}, curbufmeths.get_lines(0, -1, false))
+
+ feed[[G3Q]]
+ eq({'helloFOOFOO', 'hello', 'helloFOOFOOFOO'}, curbufmeths.get_lines(0, -1, false))
+ end)
+end)
+
+describe('reg_recorded()', function()
+ before_each(clear)
+
+ it('returns the correct value', function()
+ feed [[qqyyq]]
+ eq('q', eval('reg_recorded()'))
+ end)
end)