diff options
author | zeertzjq <zeertzjq@outlook.com> | 2022-09-14 19:49:55 +0800 |
---|---|---|
committer | zeertzjq <zeertzjq@outlook.com> | 2022-09-17 21:48:44 +0800 |
commit | 644a3f48b117abd1d0d0aab5ec96cd62392ca0f1 (patch) | |
tree | 0b15f58a19574d34ba97d455e09b3afc94b35bb7 | |
parent | 3c3f3e7353ba2cb9071183cd05036ca2a98d3672 (diff) | |
download | rneovim-644a3f48b117abd1d0d0aab5ec96cd62392ca0f1.tar.gz rneovim-644a3f48b117abd1d0d0aab5ec96cd62392ca0f1.tar.bz2 rneovim-644a3f48b117abd1d0d0aab5ec96cd62392ca0f1.zip |
fix(events): make CursorHold behave as documented
-rw-r--r-- | src/nvim/getchar.c | 2 | ||||
-rw-r--r-- | src/nvim/os/input.c | 24 | ||||
-rw-r--r-- | src/nvim/state.c | 2 | ||||
-rw-r--r-- | test/functional/autocmd/cursorhold_spec.lua | 57 |
4 files changed, 72 insertions, 13 deletions
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index c93810df8d..fd0acce25f 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1666,7 +1666,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv) if (!char_avail()) { // flush output before waiting ui_flush(); - (void)os_inchar(NULL, 0, -1, 0, main_loop.events); + (void)os_inchar(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events); if (!multiqueue_empty(main_loop.events)) { state_handle_k_event(); continue; diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index ea9f31d776..8fc4a5dce5 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -38,6 +38,8 @@ static RBuffer *input_buffer = NULL; static bool input_eof = false; static int global_fd = -1; static bool blocking = false; +static int cursorhold_time = 0; ///< time waiting for CursorHold event +static int cursorhold_tb_change_cnt = 0; ///< tb_change_cnt when waiting started #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/input.c.generated.h" @@ -97,13 +99,25 @@ static void create_cursorhold_event(bool events_enabled) multiqueue_put(main_loop.events, cursorhold_event, 0); } +static void restart_cursorhold_wait(int tb_change_cnt) +{ + cursorhold_time = 0; + cursorhold_tb_change_cnt = tb_change_cnt; +} + /// Low level input function /// /// wait until either the input buffer is non-empty or, if `events` is not NULL /// until `events` is non-empty. int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *events) { + // This check is needed so that feeding typeahead from RPC can prevent CursorHold. + if (tb_change_cnt != cursorhold_tb_change_cnt) { + restart_cursorhold_wait(tb_change_cnt); + } + if (maxlen && rbuffer_size(input_buffer)) { + restart_cursorhold_wait(tb_change_cnt); return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen); } @@ -118,18 +132,23 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e return 0; } } else { - if ((result = inbuf_poll((int)p_ut, events)) == kInputNone) { + uint64_t wait_start = os_hrtime(); + assert((int)p_ut >= cursorhold_time); + if ((result = inbuf_poll((int)p_ut - cursorhold_time, events)) == kInputNone) { if (read_stream.closed && silent_mode) { // Drained eventloop & initial input; exit silent/batch-mode (-es/-Es). read_error_exit(); } - + restart_cursorhold_wait(tb_change_cnt); if (trigger_cursorhold() && !typebuf_changed(tb_change_cnt)) { create_cursorhold_event(events == main_loop.events); } else { before_blocking(); result = inbuf_poll(-1, events); } + } else { + cursorhold_time += (int)((os_hrtime() - wait_start) / 1000000); + cursorhold_time = MIN(cursorhold_time, (int)p_ut); } } @@ -141,6 +160,7 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e } if (maxlen && rbuffer_size(input_buffer)) { + restart_cursorhold_wait(tb_change_cnt); // Safe to convert rbuffer_read to int, it will never overflow since we use // relatively small buffers. return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen); diff --git a/src/nvim/state.c b/src/nvim/state.c index 458eed960f..8b07c484e6 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -65,7 +65,7 @@ getkey: // Call `os_inchar` directly to block for events or user input without // consuming anything from `input_buffer`(os/input.c) or calling the // mapping engine. - (void)os_inchar(NULL, 0, -1, 0, main_loop.events); + (void)os_inchar(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events); // If an event was put into the queue, we send K_EVENT directly. if (!multiqueue_empty(main_loop.events)) { key = K_EVENT; diff --git a/test/functional/autocmd/cursorhold_spec.lua b/test/functional/autocmd/cursorhold_spec.lua index 506b688853..5d54610e1d 100644 --- a/test/functional/autocmd/cursorhold_spec.lua +++ b/test/functional/autocmd/cursorhold_spec.lua @@ -5,22 +5,61 @@ local eq = helpers.eq local eval = helpers.eval local feed = helpers.feed local retry = helpers.retry -local source = helpers.source +local exec = helpers.source local sleep = helpers.sleep +local meths = helpers.meths -describe('CursorHoldI', function() - before_each(clear) +before_each(clear) + +describe('CursorHold', function() + it('is triggered correctly #12587', function() + exec([[ + augroup test + au CursorHold * let g:cursorhold += 1 + augroup END + ]]) + + local function test_cursorhold(fn, early) + local ut = 2 + -- if testing with small 'updatetime' fails, double its value and test again + retry(10, nil, function() + ut = ut * 2 + meths.set_option('updatetime', ut) + feed('0') -- reset did_cursorhold + meths.set_var('cursorhold', 0) + sleep(ut / 4) + fn() + eq(0, meths.get_var('cursorhold')) + sleep(ut / 2) + fn() + eq(0, meths.get_var('cursorhold')) + sleep(ut / 2) + eq(early, meths.get_var('cursorhold')) + sleep(ut / 4 * 3) + eq(1, meths.get_var('cursorhold')) + end) + end + local ignore_key = meths.replace_termcodes('<Ignore>', true, true, true) + test_cursorhold(function() end, 1) + test_cursorhold(function() feed('') end, 1) + test_cursorhold(function() meths.feedkeys('', 'n', true) end, 1) + test_cursorhold(function() feed('<Ignore>') end, 0) + test_cursorhold(function() meths.feedkeys(ignore_key, 'n', true) end, 0) + end) +end) + +describe('CursorHoldI', function() -- NOTE: since this test uses RPC it is not necessary to trigger the initial -- issue (#3757) via timer's or RPC callbacks in the first place. it('is triggered after input', function() - source([[ - set updatetime=1 + exec([[ + set updatetime=1 - let g:cursorhold = 0 - augroup test - au CursorHoldI * let g:cursorhold += 1 - augroup END + let g:cursorhold = 0 + augroup test + au CursorHoldI * let g:cursorhold += 1 + augroup END ]]) feed('ifoo') retry(5, nil, function() |