diff options
author | Björn Linse <bjorn.linse@gmail.com> | 2016-05-25 11:00:54 +0200 |
---|---|---|
committer | Björn Linse <bjorn.linse@gmail.com> | 2016-05-25 11:00:54 +0200 |
commit | c74ce334f2f7c42dcd33bc5a0d1cc02b752733f6 (patch) | |
tree | 0c1696f84e8ff072709f843726fdedcb3c6cd48d /src | |
parent | 1e22076a6561ae2dba820ff961795ddb571940ea (diff) | |
parent | 5cc87d4dabd02167117be7a978b5c8faaa975419 (diff) | |
download | rneovim-c74ce334f2f7c42dcd33bc5a0d1cc02b752733f6.tar.gz rneovim-c74ce334f2f7c42dcd33bc5a0d1cc02b752733f6.tar.bz2 rneovim-c74ce334f2f7c42dcd33bc5a0d1cc02b752733f6.zip |
Merge pull request #4624 from bfredl/timers
implement timers and process events during sleep
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/eval.c | 126 | ||||
-rw-r--r-- | src/nvim/event/time.c | 5 | ||||
-rw-r--r-- | src/nvim/event/time.h | 1 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 6 | ||||
-rw-r--r-- | src/nvim/ex_getln.c | 1 | ||||
-rw-r--r-- | src/nvim/message.c | 24 | ||||
-rw-r--r-- | src/nvim/testdir/Makefile | 1 | ||||
-rw-r--r-- | src/nvim/testdir/test_timers.vim | 32 | ||||
-rw-r--r-- | src/nvim/version.c | 4 |
9 files changed, 190 insertions, 10 deletions
diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 96c008b0e0..6ae5081550 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -79,6 +79,7 @@ #include "nvim/event/pty_process.h" #include "nvim/event/rstream.h" #include "nvim/event/wstream.h" +#include "nvim/event/time.h" #include "nvim/os/time.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/server.h" @@ -428,6 +429,14 @@ typedef struct { int status; } JobEvent; +typedef struct { + TimeWatcher tw; + int timer_id; + int repeat_count; + bool stopped; + ufunc_T *callback; +} timer_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval.c.generated.h" #endif @@ -438,6 +447,9 @@ typedef struct { static uint64_t current_job_id = 1; static PMap(uint64_t) *jobs = NULL; +static uint64_t last_timer_id = 0; +static PMap(uint64_t) *timers = NULL; + static const char *const msgpack_type_names[] = { [kMPNil] = "nil", [kMPBoolean] = "boolean", @@ -469,6 +481,7 @@ void eval_init(void) vimvars[VV_VERSION].vv_nr = VIM_VERSION_100; jobs = pmap_new(uint64_t)(); + timers = pmap_new(uint64_t)(); struct vimvar *p; init_var_dict(&globvardict, &globvars_var, VAR_DEF_SCOPE); @@ -6930,6 +6943,8 @@ static struct fst { { "tempname", 0, 0, f_tempname }, { "termopen", 1, 2, f_termopen }, { "test", 1, 1, f_test }, + { "timer_start", 2, 3, f_timer_start }, + { "timer_stop", 1, 1, f_timer_stop }, { "tolower", 1, 1, f_tolower }, { "toupper", 1, 1, f_toupper }, { "tr", 3, 3, f_tr }, @@ -10688,6 +10703,7 @@ static void f_has(typval_T *argvars, typval_T *rettv) "termguicolors", "termresponse", "textobjects", + "timers", "title", "user-commands", /* was accidentally included in 5.4 */ "user_commands", @@ -16441,6 +16457,116 @@ static void f_tanh(typval_T *argvars, typval_T *rettv) float_op_wrapper(argvars, rettv, &tanh); } + +/// "timer_start(timeout, callback, opts)" function +static void f_timer_start(typval_T *argvars, typval_T *rettv) +{ + long timeout = get_tv_number(&argvars[0]); + timer_T *timer; + int repeat = 1; + dict_T *dict; + + rettv->vval.v_number = -1; + + if (argvars[2].v_type != VAR_UNKNOWN) { + if (argvars[2].v_type != VAR_DICT + || (dict = argvars[2].vval.v_dict) == NULL) { + EMSG2(_(e_invarg2), get_tv_string(&argvars[2])); + return; + } + if (dict_find(dict, (char_u *)"repeat", -1) != NULL) { + repeat = get_dict_number(dict, (char_u *)"repeat"); + } + } + + if (argvars[1].v_type != VAR_FUNC && argvars[1].v_type != VAR_STRING) { + EMSG2(e_invarg2, "funcref"); + return; + } + ufunc_T *func = find_ufunc(argvars[1].vval.v_string); + if (!func) { + // Invalid function name. Error already reported by `find_ufunc`. + return; + } + func->uf_refcount++; + + timer = xmalloc(sizeof *timer); + timer->stopped = false; + timer->repeat_count = repeat; + timer->timer_id = last_timer_id++; + timer->callback = func; + + time_watcher_init(&loop, &timer->tw, timer); + timer->tw.events = queue_new_child(loop.events); + // if main loop is blocked, don't queue up multiple events + timer->tw.blockable = true; + time_watcher_start(&timer->tw, timer_due_cb, timeout, + timeout * (repeat != 1)); + + pmap_put(uint64_t)(timers, timer->timer_id, timer); + rettv->vval.v_number = timer->timer_id; +} + + +// "timer_stop(timerid)" function +static void f_timer_stop(typval_T *argvars, typval_T *rettv) +{ + if (argvars[0].v_type != VAR_NUMBER) { + EMSG(_(e_number_exp)); + return; + } + + timer_T *timer = pmap_get(uint64_t)(timers, get_tv_number(&argvars[0])); + + if (timer == NULL) { + return; + } + + timer_stop(timer); +} + +// invoked on the main loop +static void timer_due_cb(TimeWatcher *tw, void *data) +{ + timer_T *timer = (timer_T *)data; + // if repeat was negative repeat forever + if (timer->repeat_count >= 0 && --timer->repeat_count == 0) { + timer_stop(timer); + } + + typval_T argv[1]; + init_tv(argv); + argv[0].v_type = VAR_NUMBER; + argv[0].vval.v_number = timer->timer_id; + typval_T rettv; + + init_tv(&rettv); + call_user_func(timer->callback, ARRAY_SIZE(argv), argv, &rettv, + curwin->w_cursor.lnum, curwin->w_cursor.lnum, NULL); + clear_tv(&rettv); +} + +static void timer_stop(timer_T *timer) +{ + if (timer->stopped) { + // avoid double free + return; + } + timer->stopped = true; + time_watcher_stop(&timer->tw); + time_watcher_close(&timer->tw, timer_free_cb); +} + +// invoked on next event loop tick, so queue is empty +static void timer_free_cb(TimeWatcher *tw, void *data) +{ + timer_T *timer = (timer_T *)data; + queue_free(timer->tw.events); + user_func_unref(timer->callback); + pmap_del(uint64_t)(timers, timer->timer_id); + xfree(timer); +} + /* * "tolower(string)" function */ diff --git a/src/nvim/event/time.c b/src/nvim/event/time.c index 7bf333bcea..f68a66345f 100644 --- a/src/nvim/event/time.c +++ b/src/nvim/event/time.c @@ -17,6 +17,7 @@ void time_watcher_init(Loop *loop, TimeWatcher *watcher, void *data) watcher->uv.data = watcher; watcher->data = data; watcher->events = loop->fast_events; + watcher->blockable = false; } void time_watcher_start(TimeWatcher *watcher, time_cb cb, uint64_t timeout, @@ -50,6 +51,10 @@ static void time_watcher_cb(uv_timer_t *handle) FUNC_ATTR_NONNULL_ALL { TimeWatcher *watcher = handle->data; + if (watcher->blockable && !queue_empty(watcher->events)) { + // the timer blocked and there already is an unprocessed event waiting + return; + } CREATE_EVENT(watcher->events, time_event, 1, watcher); } diff --git a/src/nvim/event/time.h b/src/nvim/event/time.h index 7882b2b627..14df176ea3 100644 --- a/src/nvim/event/time.h +++ b/src/nvim/event/time.h @@ -13,6 +13,7 @@ struct time_watcher { void *data; time_cb cb, close_cb; Queue *events; + bool blockable; }; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 870284a0f7..59962c153b 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -6989,10 +6989,10 @@ static void ex_sleep(exarg_T *eap) */ void do_sleep(long msec) { - long done; ui_flush(); // flush before waiting - for (done = 0; !got_int && done < msec; done += 1000L) { - os_delay(msec - done > 1000L ? 1000L : msec - done, true); + for (long left = msec; !got_int && left > 0; left -= 1000L) { + int next = left > 1000l ? 1000 : (int)left; + LOOP_PROCESS_EVENTS_UNTIL(&loop, loop.events, (int)next, got_int); os_breakcheck(); } } diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index a4e5a4dcd7..db21fddedb 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -359,6 +359,7 @@ static int command_line_execute(VimState *state, int key) if (s->c == K_EVENT) { queue_process_events(loop.events); + redrawcmdline(); return 1; } diff --git a/src/nvim/message.c b/src/nvim/message.c index 47f246fc76..521db85cf0 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -785,11 +785,13 @@ void wait_return(int redraw) State = HITRETURN; setmouse(); - /* Avoid the sequence that the user types ":" at the hit-return prompt - * to start an Ex command, but the file-changed dialog gets in the - * way. */ - if (need_check_timestamps) - check_timestamps(FALSE); + cmdline_row = msg_row; + // Avoid the sequence that the user types ":" at the hit-return prompt + // to start an Ex command, but the file-changed dialog gets in the + // way. + if (need_check_timestamps) { + check_timestamps(false); + } hit_return_msg(); @@ -1970,6 +1972,7 @@ static void msg_puts_printf(char *str, int maxlen) */ static int do_more_prompt(int typed_char) { + static bool entered = false; int used_typed_char = typed_char; int oldState = State; int c; @@ -1979,6 +1982,13 @@ static int do_more_prompt(int typed_char) msgchunk_T *mp; int i; + // We get called recursively when a timer callback outputs a message. In + // that case don't show another prompt. Also when at the hit-Enter prompt. + if (entered || State == HITRETURN) { + return false; + } + entered = true; + if (typed_char == 'G') { /* "g<": Find first line on the last page. */ mp_last = msg_sb_start(last_msgchunk); @@ -2153,9 +2163,11 @@ static int do_more_prompt(int typed_char) if (quit_more) { msg_row = Rows - 1; msg_col = 0; - } else if (cmdmsg_rl) + } else if (cmdmsg_rl) { msg_col = Columns - 1; + } + entered = false; return retval; } diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 2fba6dd563..3e17117db0 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -40,6 +40,7 @@ NEW_TESTS = \ test_cursor_func.res \ test_help_tagjump.res \ test_menu.res \ + test_timers.res \ test_viml.res \ test_alot.res diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim new file mode 100644 index 0000000000..9f58a35909 --- /dev/null +++ b/src/nvim/testdir/test_timers.vim @@ -0,0 +1,32 @@ +" Test for timers + +if !has('timers') + finish +endif + +func MyHandler(timer) + let s:val += 1 +endfunc + +func Test_oneshot() + let s:val = 0 + let timer = timer_start(50, 'MyHandler') + sleep 200m + call assert_equal(1, s:val) +endfunc + +func Test_repeat_three() + let s:val = 0 + let timer = timer_start(50, 'MyHandler', {'repeat': 3}) + sleep 500m + call assert_equal(3, s:val) +endfunc + +func Test_repeat_many() + let s:val = 0 + let timer = timer_start(50, 'MyHandler', {'repeat': -1}) + sleep 200m + call timer_stop(timer) + call assert_true(s:val > 1) + call assert_true(s:val < 5) +endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index c19e1b01c2..d34df421f3 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -70,6 +70,7 @@ static char *features[] = { // clang-format off static int included_patches[] = { 1832, + 1831, 1809, 1808, 1806, @@ -82,6 +83,7 @@ static int included_patches[] = { 1652, 1643, 1641, + // 1624 NA // 1600 NA // 1599 NA @@ -106,7 +108,7 @@ static int included_patches[] = { // 1581, // 1580, // 1579, - // 1578, + 1578, // 1577, 1576, // 1575 NA |