mirror of
https://github.com/neovim/neovim.git
synced 2025-09-27 05:28:33 +00:00
fix(events): make CursorHold behave as documented
This commit is contained in:
@@ -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;
|
||||||
|
@@ -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);
|
||||||
|
@@ -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;
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user