Make partials work with jobs, timers, and dictwatchers.

This commit is contained in:
Björn Linse 2016-11-01 10:54:32 +01:00 committed by James McCoy
parent a21c687661
commit 0f681c80e1
5 changed files with 333 additions and 220 deletions

View File

@ -418,7 +418,7 @@ if(NOT BUSTED_OUTPUT_TYPE)
if(WIN32)
set(BUSTED_OUTPUT_TYPE "plainTerminal")
else()
set(BUSTED_OUTPUT_TYPE "utfTerminal")
set(BUSTED_OUTPUT_TYPE "gtest")
endif()
endif()

View File

@ -413,6 +413,21 @@ static struct vimvar {
static dictitem_T vimvars_var; // variable used for v:
#define vimvarht vimvardict.dv_hashtab
typedef enum {
kCallbackNone,
kCallbackFuncref,
kCallbackPartial,
} CallbackType;
typedef struct {
union {
char_u *funcref;
partial_T *partial;
} data;
CallbackType type;
} Callback;
#define CALLBACK_NONE ((Callback){ .type = kCallbackNone })
typedef struct {
union {
LibuvProcess uv;
@ -424,15 +439,14 @@ typedef struct {
bool exited;
bool rpc;
int refcount;
ufunc_T *on_stdout, *on_stderr, *on_exit;
dict_T *self;
Callback on_stdout, on_stderr, on_exit;
int *status_ptr;
uint64_t id;
MultiQueue *events;
} TerminalJobData;
typedef struct dict_watcher {
ufunc_T *callback;
Callback callback;
char *key_pattern;
QUEUE node;
bool busy; // prevent recursion if the dict is changed in the callback
@ -440,7 +454,7 @@ typedef struct dict_watcher {
typedef struct {
TerminalJobData *data;
ufunc_T *callback;
Callback *callback;
const char *type;
list_T *received;
int status;
@ -453,7 +467,7 @@ typedef struct {
int refcount;
long timeout;
bool stopped;
ufunc_T *callback;
Callback callback;
} timer_T;
typedef void (*FunPtr)(void);
@ -5909,7 +5923,17 @@ bool garbage_collect(void)
{
TerminalJobData *data;
map_foreach_value(jobs, data, {
ABORTING(set_ref_dict)(data->self, copyID);
set_ref_in_callback(&data->on_stdout, copyID, NULL, NULL);
set_ref_in_callback(&data->on_stderr, copyID, NULL, NULL);
set_ref_in_callback(&data->on_exit, copyID, NULL, NULL);
})
}
// Timers
{
timer_T *timer;
map_foreach_value(timers, timer, {
set_ref_in_callback(&timer->callback, copyID, NULL, NULL);
})
}
@ -6159,6 +6183,13 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
*ht_stack = newitem;
}
}
QUEUE *w = NULL;
DictWatcher *watcher = NULL;
QUEUE_FOREACH(w, &dd->watchers) {
watcher = dictwatcher_node_data(w);
set_ref_in_callback(&watcher->callback, copyID, ht_stack, list_stack);
}
}
if (tv->v_type == VAR_PARTIAL) {
partial_T *pt = tv->vval.v_partial;
@ -6639,49 +6670,28 @@ dictitem_T *dict_find(dict_T *d, char_u *key, int len)
/// @param[out] result The address where a pointer to the wanted callback
/// will be left.
/// @return true/false on success/failure.
static bool get_dict_callback(dict_T *d, char *key, ufunc_T **result)
static bool get_dict_callback(dict_T *d, char *key, Callback *result)
{
dictitem_T *di = dict_find(d, (uint8_t *)key, -1);
if (di == NULL) {
*result = NULL;
result->type = kCallbackNone;
return true;
}
if (di->di_tv.v_type != VAR_FUNC && di->di_tv.v_type != VAR_STRING) {
if (di->di_tv.v_type != VAR_FUNC && di->di_tv.v_type != VAR_STRING
&& di->di_tv.v_type != VAR_PARTIAL) {
EMSG(_("Argument is not a function or function name"));
*result = NULL;
result->type = kCallbackNone;
return false;
}
if ((*result = find_ufunc(di->di_tv.vval.v_string)) == NULL) {
return false;
}
(*result)->uf_refcount++;
return true;
}
static ufunc_T *find_ufunc(uint8_t *name)
{
uint8_t *n = name;
ufunc_T *rv = NULL;
if (*n > '9' || *n < '0') {
if ((n = trans_function_name(&n, false, TFN_INT|TFN_QUIET, NULL, NULL))) {
rv = find_func(n);
xfree(n);
}
} else {
// dict function, name is already translated
rv = find_func(n);
}
if (!rv) {
EMSG2(_("Function %s doesn't exist"), name);
return NULL;
}
return rv;
typval_T tv;
copy_tv(&di->di_tv, &tv);
set_selfdict(&tv, d);
bool res = callback_from_typval(result, &tv);
clear_tv(&tv);
return res;
}
/// Get a string item from a dictionary.
@ -8555,16 +8565,14 @@ static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
ufunc_T *func = find_ufunc(argvars[2].vval.v_string);
if (!func) {
// Invalid function name. Error already reported by `find_ufunc`.
Callback callback;
if (!callback_from_typval(&callback, &argvars[2])) {
return;
}
func->uf_refcount++;
DictWatcher *watcher = xmalloc(sizeof(DictWatcher));
watcher->key_pattern = xmemdupz(key_pattern, key_len);
watcher->callback = func;
watcher->callback = callback;
watcher->busy = false;
QUEUE_INSERT_TAIL(&argvars[0].vval.v_dict->watchers, &watcher->node);
}
@ -8600,9 +8608,8 @@ static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
ufunc_T *func = find_ufunc(argvars[2].vval.v_string);
if (!func) {
// Invalid function name. Error already reported by `find_ufunc`.
Callback callback;
if (!callback_from_typval(&callback, &argvars[2])) {
return;
}
@ -8612,13 +8619,15 @@ static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
bool matched = false;
QUEUE_FOREACH(w, &dict->watchers) {
watcher = dictwatcher_node_data(w);
if (func == watcher->callback
if (callback_equal(&watcher->callback, &callback)
&& !strcmp(watcher->key_pattern, key_pattern)) {
matched = true;
break;
}
}
callback_free(&callback);
if (!matched) {
EMSG("Couldn't find a watcher matching key and callback");
return;
@ -12064,7 +12073,8 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
dict_T *job_opts = NULL;
bool detach = false, rpc = false, pty = false;
ufunc_T *on_stdout = NULL, *on_stderr = NULL, *on_exit = NULL;
Callback on_stdout = CALLBACK_NONE, on_stderr = CALLBACK_NONE,
on_exit = CALLBACK_NONE;
char *cwd = NULL;
if (argvars[1].v_type == VAR_DICT) {
job_opts = argvars[1].vval.v_dict;
@ -12096,7 +12106,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
TerminalJobData *data = common_job_init(argv, on_stdout, on_stderr, on_exit,
job_opts, pty, rpc, detach, cwd);
pty, rpc, detach, cwd);
Process *proc = (Process *)&data->proc;
if (pty) {
@ -12114,10 +12124,10 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
if (!rpc && !on_stdout) {
if (!rpc && on_stdout.type == kCallbackNone) {
proc->out = NULL;
}
if (!on_stderr) {
if (on_stderr.type == kCallbackNone) {
proc->err = NULL;
}
common_job_start(data, rettv);
@ -14325,8 +14335,9 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
// The last item of argv must be NULL
argv[i] = NULL;
TerminalJobData *data = common_job_init(argv, NULL, NULL, NULL,
NULL, false, true, false, NULL);
TerminalJobData *data = common_job_init(argv, CALLBACK_NONE, CALLBACK_NONE,
CALLBACK_NONE, false, true, false,
NULL);
common_job_start(data, rettv);
}
@ -16904,7 +16915,8 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
ufunc_T *on_stdout = NULL, *on_stderr = NULL, *on_exit = NULL;
Callback on_stdout = CALLBACK_NONE, on_stderr = CALLBACK_NONE,
on_exit = CALLBACK_NONE;
dict_T *job_opts = NULL;
char *cwd = ".";
if (argvars[1].v_type == VAR_DICT) {
@ -16928,7 +16940,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
TerminalJobData *data = common_job_init(argv, on_stdout, on_stderr, on_exit,
job_opts, true, false, false, cwd);
true, false, false, cwd);
data->proc.pty.width = curwin->w_width;
data->proc.pty.height = curwin->w_height;
data->proc.pty.term_name = xstrdup("xterm-256color");
@ -16974,6 +16986,125 @@ static void f_test(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/* Used for unit testing. Change the code below to your liking. */
}
static bool callback_from_typval(Callback *callback, typval_T *arg)
{
if (arg->v_type == VAR_PARTIAL && arg->vval.v_partial != NULL) {
callback->data.partial = arg->vval.v_partial;
callback->data.partial->pt_refcount++;
callback->type = kCallbackPartial;
} else if (arg->v_type == VAR_FUNC || arg->v_type == VAR_STRING) {
char_u *name = arg->vval.v_string;
func_ref(name);
callback->data.funcref = vim_strsave(name);
callback->type = kCallbackFuncref;
} else if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0) {
callback->type = kCallbackNone;
} else {
EMSG(_("E921: Invalid callback argument"));
return false;
}
return true;
}
/// Unref/free callback
static void callback_free(Callback *callback)
{
switch (callback->type) {
case kCallbackFuncref:
func_unref(callback->data.funcref);
xfree(callback->data.funcref);
break;
case kCallbackPartial:
partial_unref(callback->data.partial);
break;
case kCallbackNone:
break;
default:
abort();
}
callback->type = kCallbackNone;
}
static bool callback_equal(Callback *cb1, Callback *cb2)
{
if (cb1->type != cb2->type) {
return false;
}
switch (cb1->type) {
case kCallbackFuncref:
return STRCMP(cb1->data.funcref, cb2->data.funcref) == 0;
case kCallbackPartial:
// FIXME: this is inconsistent with tv_equal but is needed for precision
// maybe change dictwatcheradd to return a watcher id instead?
return cb1->data.partial == cb2->data.partial;
case kCallbackNone:
return true;
default:
abort();
}
}
static bool callback_call(Callback *callback, int argcount_in,
typval_T *argvars_in, typval_T *rettv)
{
partial_T *partial;
char_u *name;
switch (callback->type) {
case kCallbackFuncref:
name = callback->data.funcref;
partial = NULL;
break;
case kCallbackPartial:
partial = callback->data.partial;
name = partial->pt_name;
break;
case kCallbackNone:
return false;
break;
default:
abort();
}
int dummy;
return call_func(name, (int)STRLEN(name), rettv, argcount_in, argvars_in,
curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy,
true, partial, NULL);
}
static bool set_ref_in_callback(Callback *callback, int copyID,
ht_stack_T **ht_stack,
list_stack_T **list_stack)
{
typval_T tv;
switch (callback->type) {
case kCallbackFuncref:
case kCallbackNone:
break;
case kCallbackPartial:
tv.v_type = VAR_PARTIAL;
tv.vval.v_partial = callback->data.partial;
return set_ref_in_item(&tv, copyID, ht_stack, list_stack);
break;
default:
abort();
}
return false;
}
/// "timer_start(timeout, callback, opts)" function
static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
@ -16998,16 +17129,10 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
if (argvars[1].v_type != VAR_FUNC && argvars[1].v_type != VAR_STRING) {
EMSG2(e_invarg2, "funcref");
Callback callback;
if (!callback_from_typval(&callback, &argvars[1])) {
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->refcount = 1;
@ -17015,7 +17140,7 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr)
timer->repeat_count = repeat;
timer->timeout = timeout;
timer->timer_id = last_timer_id++;
timer->callback = func;
timer->callback = callback;
time_watcher_init(&main_loop, &timer->tw, timer);
timer->tw.events = multiqueue_new_child(main_loop.events);
@ -17059,15 +17184,14 @@ static void timer_due_cb(TimeWatcher *tw, void *data)
timer_stop(timer);
}
typval_T argv[1];
typval_T argv[2];
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);
callback_call(&timer->callback, 1, argv, &rettv);
clear_tv(&rettv);
if (!timer->stopped && timer->timeout == 0) {
@ -17096,7 +17220,7 @@ static void timer_close_cb(TimeWatcher *tw, void *data)
{
timer_T *timer = (timer_T *)data;
multiqueue_free(timer->tw.events);
user_func_unref(timer->callback);
callback_free(&timer->callback);
pmap_del(uint64_t)(timers, timer->timer_id);
timer_decref(timer);
}
@ -18531,72 +18655,78 @@ handle_subscript (
}
// Turn "dict.Func" into a partial for "Func" bound to "dict".
// Don't do this when "Func" is already a partial that was bound
// explicitly (pt_auto is false).
if (selfdict != NULL
&& (rettv->v_type == VAR_FUNC
|| (rettv->v_type == VAR_PARTIAL
&& (rettv->vval.v_partial->pt_auto
|| rettv->vval.v_partial->pt_dict == NULL)))) {
char_u *fname = rettv->v_type == VAR_FUNC ? rettv->vval.v_string
: rettv->vval.v_partial->pt_name;
char_u *tofree = NULL;
ufunc_T *fp;
char_u fname_buf[FLEN_FIXED + 1];
int error;
// Translate "s:func" to the stored function name.
fname = fname_trans_sid(fname, fname_buf, &tofree, &error);
fp = find_func(fname);
xfree(tofree);
// Turn "dict.Func" into a partial for "Func" with "dict".
if (fp != NULL && (fp->uf_flags & FC_DICT)) {
partial_T *pt = (partial_T *)xcalloc(1, sizeof(partial_T));
if (pt != NULL) {
pt->pt_refcount = 1;
pt->pt_dict = selfdict;
pt->pt_auto = true;
selfdict = NULL;
if (rettv->v_type == VAR_FUNC) {
// Just a function: Take over the function name and use selfdict.
pt->pt_name = rettv->vval.v_string;
} else {
partial_T *ret_pt = rettv->vval.v_partial;
int i;
// Partial: copy the function name, use selfdict and copy
// args. Can't take over name or args, the partial might
// be referenced elsewhere.
pt->pt_name = vim_strsave(ret_pt->pt_name);
func_ref(pt->pt_name);
if (ret_pt->pt_argc > 0) {
size_t arg_size = sizeof(typval_T) * ret_pt->pt_argc;
pt->pt_argv = (typval_T *)xmalloc(arg_size);
if (pt->pt_argv == NULL) {
// out of memory: drop the arguments
pt->pt_argc = 0;
} else {
pt->pt_argc = ret_pt->pt_argc;
for (i = 0; i < pt->pt_argc; i++) {
copy_tv(&ret_pt->pt_argv[i], &pt->pt_argv[i]);
}
}
}
partial_unref(ret_pt);
}
rettv->v_type = VAR_PARTIAL;
rettv->vval.v_partial = pt;
}
}
|| rettv->v_type == VAR_PARTIAL)) {
set_selfdict(rettv, selfdict);
}
dict_unref(selfdict);
return ret;
}
static void set_selfdict(typval_T *rettv, dict_T *selfdict) {
// Don't do this when "dict.Func" is already a partial that was bound
// explicitly (pt_auto is false).
if (rettv->v_type == VAR_PARTIAL && !rettv->vval.v_partial->pt_auto
&& rettv->vval.v_partial->pt_dict != NULL) {
return;
}
char_u *fname = rettv->v_type == VAR_FUNC ? rettv->vval.v_string
: rettv->vval.v_partial->pt_name;
char_u *tofree = NULL;
ufunc_T *fp;
char_u fname_buf[FLEN_FIXED + 1];
int error;
// Translate "s:func" to the stored function name.
fname = fname_trans_sid(fname, fname_buf, &tofree, &error);
fp = find_func(fname);
xfree(tofree);
// Turn "dict.Func" into a partial for "Func" with "dict".
if (fp != NULL && (fp->uf_flags & FC_DICT)) {
partial_T *pt = (partial_T *)xcalloc(1, sizeof(partial_T));
if (pt != NULL) {
pt->pt_refcount = 1;
pt->pt_dict = selfdict;
(selfdict->dv_refcount)++;
pt->pt_auto = true;
if (rettv->v_type == VAR_FUNC) {
// Just a function: Take over the function name and use selfdict.
pt->pt_name = rettv->vval.v_string;
} else {
partial_T *ret_pt = rettv->vval.v_partial;
int i;
// Partial: copy the function name, use selfdict and copy
// args. Can't take over name or args, the partial might
// be referenced elsewhere.
pt->pt_name = vim_strsave(ret_pt->pt_name);
func_ref(pt->pt_name);
if (ret_pt->pt_argc > 0) {
size_t arg_size = sizeof(typval_T) * ret_pt->pt_argc;
pt->pt_argv = (typval_T *)xmalloc(arg_size);
if (pt->pt_argv == NULL) {
// out of memory: drop the arguments
pt->pt_argc = 0;
} else {
pt->pt_argc = ret_pt->pt_argc;
for (i = 0; i < pt->pt_argc; i++) {
copy_tv(&ret_pt->pt_argv[i], &pt->pt_argv[i]);
}
}
}
partial_unref(ret_pt);
}
rettv->v_type = VAR_PARTIAL;
rettv->vval.v_partial = pt;
}
}
}
/*
* Free the memory for a variable type-value.
*/
@ -22444,10 +22574,9 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, char_u *flags)
}
static inline TerminalJobData *common_job_init(char **argv,
ufunc_T *on_stdout,
ufunc_T *on_stderr,
ufunc_T *on_exit,
dict_T *self,
Callback on_stdout,
Callback on_stderr,
Callback on_exit,
bool pty,
bool rpc,
bool detach,
@ -22458,7 +22587,6 @@ static inline TerminalJobData *common_job_init(char **argv,
data->on_stdout = on_stdout;
data->on_stderr = on_stderr;
data->on_exit = on_exit;
data->self = self;
data->events = multiqueue_new_child(main_loop.events);
data->rpc = rpc;
if (pty) {
@ -22483,8 +22611,8 @@ static inline TerminalJobData *common_job_init(char **argv,
/// common code for getting job callbacks for jobstart, termopen and rpcstart
///
/// @return true/false on success/failure.
static inline bool common_job_callbacks(dict_T *vopts, ufunc_T **on_stdout,
ufunc_T **on_stderr, ufunc_T **on_exit)
static inline bool common_job_callbacks(dict_T *vopts, Callback *on_stdout,
Callback *on_stderr, Callback *on_exit)
{
if (get_dict_callback(vopts, "on_stdout", on_stdout)
&& get_dict_callback(vopts, "on_stderr", on_stderr)
@ -22492,15 +22620,10 @@ static inline bool common_job_callbacks(dict_T *vopts, ufunc_T **on_stdout,
vopts->dv_refcount++;
return true;
}
if (*on_stdout) {
user_func_unref(*on_stdout);
}
if (*on_stderr) {
user_func_unref(*on_stderr);
}
if (*on_exit) {
user_func_unref(*on_exit);
}
callback_free(on_stdout);
callback_free(on_stderr);
callback_free(on_exit);
return false;
}
@ -22555,19 +22678,10 @@ static inline bool common_job_start(TerminalJobData *data, typval_T *rettv)
static inline void free_term_job_data_event(void **argv)
{
TerminalJobData *data = argv[0];
if (data->on_stdout) {
user_func_unref(data->on_stdout);
}
if (data->on_stderr) {
user_func_unref(data->on_stderr);
}
if (data->on_exit) {
user_func_unref(data->on_exit);
}
callback_free(&data->on_stdout);
callback_free(&data->on_stderr);
callback_free(&data->on_exit);
if (data->self) {
dict_unref(data->self);
}
multiqueue_free(data->events);
pmap_del(uint64_t)(jobs, data->id);
xfree(data);
@ -22581,8 +22695,9 @@ static inline void free_term_job_data(TerminalJobData *data)
}
// vimscript job callbacks must be executed on Nvim main loop
static inline void process_job_event(TerminalJobData *data, ufunc_T *callback,
const char *type, char *buf, size_t count, int status)
static inline void process_job_event(TerminalJobData *data, Callback *callback,
const char *type, char *buf, size_t count,
int status)
{
JobEvent event_data;
event_data.received = NULL;
@ -22622,18 +22737,19 @@ static void on_job_stdout(Stream *stream, RBuffer *buf, size_t count,
void *job, bool eof)
{
TerminalJobData *data = job;
on_job_output(stream, job, buf, count, eof, data->on_stdout, "stdout");
on_job_output(stream, job, buf, count, eof, &data->on_stdout, "stdout");
}
static void on_job_stderr(Stream *stream, RBuffer *buf, size_t count,
void *job, bool eof)
{
TerminalJobData *data = job;
on_job_output(stream, job, buf, count, eof, data->on_stderr, "stderr");
on_job_output(stream, job, buf, count, eof, &data->on_stderr, "stderr");
}
static void on_job_output(Stream *stream, TerminalJobData *data, RBuffer *buf,
size_t count, bool eof, ufunc_T *callback, const char *type)
size_t count, bool eof, Callback *callback,
const char *type)
{
if (eof) {
return;
@ -22650,7 +22766,7 @@ static void on_job_output(Stream *stream, TerminalJobData *data, RBuffer *buf,
terminal_receive(data->term, ptr, count);
}
if (callback) {
if (callback->type != kCallbackNone) {
process_job_event(data, callback, type, ptr, count, 0);
}
@ -22674,7 +22790,7 @@ static void eval_job_process_exit_cb(Process *proc, int status, void *d)
*data->status_ptr = status;
}
process_job_event(data, data->on_exit, "exit", NULL, 0, status);
process_job_event(data, &data->on_exit, "exit", NULL, 0, status);
term_job_data_decref(data);
}
@ -22733,38 +22849,30 @@ static void on_job_event(JobEvent *ev)
return;
}
typval_T argv[3];
int argc = ev->callback->uf_args.ga_len;
typval_T argv[4];
if (argc > 0) {
argv[0].v_type = VAR_NUMBER;
argv[0].v_lock = 0;
argv[0].vval.v_number = ev->data->id;
argv[0].v_type = VAR_NUMBER;
argv[0].v_lock = 0;
argv[0].vval.v_number = ev->data->id;
if (ev->received) {
argv[1].v_type = VAR_LIST;
argv[1].v_lock = 0;
argv[1].vval.v_list = ev->received;
argv[1].vval.v_list->lv_refcount++;
} else {
argv[1].v_type = VAR_NUMBER;
argv[1].v_lock = 0;
argv[1].vval.v_number = ev->status;
}
if (argc > 1) {
if (ev->received) {
argv[1].v_type = VAR_LIST;
argv[1].v_lock = 0;
argv[1].vval.v_list = ev->received;
argv[1].vval.v_list->lv_refcount++;
} else {
argv[1].v_type = VAR_NUMBER;
argv[1].v_lock = 0;
argv[1].vval.v_number = ev->status;
}
}
if (argc > 2) {
argv[2].v_type = VAR_STRING;
argv[2].v_lock = 0;
argv[2].vval.v_string = (uint8_t *)ev->type;
}
argv[2].v_type = VAR_STRING;
argv[2].v_lock = 0;
argv[2].vval.v_string = (uint8_t *)ev->type;
typval_T rettv;
init_tv(&rettv);
call_user_func(ev->callback, argc, argv, &rettv, curwin->w_cursor.lnum,
curwin->w_cursor.lnum, ev->data->self);
callback_call(ev->callback, 3, argv, &rettv);
clear_tv(&rettv);
}
@ -22888,7 +22996,7 @@ static void dictwatcher_notify(dict_T *dict, const char *key, typval_T *newtv,
typval_T *oldtv)
FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_NONNULL_ARG(2)
{
typval_T argv[3];
typval_T argv[4];
for (size_t i = 0; i < ARRAY_SIZE(argv); i++) {
init_tv(argv + i);
}
@ -22921,8 +23029,7 @@ static void dictwatcher_notify(dict_T *dict, const char *key, typval_T *newtv,
if (!watcher->busy && dictwatcher_matches(watcher, key)) {
init_tv(&rettv);
watcher->busy = true;
call_user_func(watcher->callback, ARRAY_SIZE(argv), argv, &rettv,
curwin->w_cursor.lnum, curwin->w_cursor.lnum, NULL);
callback_call(&watcher->callback, 3, argv, &rettv);
watcher->busy = false;
clear_tv(&rettv);
}
@ -22953,7 +23060,7 @@ static bool dictwatcher_matches(DictWatcher *watcher, const char *key)
static void dictwatcher_free(DictWatcher *watcher)
FUNC_ATTR_NONNULL_ALL
{
user_func_unref(watcher->callback);
callback_free(&watcher->callback);
xfree(watcher->key_pattern);
xfree(watcher);
}

View File

@ -41,6 +41,7 @@ func Test_with_partial_callback()
function s:meow.bite(...)
let s:val += 1
endfunction
call timer_start(50, s:meow.bite)
sleep 200m
call assert_equal(1, s:val)

View File

@ -18,7 +18,7 @@ describe('jobs', function()
channel = nvim('get_api_info')[1]
nvim('set_var', 'channel', channel)
source([[
function! s:OnEvent(id, data, event)
function! s:OnEvent(id, data, event) dict
let userdata = get(self, 'user')
call rpcnotify(g:channel, a:event, userdata, a:data)
endfunction
@ -265,9 +265,12 @@ describe('jobs', function()
eq({'notification', 'exit', {45, 10}}, next_msg())
end)
it('cannot redefine callbacks being used by a job', function()
it('can redefine callbacks being used by a job', function()
local screen = Screen.new()
screen:attach()
screen:set_default_attr_ids({
[1] = {bold=true, foreground=Screen.colors.Blue},
})
local script = [[
function! g:JobHandler(job_id, data, event)
endfunction
@ -283,20 +286,20 @@ describe('jobs', function()
feed(':function! g:JobHandler(job_id, data, event)<cr>')
feed(':endfunction<cr>')
screen:expect([[
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
:function! g:JobHandler(job_id, data, event) |
: :endfunction |
E127: Cannot redefine function JobHandler: It is in u|
se |
Press ENTER or type command to continue^ |
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
|
]])
end)
@ -317,7 +320,7 @@ describe('jobs', function()
source([[
let g:dict = {'id': 10}
let g:exits = 0
function g:dict.on_exit(id, code)
function g:dict.on_exit(id, code, event)
if a:code != 5
throw 'Error!'
endif
@ -365,7 +368,7 @@ describe('jobs', function()
eq({'notification', 'wait', {{-2}}}, next_msg())
end)
it('can be called recursively', function()
pending('can be called recursively', function()
source([[
let g:opts = {}
let g:counter = 0

View File

@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each)
local clear, nvim, source = helpers.clear, helpers.nvim, helpers.source
local eq, next_msg = helpers.eq, helpers.next_message
local exc_exec = helpers.exc_exec
local command = helpers.command
describe('dictionary change notifications', function()
@ -229,11 +230,9 @@ describe('dictionary change notifications', function()
exc_exec('call dictwatcherdel(g:, "invalid_key", "g:Watcher2")'))
end)
it("fails to add/remove if the callback doesn't exist", function()
eq("Vim(call):Function g:InvalidCb doesn't exist",
exc_exec('call dictwatcheradd(g:, "key", "g:InvalidCb")'))
eq("Vim(call):Function g:InvalidCb doesn't exist",
exc_exec('call dictwatcherdel(g:, "key", "g:InvalidCb")'))
it("does not fail to add/remove if the callback doesn't exist", function()
command('call dictwatcheradd(g:, "key", "g:InvalidCb")')
command('call dictwatcherdel(g:, "key", "g:InvalidCb")')
end)
it('fails with empty keys', function()
@ -243,15 +242,18 @@ describe('dictionary change notifications', function()
exc_exec('call dictwatcherdel(g:, "", "g:Watcher1")'))
end)
it('fails to replace a watcher function', function()
it('does not fail to replace a watcher function', function()
source([[
function! g:ReplaceWatcher2()
function! g:Watcher2()
function! g:Watcher2(dict, key, value)
call rpcnotify(g:channel, '2b', a:key, a:value)
endfunction
endfunction
]])
eq("Vim(function):E127: Cannot redefine function Watcher2: It is in use",
exc_exec('call g:ReplaceWatcher2()'))
command('call g:ReplaceWatcher2()')
command('let g:key = "value"')
eq({'notification', '2b', {'key', {old = 'v2', new = 'value'}}}, next_msg())
end)
end)
end)