autocmd: RecordingEnter, RecordingLeave (#16684)

This commit is contained in:
Gregory Anders 2021-12-18 08:55:43 -07:00 committed by GitHub
commit 36758ba9a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 147 additions and 30 deletions

View File

@ -828,6 +828,18 @@ 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 recording.
The pattern is the current file name, and
|reg_recording()| is the current register that
is used.
*RecordingLeave*
RecordingLeave When a macro stops recording.
The pattern is the current file name, and
|reg_recording()| is the recorded
register.
|reg_recorded()| is only updated after this
event.
*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
@ -7201,7 +7202,7 @@ mode([expr]) Return a string that indicates the current mode.
Rvc Virtual Replace mode completion |compl-generic| Rvc Virtual Replace mode completion |compl-generic|
Rvx Virtual Replace mode |i_CTRL-X| completion Rvx Virtual Replace mode |i_CTRL-X| completion
c Command-line editing c Command-line editing
cv Vim Ex mode |Q| or |gQ| cv Vim Ex mode |gQ|
r Hit-enter prompt r Hit-enter prompt
rm The -- more -- prompt rm The -- more -- prompt
r? A |:confirm| query of some sort r? A |:confirm| query of some sort
@ -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| and |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

@ -339,7 +339,6 @@ tag char note action in Normal mode ~
insert text, repeat N times insert text, repeat N times
|P| ["x]P 2 put the text [from register x] before the |P| ["x]P 2 put the text [from register x] before the
cursor N times cursor N times
|Q| Q switch to "Ex" mode
|R| R 2 enter replace mode: overtype existing |R| R 2 enter replace mode: overtype existing
characters, repeat the entered text N-1 characters, repeat the entered text N-1
times times
@ -401,6 +400,7 @@ tag char note action in Normal mode ~
|q| q{0-9a-zA-Z"} record typed characters into named register |q| q{0-9a-zA-Z"} record typed characters into named register
{0-9a-zA-Z"} (uppercase to append) {0-9a-zA-Z"} (uppercase to append)
|q| q (while recording) stops recording |q| q (while recording) stops recording
|Q| Q replay last recorded macro
|q:| q: edit : command-line in command-line window |q:| q: edit : command-line in command-line window
|q/| q/ edit / command-line in command-line window |q/| q/ edit / command-line in command-line window
|q?| q? edit ? command-line in command-line window |q?| q? edit ? command-line in command-line window

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

@ -953,7 +953,6 @@ A jump table for the options with a short description can be found at |Q_op|.
error Other Error occurred (e.g. try to join last line) error Other Error occurred (e.g. try to join last line)
(mostly used in |Normal-mode| or |Cmdline-mode|). (mostly used in |Normal-mode| or |Cmdline-mode|).
esc hitting <Esc> in |Normal-mode|. esc hitting <Esc> in |Normal-mode|.
ex In |Visual-mode|, hitting |Q| results in an error.
hangul Ignored. hangul Ignored.
insertmode Pressing <Esc> in 'insertmode'. insertmode Pressing <Esc> in 'insertmode'.
lang Calling the beep module for Lua/Mzscheme/TCL. lang Calling the beep module for Lua/Mzscheme/TCL.

View File

@ -489,6 +489,7 @@ In Insert or Command-line mode:
|q| q{a-z} record typed characters into register {a-z} |q| q{a-z} record typed characters into register {a-z}
|q| q{A-Z} record typed characters, appended to register {a-z} |q| q{A-Z} record typed characters, appended to register {a-z}
|q| q stop recording |q| q stop recording
|Q| Q replay last recorded macro
|@| N @{a-z} execute the contents of register {a-z} (N times) |@| N @{a-z} execute the contents of register {a-z} (N times)
|@@| N @@ repeat previous @{a-z} (N times) |@@| N @@ repeat previous @{a-z} (N times)
|:@| :@{a-z} execute the contents of register {a-z} as an Ex |:@| :@{a-z} execute the contents of register {a-z} as an Ex
@ -997,7 +998,7 @@ Short explanation of each option: *option-list*
|:version| :ve[rsion] show version information |:version| :ve[rsion] show version information
|:normal| :norm[al][!] {commands} |:normal| :norm[al][!] {commands}
execute Normal mode commands execute Normal mode commands
|Q| Q switch to "Ex" mode |gQ| gQ switch to "Ex" mode
|:redir| :redir >{file} redirect messages to {file} |:redir| :redir >{file} redirect messages to {file}
|:silent| :silent[!] {command} execute {command} silently |:silent| :silent[!] {command} execute {command} silently

View File

@ -103,7 +103,7 @@ Which is two characters shorter!
When using "global" in Ex mode, a special case is using ":visual" as a When using "global" in Ex mode, a special case is using ":visual" as a
command. This will move to a matching line, go to Normal mode to let you command. This will move to a matching line, go to Normal mode to let you
execute commands there until you use |Q| to return to Ex mode. This will be execute commands there until you use |gQ| to return to Ex mode. This will be
repeated for each matching line. While doing this you cannot use ":global". repeated for each matching line. While doing this you cannot use ":global".
To abort this type CTRL-C twice. To abort this type CTRL-C twice.
@ -147,6 +147,10 @@ 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.
See |reg_recorded()|.
*:@* *:@*
:[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

@ -180,6 +180,8 @@ Commands:
|:match| can be invoked before highlight group is defined |:match| can be invoked before highlight group is defined
Events: Events:
|RecordingEnter|
|RecordingLeave|
|SearchWrapped| |SearchWrapped|
|Signal| |Signal|
|TabNewEntered| |TabNewEntered|
@ -356,7 +358,8 @@ Motion:
The |jumplist| avoids useless/phantom jumps. The |jumplist| avoids useless/phantom jumps.
Normal commands: Normal commands:
|Q| is the same as |gQ| |Q| replays the last recorded macro instead of switching to Ex mode.
Instead |gQ| can be used to enter Ex mode.
Options: Options:
'ttimeout', 'ttimeoutlen' behavior was simplified 'ttimeout', 'ttimeoutlen' behavior was simplified

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', -- just before a macro stops recording
'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

@ -85,7 +85,7 @@ endfunc
func Test_ex_mode_count_overflow() func Test_ex_mode_count_overflow()
" this used to cause a crash " this used to cause a crash
let lines =<< trim END let lines =<< trim END
call feedkeys("\<Esc>Q\<CR>") call feedkeys("\<Esc>gQ\<CR>")
v9|9silent! vi|333333233333y32333333%O v9|9silent! vi|333333233333y32333333%O
call writefile(['done'], 'Xdidexmode') call writefile(['done'], 'Xdidexmode')
qall! qall!

View File

@ -137,7 +137,7 @@ func Test_substitute_repeat()
" This caused an invalid memory access. " This caused an invalid memory access.
split Xfile split Xfile
s/^/x s/^/x
call feedkeys("Qsc\<CR>y", 'tx') call feedkeys("gQsc\<CR>y", 'tx')
bwipe! bwipe!
endfunc endfunc

View File

@ -279,7 +279,7 @@ func Test_ex_mode()
endfunc endfunc
let timer = timer_start(40, function('g:Foo'), {'repeat':-1}) let timer = timer_start(40, function('g:Foo'), {'repeat':-1})
" This used to throw error E749. " This used to throw error E749.
exe "normal Qsleep 100m\rvi\r" exe "normal gQsleep 100m\rvi\r"
call timer_stop(timer) call timer_stop(timer)
endfunc endfunc

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)