fix(events): make CursorHold behave as documented

This commit is contained in:
zeertzjq
2022-09-14 19:49:55 +08:00
parent 3c3f3e7353
commit 644a3f48b1
4 changed files with 72 additions and 13 deletions

View File

@@ -1666,7 +1666,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv)
if (!char_avail()) { if (!char_avail()) {
// flush output before waiting // flush output before waiting
ui_flush(); 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)) { if (!multiqueue_empty(main_loop.events)) {
state_handle_k_event(); state_handle_k_event();
continue; continue;

View File

@@ -38,6 +38,8 @@ static RBuffer *input_buffer = NULL;
static bool input_eof = false; static bool input_eof = false;
static int global_fd = -1; static int global_fd = -1;
static bool blocking = false; 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 #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/input.c.generated.h" # 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); 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 /// Low level input function
/// ///
/// wait until either the input buffer is non-empty or, if `events` is not NULL /// wait until either the input buffer is non-empty or, if `events` is not NULL
/// until `events` is non-empty. /// until `events` is non-empty.
int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *events) 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)) { if (maxlen && rbuffer_size(input_buffer)) {
restart_cursorhold_wait(tb_change_cnt);
return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen); 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; return 0;
} }
} else { } 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) { if (read_stream.closed && silent_mode) {
// Drained eventloop & initial input; exit silent/batch-mode (-es/-Es). // Drained eventloop & initial input; exit silent/batch-mode (-es/-Es).
read_error_exit(); read_error_exit();
} }
restart_cursorhold_wait(tb_change_cnt);
if (trigger_cursorhold() && !typebuf_changed(tb_change_cnt)) { if (trigger_cursorhold() && !typebuf_changed(tb_change_cnt)) {
create_cursorhold_event(events == main_loop.events); create_cursorhold_event(events == main_loop.events);
} else { } else {
before_blocking(); before_blocking();
result = inbuf_poll(-1, events); 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)) { 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 // Safe to convert rbuffer_read to int, it will never overflow since we use
// relatively small buffers. // relatively small buffers.
return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen); return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen);

View File

@@ -65,7 +65,7 @@ getkey:
// Call `os_inchar` directly to block for events or user input without // Call `os_inchar` directly to block for events or user input without
// consuming anything from `input_buffer`(os/input.c) or calling the // consuming anything from `input_buffer`(os/input.c) or calling the
// mapping engine. // 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 an event was put into the queue, we send K_EVENT directly.
if (!multiqueue_empty(main_loop.events)) { if (!multiqueue_empty(main_loop.events)) {
key = K_EVENT; key = K_EVENT;

View File

@@ -5,16 +5,55 @@ local eq = helpers.eq
local eval = helpers.eval local eval = helpers.eval
local feed = helpers.feed local feed = helpers.feed
local retry = helpers.retry local retry = helpers.retry
local source = helpers.source local exec = helpers.source
local sleep = helpers.sleep 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 -- 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. -- issue (#3757) via timer's or RPC callbacks in the first place.
it('is triggered after input', function() it('is triggered after input', function()
source([[ exec([[
set updatetime=1 set updatetime=1
let g:cursorhold = 0 let g:cursorhold = 0