aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbfredl <bjorn.linse@gmail.com>2022-02-06 19:22:02 +0100
committerGitHub <noreply@github.com>2022-02-06 19:22:02 +0100
commitf0699f43566cd3e5f7fbc4d1e2802c610b5bfa5c (patch)
tree4a418cd1bbd545e1a106fb122ea2d8c050da2fbe
parentc7df847c07c3b3cc6c47e3f33be9b66eb69ce448 (diff)
parent74998b0449c4df0494c3bfe5d4034c575d972406 (diff)
downloadrneovim-f0699f43566cd3e5f7fbc4d1e2802c610b5bfa5c.tar.gz
rneovim-f0699f43566cd3e5f7fbc4d1e2802c610b5bfa5c.tar.bz2
rneovim-f0699f43566cd3e5f7fbc4d1e2802c610b5bfa5c.zip
Merge pull request #17279 from zeertzjq/state-enter-vpeekc
fix(event-loop): call vpeekc() directly first to check for character
-rw-r--r--src/nvim/eval/funcs.c2
-rw-r--r--src/nvim/state.c22
-rw-r--r--test/functional/ui/input_spec.lua19
-rw-r--r--test/functional/vimscript/timer_spec.lua8
4 files changed, 43 insertions, 8 deletions
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index edf6ed3c12..aa60bc6b22 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -3189,7 +3189,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv)
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())) {
+ if (!char_avail()) {
(void)os_inchar(NULL, 0, -1, 0, main_loop.events);
if (!multiqueue_empty(main_loop.events)) {
state_handle_k_event();
diff --git a/src/nvim/state.c b/src/nvim/state.c
index 9e4c9b2bad..f9a3aaab7f 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -39,10 +39,16 @@ void state_enter(VimState *s)
int key;
getkey:
- if (char_avail() || using_script() || input_available()) {
- // Don't block for events if there's a character already available for
- // processing. Characters can come from mappings, scripts and other
- // sources, so this scenario is very common.
+ // Expand mappings first by calling vpeekc() directly.
+ // - If vpeekc() returns non-NUL, there is a character already available for processing, so
+ // don't block for events. vgetc() may still block, in case of an incomplete UTF-8 sequence.
+ // - If vpeekc() returns NUL, vgetc() will block, and there are three cases:
+ // - There is no input available.
+ // - All of available input maps to an empty string.
+ // - There is an incomplete mapping.
+ // A blocking wait for a character should only be done in the third case, which is the only
+ // case of the three where typebuf.tb_len > 0 after vpeekc() returns NUL.
+ if (vpeekc() != NUL || typebuf.tb_len > 0) {
key = safe_vgetc();
} else if (!multiqueue_empty(main_loop.events)) {
// Event was made available after the last multiqueue_process_events call
@@ -55,9 +61,11 @@ getkey:
// mapping engine.
(void)os_inchar(NULL, 0, -1, 0, main_loop.events);
// If an event was put into the queue, we send K_EVENT directly.
- key = !multiqueue_empty(main_loop.events)
- ? K_EVENT
- : safe_vgetc();
+ if (!multiqueue_empty(main_loop.events)) {
+ key = K_EVENT;
+ } else {
+ goto getkey;
+ }
}
if (key == K_EVENT) {
diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua
index 78f4c6ddd3..7000505909 100644
--- a/test/functional/ui/input_spec.lua
+++ b/test/functional/ui/input_spec.lua
@@ -140,6 +140,25 @@ describe('input utf sequences that contain CSI (0x9B)', function()
end)
end)
+describe('input split utf sequences', function()
+ it('ok', function()
+ local str = '►'
+ feed('i' .. str:sub(1, 1))
+ helpers.sleep(10)
+ feed(str:sub(2, 3))
+ expect('►')
+ end)
+
+ it('can be mapped', function()
+ command('inoremap ► E296BA')
+ local str = '►'
+ feed('i' .. str:sub(1, 1))
+ helpers.sleep(10)
+ feed(str:sub(2, 3))
+ expect('E296BA')
+ end)
+end)
+
describe('input non-printable chars', function()
after_each(function()
os.remove('Xtest-overwrite')
diff --git a/test/functional/vimscript/timer_spec.lua b/test/functional/vimscript/timer_spec.lua
index e45b64422f..25e46062b7 100644
--- a/test/functional/vimscript/timer_spec.lua
+++ b/test/functional/vimscript/timer_spec.lua
@@ -272,4 +272,12 @@ describe('timers', function()
]]
eq("Vim(call):E48: Not allowed in sandbox", exc_exec("sandbox call timer_start(0, 'Scary')"))
end)
+
+ it('can be triggered after an empty string <expr> mapping', function()
+ local screen = Screen.new(40, 6)
+ screen:attach()
+ command([=[imap <expr> <F2> [timer_start(0, { _ -> execute("throw 'x'", "") }), ''][-1]]=])
+ feed('i<F2>')
+ screen:expect({any='E605: Exception not caught: x'})
+ end)
end)