mirror of
https://github.com/neovim/neovim.git
synced 2025-09-05 19:08:15 +00:00
state: throttle batched event processing when input is available
before, calling vim.schedule() from inside an event would execute the scheduled callback immediately after this event without checking for user input in between. Break event processing whenever user input or an interrupt is available.
This commit is contained in:
@@ -1024,7 +1024,7 @@ static int insert_handle_key(InsertState *s)
|
||||
break;
|
||||
|
||||
case K_EVENT: // some event
|
||||
multiqueue_process_events(main_loop.events);
|
||||
state_handle_k_event();
|
||||
goto check_pum;
|
||||
|
||||
case K_COMMAND: // some command
|
||||
|
@@ -3029,10 +3029,11 @@ static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
|
||||
if (argvars[0].v_type == VAR_UNKNOWN) {
|
||||
// getchar(): blocking wait.
|
||||
// TODO(bfredl): deduplicate shared logic with state_enter ?
|
||||
if (!(char_avail() || using_script() || input_available())) {
|
||||
(void)os_inchar(NULL, 0, -1, 0, main_loop.events);
|
||||
if (!multiqueue_empty(main_loop.events)) {
|
||||
multiqueue_process_events(main_loop.events);
|
||||
state_handle_k_event();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@@ -935,7 +935,7 @@ static int command_line_execute(VimState *state, int key)
|
||||
|
||||
if (s->c == K_EVENT || s->c == K_COMMAND) {
|
||||
if (s->c == K_EVENT) {
|
||||
multiqueue_process_events(main_loop.events);
|
||||
state_handle_k_event();
|
||||
} else {
|
||||
do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT);
|
||||
}
|
||||
|
@@ -8103,7 +8103,7 @@ static void nv_event(cmdarg_T *cap)
|
||||
// lists or dicts being used.
|
||||
may_garbage_collect = false;
|
||||
bool may_restart = (restart_edit != 0);
|
||||
multiqueue_process_events(main_loop.events);
|
||||
state_handle_k_event();
|
||||
finish_op = false;
|
||||
if (may_restart) {
|
||||
// Tricky: if restart_edit was set before the handler we are in ctrl-o mode,
|
||||
|
@@ -159,16 +159,28 @@ bool os_char_avail(void)
|
||||
return inbuf_poll(0, NULL) == kInputAvail;
|
||||
}
|
||||
|
||||
// Check for CTRL-C typed by reading all available characters.
|
||||
/// Poll for fast events. `got_int` will be set to `true` if CTRL-C was typed.
|
||||
///
|
||||
/// This invokes a full libuv loop iteration which can be quite costly.
|
||||
/// Prefer `line_breakcheck()` if called in a busy inner loop.
|
||||
///
|
||||
/// Caller must at least check `got_int` before calling this function again.
|
||||
/// checking for other low-level input state like `input_available()` might
|
||||
/// also be relevant (i e to throttle idle processing when user input is
|
||||
/// available)
|
||||
void os_breakcheck(void)
|
||||
{
|
||||
if (got_int) {
|
||||
return;
|
||||
}
|
||||
|
||||
int save_us = updating_screen;
|
||||
// We do not want screen_resize() to redraw here.
|
||||
// TODO(bfredl): we are already special casing redraw events, is this
|
||||
// hack still needed?
|
||||
updating_screen++;
|
||||
|
||||
if (!got_int) {
|
||||
loop_poll_events(&main_loop, 0);
|
||||
}
|
||||
loop_poll_events(&main_loop, 0);
|
||||
|
||||
updating_screen = save_us;
|
||||
}
|
||||
|
@@ -75,6 +75,34 @@ getkey:
|
||||
}
|
||||
}
|
||||
|
||||
/// process events on main_loop, but interrupt if input is available
|
||||
///
|
||||
/// This should be used to handle K_EVENT in states accepting input
|
||||
/// otherwise bursts of events can block break checking indefinitely.
|
||||
void state_handle_k_event(void)
|
||||
{
|
||||
while (true) {
|
||||
Event event = multiqueue_get(main_loop.events);
|
||||
if (event.handler) {
|
||||
event.handler(event.argv);
|
||||
}
|
||||
|
||||
if (multiqueue_empty(main_loop.events)) {
|
||||
// don't breakcheck before return, caller should return to main-loop
|
||||
// and handle input already.
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(bfredl): as an further micro-optimization, we could check whether
|
||||
// event.handler already checked input.
|
||||
os_breakcheck();
|
||||
if (input_available() || got_int) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Return true if in the current mode we need to use virtual.
|
||||
bool virtual_active(void)
|
||||
{
|
||||
|
@@ -457,7 +457,7 @@ static int terminal_execute(VimState *state, int key)
|
||||
case K_EVENT:
|
||||
// We cannot let an event free the terminal yet. It is still needed.
|
||||
s->term->refcount++;
|
||||
multiqueue_process_events(main_loop.events);
|
||||
state_handle_k_event();
|
||||
s->term->refcount--;
|
||||
if (s->term->buf_handle == 0) {
|
||||
s->close = true;
|
||||
|
@@ -257,6 +257,7 @@ describe('LSP', function()
|
||||
eq(0, client.resolved_capabilities().text_document_did_change)
|
||||
client.request('shutdown')
|
||||
client.notify('exit')
|
||||
client.stop()
|
||||
end;
|
||||
on_exit = function(code, signal)
|
||||
eq(0, code, "exit code", fake_lsp_logfile)
|
||||
|
@@ -1,16 +1,18 @@
|
||||
local helpers = require('test.functional.helpers')(after_each)
|
||||
local clear, feed_command, nvim = helpers.clear, helpers.feed_command, helpers.nvim
|
||||
local clear, feed_command = helpers.clear, helpers.feed_command
|
||||
local feed, next_msg, eq = helpers.feed, helpers.next_msg, helpers.eq
|
||||
local command = helpers.command
|
||||
local expect = helpers.expect
|
||||
local meths = helpers.meths
|
||||
local exec_lua = helpers.exec_lua
|
||||
local write_file = helpers.write_file
|
||||
local Screen = require('test.functional.ui.screen')
|
||||
|
||||
describe('mappings', function()
|
||||
local cid
|
||||
before_each(clear)
|
||||
|
||||
describe('mappings', function()
|
||||
local add_mapping = function(mapping, send)
|
||||
local cmd = "nnoremap "..mapping.." :call rpcnotify("..cid..", 'mapped', '"
|
||||
local cmd = "nnoremap "..mapping.." :call rpcnotify(1, 'mapped', '"
|
||||
..send:gsub('<', '<lt>').."')<cr>"
|
||||
feed_command(cmd)
|
||||
end
|
||||
@@ -21,8 +23,6 @@ describe('mappings', function()
|
||||
end
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
cid = nvim('get_api_info')[1]
|
||||
add_mapping('<C-L>', '<C-L>')
|
||||
add_mapping('<C-S-L>', '<C-S-L>')
|
||||
add_mapping('<s-up>', '<s-up>')
|
||||
@@ -115,7 +115,6 @@ describe('mappings', function()
|
||||
end)
|
||||
|
||||
describe('input utf sequences that contain CSI/K_SPECIAL', function()
|
||||
before_each(clear)
|
||||
it('ok', function()
|
||||
feed('i…<esc>')
|
||||
expect('…')
|
||||
@@ -129,7 +128,6 @@ describe('input non-printable chars', function()
|
||||
|
||||
it("doesn't crash when echoing them back", function()
|
||||
write_file("Xtest-overwrite", [[foobar]])
|
||||
clear()
|
||||
local screen = Screen.new(60,8)
|
||||
screen:set_default_attr_ids({
|
||||
[1] = {bold = true, foreground = Screen.colors.Blue1},
|
||||
@@ -215,3 +213,27 @@ describe('input non-printable chars', function()
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("event processing and input", function()
|
||||
it('not blocked by event bursts', function()
|
||||
meths.set_keymap('', '<f2>', "<cmd>lua vim.rpcnotify(1, 'stop') winning = true <cr>", {noremap=true})
|
||||
|
||||
exec_lua [[
|
||||
winning = false
|
||||
burst = vim.schedule_wrap(function(tell)
|
||||
if tell then
|
||||
vim.rpcnotify(1, 'start')
|
||||
end
|
||||
-- Are we winning, son?
|
||||
if not winning then
|
||||
burst(false)
|
||||
end
|
||||
end)
|
||||
burst(true)
|
||||
]]
|
||||
|
||||
eq({'notification', 'start', {}}, next_msg())
|
||||
feed '<f2>'
|
||||
eq({'notification', 'stop', {}}, next_msg())
|
||||
end)
|
||||
end)
|
||||
|
Reference in New Issue
Block a user