aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2022-04-07 22:40:01 +0800
committerGitHub <noreply@github.com>2022-04-07 22:40:01 +0800
commitabc157a6fd5ed2f09271ee3dd75d23d9ec3e0313 (patch)
treedde71e2d365e1d1eb49453eaf83fd3e03efc2619
parentdc9e436986bec15b027c2a8d78782f514c046a8b (diff)
parent64802da6c4304a2700d9471d17c0aae143d9aab1 (diff)
downloadrneovim-abc157a6fd5ed2f09271ee3dd75d23d9ec3e0313.tar.gz
rneovim-abc157a6fd5ed2f09271ee3dd75d23d9ec3e0313.tar.bz2
rneovim-abc157a6fd5ed2f09271ee3dd75d23d9ec3e0313.zip
Merge pull request #18021 from zeertzjq/fix-clearing-reg-executing
Fix clearing of reg_executing
-rw-r--r--src/nvim/ex_docmd.c4
-rw-r--r--src/nvim/ex_docmd.h1
-rw-r--r--src/nvim/getchar.c21
-rw-r--r--src/nvim/globals.h2
-rw-r--r--src/nvim/state.c5
-rw-r--r--src/nvim/testdir/test_registers.vim18
-rw-r--r--test/functional/editor/macro_spec.lua47
7 files changed, 90 insertions, 8 deletions
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index bb6f3ba395..1f17101aca 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -1248,6 +1248,7 @@ static char_u *do_one_cmd(char_u **cmdlinep, int flags, cstack_T *cstack, LineGe
const int save_msg_scroll = msg_scroll;
cmdmod_T save_cmdmod;
const int save_reg_executing = reg_executing;
+ const bool save_pending_end_reg_executing = pending_end_reg_executing;
char_u *cmd;
memset(&ea, 0, sizeof(ea));
@@ -2018,6 +2019,7 @@ doend:
undo_cmdmod(&ea, save_msg_scroll);
cmdmod = save_cmdmod;
reg_executing = save_reg_executing;
+ pending_end_reg_executing = save_pending_end_reg_executing;
if (ea.did_sandbox) {
sandbox--;
@@ -8529,6 +8531,7 @@ bool save_current_state(save_state_T *sst)
sst->save_finish_op = finish_op;
sst->save_opcount = opcount;
sst->save_reg_executing = reg_executing;
+ sst->save_pending_end_reg_executing = pending_end_reg_executing;
msg_scroll = false; // no msg scrolling in Normal mode
restart_edit = 0; // don't go to Insert mode
@@ -8559,6 +8562,7 @@ void restore_current_state(save_state_T *sst)
finish_op = sst->save_finish_op;
opcount = sst->save_opcount;
reg_executing = sst->save_reg_executing;
+ pending_end_reg_executing = sst->save_pending_end_reg_executing;
// don't reset msg_didout now
msg_didout |= sst->save_msg_didout;
diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h
index be9f97e27d..874e0e599e 100644
--- a/src/nvim/ex_docmd.h
+++ b/src/nvim/ex_docmd.h
@@ -29,6 +29,7 @@ typedef struct {
bool save_finish_op;
long save_opcount;
int save_reg_executing;
+ bool save_pending_end_reg_executing;
tasave_T tabuf;
} save_state_T;
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index eddd5ccd14..299385cb17 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -2060,7 +2060,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth)
}
/// unget one character (can only be done once!)
-/// If the character was stuffed, vgetc() will get it next time it was called.
+/// If the character was stuffed, vgetc() will get it next time it is called.
/// Otherwise vgetc() will only get it when the stuff buffer is empty.
void vungetc(int c)
{
@@ -2072,6 +2072,20 @@ void vungetc(int c)
old_KeyStuffed = KeyStuffed;
}
+/// When peeking and not getting a character, reg_executing cannot be cleared
+/// yet, so set a flag to clear it later.
+void check_end_reg_executing(bool advance)
+{
+ if (reg_executing != 0 && (typebuf.tb_maplen == 0 || pending_end_reg_executing)) {
+ if (advance) {
+ reg_executing = 0;
+ pending_end_reg_executing = false;
+ } else {
+ pending_end_reg_executing = true;
+ }
+ }
+}
+
/// Gets a byte:
/// 1. from the stuffbuffer
/// This is used for abbreviated commands like "D" -> "d$".
@@ -2126,9 +2140,7 @@ static int vgetorpeek(bool advance)
init_typebuf();
start_stuff();
- if (advance && typebuf.tb_maplen == 0) {
- reg_executing = 0;
- }
+ check_end_reg_executing(advance);
do {
// get a character: 1. from the stuffbuffer
if (typeahead_char != 0) {
@@ -2155,6 +2167,7 @@ static int vgetorpeek(bool advance)
// If a mapped key sequence is found we go back to the start to
// try re-mapping.
for (;;) {
+ check_end_reg_executing(advance);
// os_breakcheck() is slow, don't use it too often when
// inside a mapping. But call it each time for typed
// characters.
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index ace7647b35..4aa49337cf 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -643,6 +643,8 @@ 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
+// Flag set when peeking a character and found the end of executed register
+EXTERN bool pending_end_reg_executing INIT(= false);
EXTERN int reg_recorded INIT(= 0); // last recorded register or zero
EXTERN int no_mapping INIT(= false); // currently no mapping allowed
diff --git a/src/nvim/state.c b/src/nvim/state.c
index 3a7636085b..34e3ddf654 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -40,7 +40,7 @@ void state_enter(VimState *s)
int key;
getkey:
- // Expand mappings first by calling vpeekc() directly.
+ // Apply mappings first by calling vpeekc() directly.
// - If vpeekc() returns non-NUL, there is a character already available for processing, so
// don't block for events. vgetc() may still block, in case of an incomplete UTF-8 sequence.
// - If vpeekc() returns NUL, vgetc() will block, and there are three cases:
@@ -76,6 +76,9 @@ getkey:
}
if (key == K_EVENT) {
+ // An event handler may use the value of reg_executing.
+ // Clear it if it should be cleared when getting the next character.
+ check_end_reg_executing(true);
may_sync_undo();
}
diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim
index f78b748d71..c623edd5a1 100644
--- a/src/nvim/testdir/test_registers.vim
+++ b/src/nvim/testdir/test_registers.vim
@@ -690,5 +690,23 @@ func Test_record_in_select_mode()
bwipe!
endfunc
+func Test_end_reg_executing()
+ nnoremap s <Nop>
+ let @a = 's'
+ call feedkeys("@aqaq\<Esc>", 'tx')
+ call assert_equal('', @a)
+ call assert_equal('', getline(1))
+
+ call setline(1, 'aaa')
+ nnoremap s qa
+ let @a = 'fa'
+ call feedkeys("@asq\<Esc>", 'tx')
+ call assert_equal('', @a)
+ call assert_equal('aaa', getline(1))
+
+ nunmap s
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/test/functional/editor/macro_spec.lua b/test/functional/editor/macro_spec.lua
index c0c9256af2..d4cf6b28fd 100644
--- a/test/functional/editor/macro_spec.lua
+++ b/test/functional/editor/macro_spec.lua
@@ -6,11 +6,14 @@ local feed = helpers.feed
local clear = helpers.clear
local expect = helpers.expect
local command = helpers.command
+local funcs = helpers.funcs
+local meths = helpers.meths
local insert = helpers.insert
local curbufmeths = helpers.curbufmeths
+before_each(clear)
+
describe('macros', function()
- before_each(clear)
it('can be recorded and replayed', function()
feed('qiahello<esc>q')
expect('hello')
@@ -47,9 +50,47 @@ hello]]
end)
end)
-describe('reg_recorded()', function()
- before_each(clear)
+describe('immediately after a macro has finished executing,', function()
+ before_each(function()
+ command([[let @a = 'gg0']])
+ end)
+
+ describe('reg_executing() from RPC returns an empty string', function()
+ it('if the macro does not end with a <Nop> mapping', function()
+ feed('@a')
+ eq('', funcs.reg_executing())
+ end)
+
+ it('if the macro ends with a <Nop> mapping', function()
+ command('nnoremap 0 <Nop>')
+ feed('@a')
+ eq('', funcs.reg_executing())
+ end)
+ end)
+ describe('characters from a mapping are not treated as a part of the macro #18015', function()
+ before_each(function()
+ command('nnoremap s qa')
+ end)
+
+ it('if the macro does not end with a <Nop> mapping', function()
+ feed('@asq') -- "q" from "s" mapping should start recording a macro instead of being no-op
+ eq({mode = 'n', blocking = false}, meths.get_mode())
+ expect('')
+ eq('', eval('@a'))
+ end)
+
+ it('if the macro ends with a <Nop> mapping', function()
+ command('nnoremap 0 <Nop>')
+ feed('@asq') -- "q" from "s" mapping should start recording a macro instead of being no-op
+ eq({mode = 'n', blocking = false}, meths.get_mode())
+ expect('')
+ eq('', eval('@a'))
+ end)
+ end)
+end)
+
+describe('reg_recorded()', function()
it('returns the correct value', function()
feed [[qqyyq]]
eq('q', eval('reg_recorded()'))