events: allow event processing in getchar()

this is consistent with vim, and is necessary for plugins that implement
their own input modes using "getchar()" and still want to do async
event processing.
This commit is contained in:
Björn Linse 2016-09-25 09:47:23 +02:00
parent 5a61ff188c
commit 719dae2e01
2 changed files with 85 additions and 16 deletions

View File

@ -9527,24 +9527,35 @@ static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
varnumber_T n; varnumber_T n;
int error = FALSE; int error = FALSE;
/* Position the cursor. Needed after a message that ends in a space. */
ui_cursor_goto(msg_row, msg_col);
++no_mapping; ++no_mapping;
++allow_keys; ++allow_keys;
for (;; ) { for (;; ) {
if (argvars[0].v_type == VAR_UNKNOWN) // Position the cursor. Needed after a message that ends in a space,
/* getchar(): blocking wait. */ // or if event processing caused a redraw.
ui_cursor_goto(msg_row, msg_col);
if (argvars[0].v_type == VAR_UNKNOWN) {
// getchar(): blocking wait.
if (!(char_avail() || using_script() || input_available())) {
input_enable_events();
(void)os_inchar(NULL, 0, -1, 0);
input_disable_events();
if (!multiqueue_empty(main_loop.events)) {
multiqueue_process_events(main_loop.events);
continue;
}
}
n = safe_vgetc(); n = safe_vgetc();
else if (get_tv_number_chk(&argvars[0], &error) == 1) } else if (get_tv_number_chk(&argvars[0], &error) == 1) {
/* getchar(1): only check if char avail */ // getchar(1): only check if char avail
n = vpeekc_any(); n = vpeekc_any();
else if (error || vpeekc_any() == NUL) } else if (error || vpeekc_any() == NUL) {
/* illegal argument or getchar(0) and no char avail: return zero */ // illegal argument or getchar(0) and no char avail: return zero
n = 0; n = 0;
else } else {
/* getchar(0) and char avail: return char */ // getchar(0) and char avail: return char
n = safe_vgetc(); n = safe_vgetc();
}
if (n == K_IGNORE) if (n == K_IGNORE)
continue; continue;

View File

@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen')
local ok, feed, eq, eval = helpers.ok, helpers.feed, helpers.eq, helpers.eval local ok, feed, eq, eval = helpers.ok, helpers.feed, helpers.eq, helpers.eval
local source, nvim_async, run = helpers.source, helpers.nvim_async, helpers.run local source, nvim_async, run = helpers.source, helpers.nvim_async, helpers.run
local clear, execute, funcs = helpers.clear, helpers.execute, helpers.funcs local clear, execute, funcs = helpers.clear, helpers.execute, helpers.funcs
local curbufmeths = helpers.curbufmeths
describe('timers', function() describe('timers', function()
before_each(function() before_each(function()
@ -62,19 +63,76 @@ describe('timers', function()
end) end)
it('are paused when event processing is disabled', function() it('are paused when event processing is disabled', function()
-- this is not the intended behavior, but at least there will execute("call timer_start(50, 'MyHandler', {'repeat': -1})")
-- not be a burst of queued up callbacks
execute("call timer_start(50, 'MyHandler', {'repeat': 2})")
run(nil, nil, nil, 100) run(nil, nil, nil, 100)
local count = eval("g:val") local count = eval("g:val")
-- shows two line error message and thus invokes the return prompt.
-- if we start to allow event processing here, we need to change this test.
execute("throw 'fatal error'")
run(nil, nil, nil, 300)
feed("<cr>")
local diff = eval("g:val") - count
ok(0 <= diff and diff <= 4)
end)
it('are triggered in blocking getchar() call', function()
execute("call timer_start(50, 'MyHandler', {'repeat': -1})")
nvim_async("command", "let g:c = getchar()") nvim_async("command", "let g:c = getchar()")
run(nil, nil, nil, 300) run(nil, nil, nil, 300)
feed("c") feed("c")
local diff = eval("g:val") - count local count = eval("g:val")
ok(0 <= diff and diff <= 2) ok(count >= 5)
eq(99, eval("g:c")) eq(99, eval("g:c"))
end) end)
it('can invoke redraw in blocking getchar() call', function()
local screen = Screen.new(40, 6)
screen:attach()
screen:set_default_attr_ids({
[1] = {bold=true, foreground=Screen.colors.Blue},
})
curbufmeths.set_lines(0, -1, true, {"ITEM 1", "ITEM 2"})
source([[
func! AddItem(timer)
call nvim_buf_set_lines(0, 2, 2, v:true, ['ITEM 3'])
redraw
endfunc
call timer_start(200, 'AddItem')
]])
nvim_async("command", "let g:c2 = getchar()")
screen:expect([[
ITEM 1 |
ITEM 2 |
{1:~ }|
{1:~ }|
{1:~ }|
^ |
]])
screen:sleep(200)
screen:expect([[
ITEM 1 |
ITEM 2 |
ITEM 3 |
{1:~ }|
{1:~ }|
^ |
]])
feed("3")
eq(51, eval("g:c2"))
screen:expect([[
^ITEM 1 |
ITEM 2 |
ITEM 3 |
{1:~ }|
{1:~ }|
|
]])
end)
it('can be stopped', function() it('can be stopped', function()
local t = eval("timer_start(50, 'MyHandler', {'repeat': -1})") local t = eval("timer_start(50, 'MyHandler', {'repeat': -1})")
eq(0,eval("g:val")) eq(0,eval("g:val"))