Skip to content

Commit

Permalink
Merge pull request #13686 from bfredl/fastevent
Browse files Browse the repository at this point in the history
state: throttle batched event processing when input is available
  • Loading branch information
bfredl committed Mar 8, 2021
2 parents c12ea02 + f901149 commit 7c204af
Show file tree
Hide file tree
Showing 9 changed files with 81 additions and 17 deletions.
2 changes: 1 addition & 1 deletion src/nvim/edit.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/nvim/eval/funcs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/nvim/ex_getln.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion src/nvim/normal.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
20 changes: 16 additions & 4 deletions src/nvim/os/input.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
28 changes: 28 additions & 0 deletions src/nvim/state.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,34 @@ void state_enter(VimState *s)
}
}

/// 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)
{
Expand Down
2 changes: 1 addition & 1 deletion src/nvim/terminal.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions test/functional/plugin/lsp_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
38 changes: 30 additions & 8 deletions test/functional/ui/input_spec.lua
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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>')
Expand Down Expand 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('')
Expand All @@ -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},
Expand Down Expand Up @@ -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)

0 comments on commit 7c204af

Please sign in to comment.