mirror of
https://github.com/neovim/neovim.git
synced 2025-10-02 16:08:36 +00:00
Merge pull request #4624 from bfredl/timers
implement timers and process events during sleep
This commit is contained in:
126
src/nvim/eval.c
126
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
|
||||
*/
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -13,6 +13,7 @@ struct time_watcher {
|
||||
void *data;
|
||||
time_cb cb, close_cb;
|
||||
Queue *events;
|
||||
bool blockable;
|
||||
};
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
||||
|
32
src/nvim/testdir/test_timers.vim
Normal file
32
src/nvim/testdir/test_timers.vim
Normal file
@@ -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
|
@@ -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
|
||||
|
Reference in New Issue
Block a user