From e15485c5d659977d0a8f20f7f7f319dac2423a0e Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Fri, 21 Nov 2014 12:48:30 -0300 Subject: [PATCH 1/4] input: Refactor to ensure user input has higher priority --- src/nvim/os/input.c | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 3ebfb3f12b..d10d20b20e 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -84,13 +84,11 @@ void input_stop(void) // Low level input function. int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt) { - InbufPollResult result; - - if (event_has_deferred()) { - // Return pending event bytes - return push_event_key(buf, maxlen); + if (rbuffer_pending(input_buffer)) { + return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen); } + InbufPollResult result; if (ms >= 0) { if ((result = inbuf_poll(ms)) == kInputNone) { return 0; @@ -110,24 +108,27 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt) } } - // If there are deferred events, return the keys directly - if (event_has_deferred()) { - return push_event_key(buf, maxlen); - } - // If input was put directly in typeahead buffer bail out here. if (typebuf_changed(tb_change_cnt)) { return 0; } - if (result == kInputEof) { - read_error_exit(); - return 0; + if (rbuffer_pending(input_buffer)) { + // 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); } - // 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); + // If there are deferred events, return the keys directly + if (event_has_deferred()) { + return push_event_key(buf, maxlen); + } + + if (result == kInputEof) { + read_error_exit(); + } + + return 0; } // Check if a character is available for reading @@ -319,10 +320,9 @@ static int push_event_key(uint8_t *buf, int maxlen) // Check if there's pending input static bool input_ready(void) { - return typebuf_was_filled || // API call filled typeahead - event_has_deferred() || // Events must be processed - (!embedded_mode && ( - rbuffer_pending(input_buffer) > 0 || // Stdin input - eof)); // Stdin closed + return typebuf_was_filled || // API call filled typeahead + rbuffer_pending(input_buffer) > 0 || // Stdin input + event_has_deferred() || // Events must be processed + (!embedded_mode && eof); // Stdin closed } From 179c51319df0bc6f75a1376a0ca337c34d0a0b87 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Fri, 21 Nov 2014 13:06:03 -0300 Subject: [PATCH 2/4] test: Refactor functional helpers to use vim_input The vim_input function accepts raw terminal input and so is better to emulate real user, especially because it is not deferred as vim_feedkeys. Using this function required a number of changes: - expect() was refactored to use curbuf_contents() - The vim_eval function in request() was moved to curbuf_contents(). For most cases this is enough(we only care for synchronizing api calls with user input when verifying buffer contents). - (NUL) is preprocessed before being passed to replace_termcodes. - Legacy test 4 had a bug that only became visible when using vim_input, it is fixed now. - An extra blank line deletion was required for test 101 The last two items show that vim_feedkeys because it is not 100% equivalent to receiving terminal input. --- test/functional/helpers.lua | 50 +++++++++---------- .../004_bufenter_with_modelines_spec.lua | 4 +- test/functional/legacy/101_hlsearch_spec.lua | 3 +- test/functional/shell/viml_system_spec.lua | 1 + 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 2c08fb7818..4a98056104 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -42,7 +42,6 @@ local function request(method, ...) if not loop_stopped then -- Except when the loop has been stopped by a notification triggered -- by the initial request, for example. - session:request('vim_eval', '1') end return rv end @@ -99,25 +98,20 @@ local function nvim_eval(expr) return request('vim_eval', expr) end -local function nvim_feed(input, mode) - mode = mode or '' - request('vim_feedkeys', input, mode) -end - -local function buffer_slice(start, stop, buffer_idx) - local include_end = false - if not stop then - stop = -1 - include_end = true +local function nvim_feed(input) + while #input > 0 do + local written = request('vim_input', input) + input = input:sub(written + 1) end - local buffer = request('vim_get_buffers')[buffer_idx or 1] - local slice = request('buffer_get_line_slice', buffer, start or 0, stop, - true, include_end) - return table.concat(slice, '\n') end local function nvim_replace_termcodes(input) - return request('vim_replace_termcodes', input, false, true, true) + -- small hack to stop from being replaced by the internal + -- representation(which is different and won't work for vim_input) + local temp_replacement = 'CCCCCCCCC@@@@@@@@@@' + input = input:gsub('<[Cc][-]@>', temp_replacement) + local rv = request('vim_replace_termcodes', input, false, true, true) + return rv:gsub(temp_replacement, '\000') end local function dedent(str) @@ -150,7 +144,7 @@ end local function rawfeed(...) for _, v in ipairs({...}) do - nvim_feed(dedent(v), 'nt') + nvim_feed(dedent(v)) end end @@ -166,19 +160,19 @@ local function clear() end local function insert(...) - nvim_feed('i', 'nt') + nvim_feed('i') rawfeed(...) - nvim_feed(nvim_replace_termcodes(''), 'nt') + nvim_feed(nvim_replace_termcodes('')) end local function execute(...) for _, v in ipairs({...}) do if v:sub(1, 1) ~= '/' then -- not a search command, prefix with colon - nvim_feed(':', 'nt') + nvim_feed(':') end - nvim_feed(v, 'nt') - nvim_feed(nvim_replace_termcodes(''), 'nt') + nvim_feed(v) + nvim_feed(nvim_replace_termcodes('')) end end @@ -200,10 +194,6 @@ local function neq(expected, actual) return assert.are_not.same(expected, actual) end -local function expect(contents, first, last, buffer_index) - return eq(dedent(contents), buffer_slice(first, last, buffer_index)) -end - local function ok(expr) assert.is_true(expr) end @@ -233,6 +223,10 @@ local function curbuf(method, ...) end local function curbuf_contents() + -- Before inspecting the buffer, execute 'vim_eval' to wait until all + -- previously sent keys are processed(vim_eval is a deferred function, and + -- only processed after all input) + session:request('vim_eval', '1') return table.concat(curbuf('get_line_slice', 0, -1, true, true), '\n') end @@ -252,6 +246,10 @@ local function curtab(method, ...) return tabpage(method, tab, ...) end +local function expect(contents) + return eq(dedent(contents), curbuf_contents()) +end + clear() return { diff --git a/test/functional/legacy/004_bufenter_with_modelines_spec.lua b/test/functional/legacy/004_bufenter_with_modelines_spec.lua index f1222700a7..6f009b52dd 100644 --- a/test/functional/legacy/004_bufenter_with_modelines_spec.lua +++ b/test/functional/legacy/004_bufenter_with_modelines_spec.lua @@ -31,7 +31,7 @@ describe('BufEnter with modelines', function() execute('sp Xxx') -- Append text with autoindent to this file - feed('G?this is a') + feed('G?this is a') feed('othis should be auto-indented') -- Go to Xxx, no autocmd anymore @@ -39,7 +39,7 @@ describe('BufEnter with modelines', function() execute('buf Xxx') -- Append text without autoindent to Xxx - feed('G?this is a') + feed('G?this is a') feed('othis should be in column 1') execute('wq') diff --git a/test/functional/legacy/101_hlsearch_spec.lua b/test/functional/legacy/101_hlsearch_spec.lua index 4a3abb19ce..3c44f02edb 100644 --- a/test/functional/legacy/101_hlsearch_spec.lua +++ b/test/functional/legacy/101_hlsearch_spec.lua @@ -3,6 +3,7 @@ local helpers = require('test.functional.helpers') local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert local execute, expect = helpers.execute, helpers.expect +local eval = helpers.eval describe('v:hlsearch', function() setup(clear) @@ -44,7 +45,7 @@ describe('v:hlsearch', function() execute('$put=r') execute('call garbagecollect(1)') execute('call getchar()') - execute('1d') + execute('1d', '1d') -- Assert buffer contents. expect([[ diff --git a/test/functional/shell/viml_system_spec.lua b/test/functional/shell/viml_system_spec.lua index 91e115aedf..84c8765b48 100644 --- a/test/functional/shell/viml_system_spec.lua +++ b/test/functional/shell/viml_system_spec.lua @@ -10,6 +10,7 @@ local eq, clear, eval, feed, nvim = local function create_file_with_nuls(name) return function() feed('ipart1000part2000part3:w '..name..'') + eval('1') -- wait for the file to be created end end From 230c935e73c0eb8359b18f0da9ce2238cd7830bc Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Fri, 21 Nov 2014 15:21:03 -0300 Subject: [PATCH 3/4] test: Fix problems in job_spec.lua Nvim wasn't exiting cleanly in some job tests due to errors. This can't be noticed until the next commit, which will perform a refactoring to selectively process K_EVENT, so the `qa!` command executed at the end of each test blocks forever if there are errors which require the user to press ENTER(in that state Nvim no longer will process events). --- test/functional/job/job_spec.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/functional/job/job_spec.lua b/test/functional/job/job_spec.lua index 85a1e92e38..07ca0c0058 100644 --- a/test/functional/job/job_spec.lua +++ b/test/functional/job/job_spec.lua @@ -37,7 +37,7 @@ describe('jobs', function() end) it('allows interactive commands', function() - nvim('command', notify_str('v:job_data[1]', 'v:job_data[2]')) + nvim('command', notify_str('v:job_data[1]', 'get(v:job_data, 2)')) nvim('command', "let j = jobstart('xxx', 'cat', ['-'])") neq(0, eval('j')) nvim('command', 'call jobsend(j, "abc\\n")') @@ -46,9 +46,8 @@ describe('jobs', function() eq({'notification', 'stdout', {{'123', 'xyz'}}}, next_message()) nvim('command', 'call jobsend(j, [123, "xyz"])') eq({'notification', 'stdout', {{'123', 'xyz'}}}, next_message()) - nvim('command', notify_str('v:job_data[1])')) nvim('command', "call jobstop(j)") - eq({'notification', 'exit', {}}, next_message()) + eq({'notification', 'exit', {0}}, next_message()) end) it('preserves NULs', function() @@ -59,9 +58,10 @@ describe('jobs', function() file:close() -- v:job_data preserves NULs. - nvim('command', notify_str('v:job_data[1]', 'v:job_data[2]')) + nvim('command', notify_str('v:job_data[1]', 'get(v:job_data, 2)')) nvim('command', "let j = jobstart('xxx', 'cat', ['"..filename.."'])") eq({'notification', 'stdout', {{'abc\ndef'}}}, next_message()) + eq({'notification', 'exit', {0}}, next_message()) os.remove(filename) -- jobsend() preserves NULs. @@ -72,12 +72,13 @@ describe('jobs', function() end) it('will hold data if it does not end in a newline', function() - nvim('command', notify_str('v:job_data[1]', 'v:job_data[2]')) + nvim('command', notify_str('v:job_data[1]', 'get(v:job_data, 2)')) nvim('command', "let j = jobstart('xxx', 'cat', ['-'])") nvim('command', 'call jobsend(j, "abc\\nxyz")') eq({'notification', 'stdout', {{'abc'}}}, next_message()) nvim('command', "call jobstop(j)") eq({'notification', 'stdout', {{'xyz'}}}, next_message()) + eq({'notification', 'exit', {0}}, next_message()) end) it('will not allow jobsend/stop on a non-existent job', function() From f09a33bbc131138f67aa13752559ade2d4e577c2 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Fri, 21 Nov 2014 15:34:18 -0300 Subject: [PATCH 4/4] event: No longer process K_EVENT automatically Two new functions, `event_enable_deferred()`/`event_disable_deferred()` have to be called by code that is capable of handling asynchronicity. User-dialog states like "press ENTER to continue" or the swap file confirmation no longer will generate K_EVENT. --- src/nvim/edit.c | 12 ++++++++---- src/nvim/ex_getln.c | 14 +++++++------- src/nvim/getchar.c | 1 - src/nvim/message.c | 6 ------ src/nvim/normal.c | 14 ++++++++------ src/nvim/os/event.c | 20 +++++++++++++++++++- 6 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/nvim/edit.c b/src/nvim/edit.c index d7910347fc..b9ecbc71fe 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -593,9 +593,17 @@ edit ( * Get a character for Insert mode. Ignore K_IGNORE. */ lastc = c; /* remember previous char for CTRL-D */ + event_enable_deferred(); do { c = safe_vgetc(); } while (c == K_IGNORE); + event_disable_deferred(); + + if (c == K_EVENT) { + c = lastc; + event_process(); + continue; + } /* Don't want K_CURSORHOLD for the second key, e.g., after CTRL-V. */ did_cursorhold = TRUE; @@ -943,10 +951,6 @@ doESCkey: did_cursorhold = TRUE; break; - case K_EVENT: - event_process(); - break; - case K_HOME: /* */ case K_KHOME: case K_S_HOME: diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 5feff4d456..2be3757821 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -311,9 +311,16 @@ getcmdline ( /* Get a character. Ignore K_IGNORE, it should not do anything, such * as stop completion. */ + event_enable_deferred(); do { c = safe_vgetc(); } while (c == K_IGNORE); + event_disable_deferred(); + + if (c == K_EVENT) { + event_process(); + continue; + } if (KeyTyped) { some_key_typed = TRUE; @@ -769,11 +776,6 @@ getcmdline ( * Big switch for a typed command line character. */ switch (c) { - case K_EVENT: - event_process(); - // Force a redraw even though the command line didn't change - shell_resized(); - goto cmdline_not_changed; case K_BS: case Ctrl_H: case K_DEL: @@ -1885,8 +1887,6 @@ redraw: } if (IS_SPECIAL(c1)) { - // Process deferred events - event_process(); // Ignore other special key codes continue; } diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index fd60664b7b..1bec0fa1bb 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -2481,7 +2481,6 @@ inchar ( char_u dum[DUM_LEN + 1]; for (;; ) { - event_process(); len = ui_inchar(dum, DUM_LEN, 0L, 0); if (len == 0 || (len == 1 && dum[0] == 3)) break; diff --git a/src/nvim/message.c b/src/nvim/message.c index ee83fd371e..23feeab173 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -44,7 +44,6 @@ #include "nvim/term.h" #include "nvim/ui.h" #include "nvim/os/os.h" -#include "nvim/os/event.h" /* * To be able to scroll back at the "more" and "hit-enter" prompts we need to @@ -2076,9 +2075,6 @@ static int do_more_prompt(int typed_char) toscroll = 0; switch (c) { - case K_EVENT: - event_process(); - break; case BS: /* scroll one line back */ case K_BS: case 'k': @@ -2738,8 +2734,6 @@ do_dialog ( break; default: /* Could be a hotkey? */ if (c < 0) { /* special keys are ignored here */ - // drain event queue to prevent infinite loop - event_process(); continue; } if (c == ':' && ex_cmd) { diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 29070ff188..f58e044c2c 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -312,7 +312,6 @@ static const struct nv_cmd { {K_F8, farsi_fkey, 0, 0}, {K_F9, farsi_fkey, 0, 0}, {K_CURSORHOLD, nv_cursorhold, NV_KEEPREG, 0}, - {K_EVENT, nv_event, NV_KEEPREG, 0}, }; /* Number of commands in nv_cmds[]. */ @@ -483,7 +482,15 @@ normal_cmd ( /* * Get the command character from the user. */ + event_enable_deferred(); c = safe_vgetc(); + event_disable_deferred(); + + if (c == K_EVENT) { + event_process(); + return; + } + LANGMAP_ADJUST(c, true); /* @@ -7373,8 +7380,3 @@ static void nv_cursorhold(cmdarg_T *cap) did_cursorhold = true; cap->retval |= CA_COMMAND_BUSY; /* don't call edit() now */ } - -static void nv_event(cmdarg_T *cap) -{ - event_process(); -} diff --git a/src/nvim/os/event.c b/src/nvim/os/event.c index 1477072f4f..34560610bd 100644 --- a/src/nvim/os/event.c +++ b/src/nvim/os/event.c @@ -18,6 +18,8 @@ #include "nvim/vim.h" #include "nvim/memory.h" #include "nvim/misc2.h" +#include "nvim/term.h" +#include "nvim/screen.h" #include "nvim/lib/klist.h" @@ -39,6 +41,7 @@ typedef struct { // loop(to avoid recursion), but before returning from // `event_poll` static klist_t(Event) *deferred_events = NULL, *immediate_events = NULL; +static int deferred_events_allowed = 0; void event_init(void) { @@ -134,7 +137,17 @@ void event_poll(int ms) bool event_has_deferred(void) { - return !kl_empty(deferred_events); + return deferred_events_allowed && !kl_empty(deferred_events); +} + +void event_enable_deferred(void) +{ + ++deferred_events_allowed; +} + +void event_disable_deferred(void) +{ + --deferred_events_allowed; } // Queue an event @@ -146,6 +159,11 @@ void event_push(Event event, bool deferred) void event_process(void) { process_events_from(deferred_events); + + if (must_redraw) { + update_screen(0); + out_flush(); + } } static void process_events_from(klist_t(Event) *queue)