diff options
author | Gregory Anders <8965202+gpanders@users.noreply.github.com> | 2021-12-18 08:55:43 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-18 08:55:43 -0700 |
commit | 36758ba9a78ecf01ab8f654be08051015932983a (patch) | |
tree | 727a197fe984e45e8c68b1c5d67fae4f693d3f5c | |
parent | 818ae74eaf6f4538ca61ee4ba703543b0caaff10 (diff) | |
parent | df50fb5859250fd90b6bceb1f741f39a370a5241 (diff) | |
download | rneovim-36758ba9a78ecf01ab8f654be08051015932983a.tar.gz rneovim-36758ba9a78ecf01ab8f654be08051015932983a.tar.bz2 rneovim-36758ba9a78ecf01ab8f654be08051015932983a.zip |
autocmd: RecordingEnter, RecordingLeave (#16684)
-rw-r--r-- | runtime/doc/autocmd.txt | 12 | ||||
-rw-r--r-- | runtime/doc/eval.txt | 8 | ||||
-rw-r--r-- | runtime/doc/index.txt | 2 | ||||
-rw-r--r-- | runtime/doc/intro.txt | 4 | ||||
-rw-r--r-- | runtime/doc/options.txt | 1 | ||||
-rw-r--r-- | runtime/doc/quickref.txt | 3 | ||||
-rw-r--r-- | runtime/doc/repeat.txt | 6 | ||||
-rw-r--r-- | runtime/doc/vim_diff.txt | 5 | ||||
-rw-r--r-- | src/nvim/auevents.lua | 4 | ||||
-rw-r--r-- | src/nvim/eval.lua | 1 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 5 | ||||
-rw-r--r-- | src/nvim/globals.h | 1 | ||||
-rw-r--r-- | src/nvim/normal.c | 21 | ||||
-rw-r--r-- | src/nvim/ops.c | 21 | ||||
-rw-r--r-- | src/nvim/testdir/test_ex_mode.vim | 2 | ||||
-rw-r--r-- | src/nvim/testdir/test_substitute.vim | 2 | ||||
-rw-r--r-- | src/nvim/testdir/test_timers.vim | 2 | ||||
-rw-r--r-- | test/functional/autocmd/recording_spec.lua | 52 | ||||
-rw-r--r-- | test/functional/editor/macro_spec.lua | 27 |
19 files changed, 148 insertions, 31 deletions
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 242631d98c..3df8d5ced4 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -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 search wraps around the document back to 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 After loading the session file created using the |:mksession| command. diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 75b782fbff..aef303195d 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 @@ -7201,7 +7202,7 @@ mode([expr]) Return a string that indicates the current mode. Rvc Virtual Replace mode completion |compl-generic| Rvx Virtual Replace mode |i_CTRL-X| completion c Command-line editing - cv Vim Ex mode |Q| or |gQ| + cv Vim Ex mode |gQ| r Hit-enter prompt rm The -- more -- prompt 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. 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()* 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/index.txt b/runtime/doc/index.txt index baa7bc1992..d8689e2c65 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -339,7 +339,6 @@ tag char note action in Normal mode ~ insert text, repeat N times |P| ["x]P 2 put the text [from register x] before the cursor N times -|Q| Q switch to "Ex" mode |R| R 2 enter replace mode: overtype existing characters, repeat the entered text N-1 times @@ -401,6 +400,7 @@ tag char note action in Normal mode ~ |q| q{0-9a-zA-Z"} record typed characters into named register {0-9a-zA-Z"} (uppercase to append) |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 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/options.txt b/runtime/doc/options.txt index 097cd38400..b75dd78b57 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -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) (mostly used in |Normal-mode| or |Cmdline-mode|). esc hitting <Esc> in |Normal-mode|. - ex In |Visual-mode|, hitting |Q| results in an error. hangul Ignored. insertmode Pressing <Esc> in 'insertmode'. lang Calling the beep module for Lua/Mzscheme/TCL. diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index 8cabf05053..a229c8742f 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -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, appended to register {a-z} |q| q stop recording +|Q| Q replay last recorded macro |@| N @{a-z} execute the contents of register {a-z} (N times) |@@| N @@ repeat previous @{a-z} (N times) |:@| :@{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 |:normal| :norm[al][!] {commands} execute Normal mode commands -|Q| Q switch to "Ex" mode +|gQ| gQ switch to "Ex" mode |:redir| :redir >{file} redirect messages to {file} |:silent| :silent[!] {command} execute {command} silently diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt index 7e8d93aa71..c7481ad290 100644 --- a/runtime/doc/repeat.txt +++ b/runtime/doc/repeat.txt @@ -103,7 +103,7 @@ Which is two characters shorter! 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 -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". To abort this type CTRL-C twice. @@ -147,6 +147,10 @@ q Stops recording. *@@* *E748* @@ 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 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..4fcaf15717 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -180,6 +180,8 @@ Commands: |:match| can be invoked before highlight group is defined Events: + |RecordingEnter| + |RecordingLeave| |SearchWrapped| |Signal| |TabNewEntered| @@ -356,7 +358,8 @@ Motion: The |jumplist| avoids useless/phantom jumps. 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: 'ttimeout', 'ttimeoutlen' behavior was simplified diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 6227b08b26..8fe623fc96 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', -- just before a macro stops recording '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/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim index 1c645ad0f8..92e0559618 100644 --- a/src/nvim/testdir/test_ex_mode.vim +++ b/src/nvim/testdir/test_ex_mode.vim @@ -85,7 +85,7 @@ endfunc func Test_ex_mode_count_overflow() " this used to cause a crash let lines =<< trim END - call feedkeys("\<Esc>Q\<CR>") + call feedkeys("\<Esc>gQ\<CR>") v9|9silent! vi|333333233333y32333333%O call writefile(['done'], 'Xdidexmode') qall! diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index 113c85acef..20b760ac15 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -137,7 +137,7 @@ func Test_substitute_repeat() " This caused an invalid memory access. split Xfile s/^/x - call feedkeys("Qsc\<CR>y", 'tx') + call feedkeys("gQsc\<CR>y", 'tx') bwipe! endfunc diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index 5cc0da2586..aae315b2c5 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -279,7 +279,7 @@ func Test_ex_mode() endfunc let timer = timer_start(40, function('g:Foo'), {'repeat':-1}) " This used to throw error E749. - exe "normal Qsleep 100m\rvi\r" + exe "normal gQsleep 100m\rvi\r" call timer_stop(timer) endfunc 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) |