feat(autocmd): add Recording autocmds

feat(eval): add reg_recorded()

This function is used the get the last recorded register.

style(Recording): rename handler to match suggestions

fix(RecordingLeave): send autocommand earlier

This makes the autocommand fire just before setting reg_recorded to
reg_recording, this way we clearly show that we are actually just before
actually quitting the recording mode.
This commit is contained in:
Thomas Vigouroux 2021-08-17 15:56:37 +02:00 committed by Axel Dahlberg
parent b1757e1c29
commit 8a4e26c6fe
13 changed files with 131 additions and 21 deletions

View File

@ -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 SearchWrapped After making a search with |n| or |N| if the
search wraps around the document back to search wraps around the document back to
the start/finish respectively. 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*
SessionLoadPost After loading the session file created using SessionLoadPost After loading the session file created using
the |:mksession| command. the |:mksession| command.

View File

@ -2588,6 +2588,7 @@ readdir({dir} [, {expr}]) List file names in {dir} selected by {expr}
readfile({fname} [, {type} [, {max}]]) readfile({fname} [, {type} [, {max}]])
List get list of lines from file {fname} List get list of lines from file {fname}
reg_executing() String get the executing register name 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 reg_recording() String get the recording register name
reltime([{start} [, {end}]]) List get time value reltime([{start} [, {end}]]) List get time value
reltimefloat({time}) Float turn the time value into a Float 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. Returns an empty string when no register is being executed.
See |@|. 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()* reg_recording() *reg_recording()*
Returns the single letter name of the register being recorded. Returns the single letter name of the register being recorded.
Returns an empty string string when not recording. See |q|. Returns an empty string string when not recording. See |q|.

View File

@ -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 make sure Vim is in the mode indicated by 'insertmode', without knowing in
what mode Vim currently is. what mode Vim currently is.
*gQ* *Q* *mode-Ex* *Ex-mode* *Ex* *EX* *E501* *gQ* *mode-Ex* *Ex-mode* *Ex* *EX* *E501*
Q or gQ Switch to Ex mode. This is like typing ":" commands gQ Switch to Ex mode. This is like typing ":" commands
one after another, except: one after another, except:
- You don't have to keep pressing ":". - You don't have to keep pressing ":".
- The screen doesn't get updated after each command. - The screen doesn't get updated after each command.

View File

@ -147,6 +147,9 @@ q Stops recording.
*@@* *E748* *@@* *E748*
@@ Repeat the previous @{0-9a-z":*} [count] times. @@ 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 :[addr]@{0-9a-z".=*+} Execute the contents of register {0-9a-z".=*+} as an Ex
command. First set cursor at line [addr] (default is command. First set cursor at line [addr] (default is

View File

@ -218,6 +218,7 @@ Input/Mappings:
Normal commands: Normal commands:
|gO| shows a filetype-defined "outline" of the current buffer. |gO| shows a filetype-defined "outline" of the current buffer.
|Q| replays the last recorded macro.
Options: Options:
'cpoptions' flags: |cpo-_| 'cpoptions' flags: |cpo-_|

View File

@ -75,6 +75,8 @@ return {
'QuickFixCmdPost', -- after :make, :grep etc. 'QuickFixCmdPost', -- after :make, :grep etc.
'QuickFixCmdPre', -- before :make, :grep etc. 'QuickFixCmdPre', -- before :make, :grep etc.
'QuitPre', -- before :quit '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 'RemoteReply', -- upon string reception from a remote vim
'SearchWrapped', -- after the search wrapped around 'SearchWrapped', -- after the search wrapped around
'SessionLoadPost', -- after loading a session file 'SessionLoadPost', -- after loading a session file
@ -131,6 +133,8 @@ return {
BufModifiedSet=true, BufModifiedSet=true,
DiagnosticChanged=true, DiagnosticChanged=true,
DirChanged=true, DirChanged=true,
RecordingEnter=true,
RecordingLeave=true,
Signal=true, Signal=true,
TabClosed=true, TabClosed=true,
TabNew=true, TabNew=true,

View File

@ -277,6 +277,7 @@ return {
readfile={args={1, 3}, base=1}, readfile={args={1, 3}, base=1},
reg_executing={}, reg_executing={},
reg_recording={}, reg_recording={},
reg_recorded={},
reltime={args={0, 2}, base=1}, reltime={args={0, 2}, base=1},
reltimefloat={args=1, base=1}, reltimefloat={args=1, base=1},
reltimestr={args=1, base=1}, reltimestr={args=1, base=1},

View File

@ -7398,6 +7398,11 @@ static void f_reg_recording(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return_register(reg_recording, rettv); 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 /// list2proftime - convert a List to proftime_T
/// ///
/// @param arg The input list, must be of type VAR_LIST and have /// @param arg The input list, must be of type VAR_LIST and have

View File

@ -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_recording INIT(= 0); // register for recording or zero
EXTERN int reg_executing INIT(= 0); // register being executed 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_mapping INIT(= false); // currently no mapping allowed
EXTERN int no_zero_mapping INIT(= 0); // mapping zero not allowed EXTERN int no_zero_mapping INIT(= 0); // mapping zero not allowed

View File

@ -229,7 +229,7 @@ static const struct nv_cmd {
{ 'N', nv_next, 0, SEARCH_REV }, { 'N', nv_next, 0, SEARCH_REV },
{ 'O', nv_open, 0, 0 }, { 'O', nv_open, 0, 0 },
{ 'P', nv_put, 0, 0 }, { 'P', nv_put, 0, 0 },
{ 'Q', nv_exmode, NV_NCW, 0 }, { 'Q', nv_regreplay, 0, 0 },
{ 'R', nv_Replace, 0, false }, { 'R', nv_Replace, 0, false },
{ 'S', nv_subst, NV_KEEPREG, 0 }, { 'S', nv_subst, NV_KEEPREG, 0 },
{ 'T', nv_csearch, NV_NCH_ALW|NV_LANG, BACKWARD }, { 'T', nv_csearch, NV_NCH_ALW|NV_LANG, BACKWARD },
@ -4028,15 +4028,18 @@ dozet:
/* /*
* "Q" command. * "Q" command.
*/ */
static void nv_exmode(cmdarg_T *cap) static void nv_regreplay(cmdarg_T *cap)
{ {
/* if (checkclearop(cap->oap)) {
* Ignore 'Q' in Visual mode, just give a beep. return;
*/ }
if (VIsual_active) {
vim_beep(BO_EX); while (cap->count1-- && !got_int) {
} else if (!checkclearop(cap->oap)) { if (do_execreg(reg_recorded, false, false, false) == false) {
do_exmode(); clearopbeep(cap->oap);
break;
}
line_breakcheck();
} }
} }

View File

@ -912,13 +912,14 @@ int do_record(int c)
showmode(); showmode();
regname = c; regname = c;
retval = OK; retval = OK;
apply_autocmds(EVENT_RECORDINGENTER, NULL, NULL, false, curbuf);
} }
} else { // stop recording } else { // stop recording
/* // Get the recorded key hits. K_SPECIAL and CSI will be escaped, this
* 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
* needs to be removed again to put it in a register. exec_reg then // adds the escaping back later.
* adds the escaping back later. apply_autocmds(EVENT_RECORDINGLEAVE, NULL, NULL, false, curbuf);
*/ reg_recorded = reg_recording;
reg_recording = 0; reg_recording = 0;
if (ui_has(kUIMessages)) { if (ui_has(kUIMessages)) {
showmode(); showmode();
@ -932,10 +933,8 @@ int do_record(int c)
// Remove escaping for CSI and K_SPECIAL in multi-byte chars. // Remove escaping for CSI and K_SPECIAL in multi-byte chars.
vim_unescape_csi(p); vim_unescape_csi(p);
/* // We don't want to change the default register here, so save and
* We don't want to change the default register here, so save and // restore the current register name.
* restore the current register name.
*/
old_y_previous = y_previous; old_y_previous = y_previous;
retval = stuff_yank(regname, p); retval = stuff_yank(regname, p);

View File

@ -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)

View File

@ -6,6 +6,8 @@ local feed = helpers.feed
local clear = helpers.clear local clear = helpers.clear
local expect = helpers.expect local expect = helpers.expect
local command = helpers.command local command = helpers.command
local insert = helpers.insert
local curbufmeths = helpers.curbufmeths
describe('macros', function() describe('macros', function()
before_each(clear) before_each(clear)
@ -27,4 +29,29 @@ describe('macros', function()
expect('llllll') expect('llllll')
eq(eval('@i'), 'lxxx') eq(eval('@i'), 'lxxx')
end) 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) end)