# HG changeset patch # User Christian Brabandt # Date 1458080105 -3600 # Node ID 63dc856bd13d87a3f69131c1ce1d726d12dabc63 # Parent 3fd90fff8ab5c82d8aeef6cb9cc855accb42b858 commit https://github.com/vim/vim/commit/975b5271eed4fa0500c24a8f37be0b1797cb9db7 Author: Bram Moolenaar Date: Tue Mar 15 23:10:59 2016 +0100 patch 7.4.1578 Problem: There is no way to invoke a function later or periodically. Solution: Add timer support. diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1,4 +1,4 @@ -*eval.txt* For Vim version 7.4. Last change: 2016 Mar 14 +*eval.txt* For Vim version 7.4. Last change: 2016 Mar 15 VIM REFERENCE MANUAL by Bram Moolenaar @@ -350,10 +350,6 @@ This works like: > : let index = index + 1 :endwhile -Note that all items in the list should be of the same type, otherwise this -results in error |E706|. To avoid this |:unlet| the variable at the end of -the loop. - If all you want to do is modify each item in the list then the |map()| function will be a simpler method than a for loop. @@ -2133,9 +2129,12 @@ tabpagewinnr( {tabarg}[, {arg}]) Number number of current window in tab page taglist( {expr}) List list of tags matching {expr} tagfiles() List tags files used -tempname() String name for a temporary file tan( {expr}) Float tangent of {expr} tanh( {expr}) Float hyperbolic tangent of {expr} +tempname() String name for a temporary file +timer_start( {time}, {callback} [, {options}]) + Number create a timer +timer_stop( {timer}) none stop a timer tolower( {expr}) String the String {expr} switched to lowercase toupper( {expr}) String the String {expr} switched to uppercase tr( {src}, {fromstr}, {tostr}) String translate chars of {src} in {fromstr} @@ -3572,8 +3571,15 @@ foreground() Move the Vim window to the *function()* *E700* *E922* *E923* function({name} [, {arglist}] [, {dict}]) Return a |Funcref| variable that refers to function {name}. - {name} can be a user defined function or an internal function. - + {name} can be the name of a user defined function or an + internal function. + + {name} can also be a Funcref, also a partial. When it is a + partial the dict stored in it will be used and the {dict} + argument is not allowed. E.g.: > + let FuncWithArg = function(dict.Func, [arg]) + let Broken = function(dict.Func, [arg], dict) +< When {arglist} or {dict} is present this creates a partial. That mans the argument list and/or the dictionary is stored in the Funcref and will be used when the Funcref is called. @@ -3598,6 +3604,10 @@ function({name} [, {arglist}] [, {dict}] let Func = function('Callback', context) ... call Func() " will echo: called for example +< The use of function() is not needed when there are no extra + arguments, these two are equivalent: > + let Func = function('Callback', context) + let Func = context.Callback < The argument list and the Dictionary can be combined: > function Callback(arg1, count) dict @@ -4523,13 +4533,13 @@ job_info({job}) *job_info()* "status" what |job_status()| returns "channel" what |job_getchannel()| returns "exitval" only valid when "status" is "dead" - "exit-cb" function to be called on exit + "exit_cb" function to be called on exit "stoponexit" |job-stoponexit| job_setoptions({job}, {options}) *job_setoptions()* Change options for {job}. Supported are: "stoponexit" |job-stoponexit| - "exit-cb" |job-exit-cb| + "exit_cb" |job-exit_cb| job_start({command} [, {options}]) *job_start()* Start a job and return a Job object. Unlike |system()| and @@ -6897,8 +6907,7 @@ systemlist({expr} [, {input}]) *syste is the same as |readfile()| will output with {binary} argument set to "b". - Returns an empty string on error, so be careful not to run - into |E706|. + Returns an empty string on error. tabpagebuflist([{arg}]) *tabpagebuflist()* @@ -7014,6 +7023,33 @@ tanh({expr}) *tanh()* {only available when compiled with the |+float| feature} + *timer_start()* +timer_start({time}, {callback} [, {options}]) + Create a timer and return the timer ID. + + {time} is the waiting time in milliseconds. This is the + minimum time before invoking the callback. When the system is + busy or Vim is not waiting for input the time will be longer. + + {callback} is the function to call. It can be the name of a + function or a Funcref. It is called with one argument, which + is the timer ID. The callback is only invoked when Vim is + waiting for input. + + {options} is a dictionary. Supported entries: + "repeat" Number of times to repeat calling the + callback. -1 means forever. + + Example: > + func MyHandler(timer) + echo 'Handler called' + endfunc + let timer = timer_start(500, 'MyHandler', + \ {'repeat': 3}) +< This will invoke MyHandler() three times at 500 msec + intervals. + {only available when compiled with the |+timers| feature} + tolower({expr}) *tolower()* The result is a copy of the String given, with all uppercase characters turned into lowercase (just like applying |gu| to @@ -7570,6 +7606,7 @@ termresponse Compiled with support for textobjects Compiled with support for |text-objects|. tgetent Compiled with tgetent support, able to use a termcap or terminfo file. +timers Compiled with |timer_start()| support. title Compiled with window title support |'title'|. toolbar Compiled with support for |gui-toolbar|. unix Unix version of Vim. diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -794,6 +794,10 @@ static void f_test(typval_T *argvars, ty static void f_tan(typval_T *argvars, typval_T *rettv); static void f_tanh(typval_T *argvars, typval_T *rettv); #endif +#ifdef FEAT_TIMERS +static void f_timer_start(typval_T *argvars, typval_T *rettv); +static void f_timer_stop(typval_T *argvars, typval_T *rettv); +#endif static void f_tolower(typval_T *argvars, typval_T *rettv); static void f_toupper(typval_T *argvars, typval_T *rettv); static void f_tr(typval_T *argvars, typval_T *rettv); @@ -8404,6 +8408,10 @@ static struct fst #endif {"tempname", 0, 0, f_tempname}, {"test", 1, 1, f_test}, +#ifdef FEAT_TIMERS + {"timer_start", 2, 3, f_timer_start}, + {"timer_stop", 1, 1, f_timer_stop}, +#endif {"tolower", 1, 1, f_tolower}, {"toupper", 1, 1, f_toupper}, {"tr", 3, 3, f_tr}, @@ -13648,6 +13656,9 @@ f_has(typval_T *argvars, typval_T *rettv #ifdef HAVE_TGETENT "tgetent", #endif +#ifdef FEAT_TIMERS + "timers", +#endif #ifdef FEAT_TITLE "title", #endif @@ -20077,6 +20088,82 @@ f_tanh(typval_T *argvars, typval_T *rett } #endif +#if defined(FEAT_JOB_CHANNEL) || defined(FEAT_TIMERS) || defined(PROTO) +/* + * Get a callback from "arg". It can be a Funcref or a function name. + * When "arg" is zero return an empty string. + * Return NULL for an invalid argument. + */ + char_u * +get_callback(typval_T *arg, partial_T **pp) +{ + if (arg->v_type == VAR_PARTIAL && arg->vval.v_partial != NULL) + { + *pp = arg->vval.v_partial; + return (*pp)->pt_name; + } + *pp = NULL; + if (arg->v_type == VAR_FUNC || arg->v_type == VAR_STRING) + return arg->vval.v_string; + if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0) + return (char_u *)""; + EMSG(_("E921: Invalid callback argument")); + return NULL; +} +#endif + +#ifdef FEAT_TIMERS +/* + * "timer_start(time, callback [, options])" function + */ + static void +f_timer_start(typval_T *argvars, typval_T *rettv) +{ + long msec = get_tv_number(&argvars[0]); + timer_T *timer; + int repeat = 0; + char_u *callback; + dict_T *dict; + + 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"); + } + + timer = create_timer(msec, repeat); + callback = get_callback(&argvars[1], &timer->tr_partial); + if (callback == NULL) + { + stop_timer(timer); + rettv->vval.v_number = -1; + } + else + { + timer->tr_callback = vim_strsave(callback); + rettv->vval.v_number = timer->tr_id; + } +} + +/* + * "timer_stop(timer)" function + */ + static void +f_timer_stop(typval_T *argvars, typval_T *rettv UNUSED) +{ + timer_T *timer = find_timer(get_tv_number(&argvars[0])); + + if (timer != NULL) + stop_timer(timer); +} +#endif + /* * "tolower(string)" function */ diff --git a/src/ex_cmds2.c b/src/ex_cmds2.c --- a/src/ex_cmds2.c +++ b/src/ex_cmds2.c @@ -1088,6 +1088,174 @@ profile_zero(proftime_T *tm) # endif /* FEAT_PROFILE || FEAT_RELTIME */ +# if defined(FEAT_TIMERS) || defined(PROTO) +static timer_T *first_timer = NULL; +static int last_timer_id = 0; + +/* + * Insert a timer in the list of timers. + */ + static void +insert_timer(timer_T *timer) +{ + timer->tr_next = first_timer; + timer->tr_prev = NULL; + if (first_timer != NULL) + first_timer->tr_prev = timer; + first_timer = timer; +} + +/* + * Take a timer out of the list of timers. + */ + static void +remove_timer(timer_T *timer) +{ + if (timer->tr_prev == NULL) + first_timer = timer->tr_next; + else + timer->tr_prev->tr_next = timer->tr_next; + if (timer->tr_next != NULL) + timer->tr_next->tr_prev = timer->tr_prev; +} + + static void +free_timer(timer_T *timer) +{ + vim_free(timer->tr_callback); + partial_unref(timer->tr_partial); + vim_free(timer); +} + +/* + * Create a timer and return it. NULL if out of memory. + * Caller should set the callback. + */ + timer_T * +create_timer(long msec, int repeat) +{ + timer_T *timer = (timer_T *)alloc_clear(sizeof(timer_T)); + + if (timer == NULL) + return NULL; + timer->tr_id = ++last_timer_id; + insert_timer(timer); + if (repeat != 0) + { + timer->tr_repeat = repeat - 1; + timer->tr_interval = msec; + } + + profile_setlimit(msec, &timer->tr_due); + return timer; +} + +/* + * Invoke the callback of "timer". + */ + static void +timer_callback(timer_T *timer) +{ + typval_T rettv; + int dummy; + typval_T argv[2]; + + argv[0].v_type = VAR_NUMBER; + argv[0].vval.v_number = timer->tr_id; + argv[1].v_type = VAR_UNKNOWN; + + call_func(timer->tr_callback, (int)STRLEN(timer->tr_callback), + &rettv, 1, argv, 0L, 0L, &dummy, TRUE, + timer->tr_partial, NULL); + clear_tv(&rettv); +} + +/* + * Call timers that are due. + * Return the time in msec until the next timer is due. + */ + long +check_due_timer() +{ + timer_T *timer; + long this_due; + long next_due; + proftime_T now; + int did_one = FALSE; +# ifdef WIN3264 + LARGE_INTEGER fr; + + QueryPerformanceFrequency(&fr); +# endif + while (!got_int) + { + profile_start(&now); + next_due = -1; + for (timer = first_timer; timer != NULL; timer = timer->tr_next) + { +# ifdef WIN3264 + this_due = (long)(((double)(timer->tr_due.QuadPart - now.QuadPart) + / (double)fr.QuadPart) * 1000); +# else + this_due = (timer->tr_due.tv_sec - now.tv_sec) * 1000 + + (timer->tr_due.tv_usec - now.tv_usec) / 1000; +# endif + if (this_due <= 1) + { + remove_timer(timer); + timer_callback(timer); + did_one = TRUE; + if (timer->tr_repeat != 0) + { + profile_setlimit(timer->tr_interval, &timer->tr_due); + if (timer->tr_repeat > 0) + --timer->tr_repeat; + insert_timer(timer); + } + else + free_timer(timer); + /* the callback may do anything, start all over */ + break; + } + if (next_due == -1 || next_due > this_due) + next_due = this_due; + } + if (timer == NULL) + break; + } + + if (did_one) + redraw_after_callback(); + + return next_due; +} + +/* + * Find a timer by ID. Returns NULL if not found; + */ + timer_T * +find_timer(int id) +{ + timer_T *timer; + + for (timer = first_timer; timer != NULL; timer = timer->tr_next) + if (timer->tr_id == id) + break; + return timer; +} + + +/* + * Stop a timer and delete it. + */ + void +stop_timer(timer_T *timer) +{ + remove_timer(timer); + free_timer(timer); +} +# endif + #if defined(FEAT_SYN_HL) && defined(FEAT_RELTIME) && defined(FEAT_FLOAT) # if defined(HAVE_MATH_H) # include diff --git a/src/ex_docmd.c b/src/ex_docmd.c --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -8894,12 +8894,22 @@ ex_sleep(exarg_T *eap) do_sleep(long msec) { long done; + long wait_now; cursor_on(); out_flush(); - for (done = 0; !got_int && done < msec; done += 1000L) - { - ui_delay(msec - done > 1000L ? 1000L : msec - done, TRUE); + for (done = 0; !got_int && done < msec; done += wait_now) + { + wait_now = msec - done > 1000L ? 1000L : msec - done; +#ifdef FEAT_TIMERS + { + long due_time = check_due_timer(); + + if (due_time > 0 && due_time < wait_now) + wait_now = due_time; + } +#endif + ui_delay(wait_now, TRUE); ui_breakcheck(); #ifdef MESSAGE_QUEUE /* Process the netbeans and clientserver messages that may have been diff --git a/src/feature.h b/src/feature.h --- a/src/feature.h +++ b/src/feature.h @@ -400,6 +400,13 @@ #endif /* + * +timers timer_start() + */ +#if defined(FEAT_RELTIME) && (defined(UNIX) || defined(WIN32)) +# define FEAT_TIMERS +#endif + +/* * +textobjects Text objects: "vaw", "das", etc. */ #if defined(FEAT_NORMAL) && defined(FEAT_EVAL) diff --git a/src/gui.c b/src/gui.c --- a/src/gui.c +++ b/src/gui.c @@ -2849,6 +2849,35 @@ gui_insert_lines(int row, int count) } } + static int +gui_wait_for_chars_or_timer(long wtime) +{ +#ifdef FEAT_TIMERS + int due_time; + long remaining = wtime; + + /* When waiting very briefly don't trigger timers. */ + if (wtime >= 0 && wtime < 10L) + return gui_mch_wait_for_chars(wtime); + + while (wtime < 0 || remaining > 0) + { + /* Trigger timers and then get the time in wtime until the next one is + * due. Wait up to that time. */ + due_time = check_due_timer(); + if (due_time <= 0 || (wtime > 0 && due_time > remaining)) + due_time = remaining; + if (gui_mch_wait_for_chars(due_time)) + return TRUE; + if (wtime > 0) + remaining -= due_time; + } + return FALSE; +#else + return gui_mch_wait_for_chars(wtime); +#endif +} + /* * The main GUI input routine. Waits for a character from the keyboard. * wtime == -1 Wait forever. @@ -2885,7 +2914,7 @@ gui_wait_for_chars(long wtime) /* Blink when waiting for a character. Probably only does something * for showmatch() */ gui_mch_start_blink(); - retval = gui_mch_wait_for_chars(wtime); + retval = gui_wait_for_chars_or_timer(wtime); gui_mch_stop_blink(); return retval; } @@ -2901,7 +2930,7 @@ gui_wait_for_chars(long wtime) * 'updatetime' and if nothing is typed within that time put the * K_CURSORHOLD key in the input buffer. */ - if (gui_mch_wait_for_chars(p_ut) == OK) + if (gui_wait_for_chars_or_timer(p_ut) == OK) retval = OK; #ifdef FEAT_AUTOCMD else if (trigger_cursorhold()) @@ -2922,7 +2951,7 @@ gui_wait_for_chars(long wtime) { /* Blocking wait. */ before_blocking(); - retval = gui_mch_wait_for_chars(-1L); + retval = gui_wait_for_chars_or_timer(-1L); } gui_mch_stop_blink(); diff --git a/src/proto/eval.pro b/src/proto/eval.pro --- a/src/proto/eval.pro +++ b/src/proto/eval.pro @@ -91,6 +91,7 @@ void partial_unref(partial_T *pt); void mzscheme_call_vim(char_u *name, typval_T *args, typval_T *rettv); float_T vim_round(float_T f); long do_searchpair(char_u *spat, char_u *mpat, char_u *epat, int dir, char_u *skip, int flags, pos_T *match_pos, linenr_T lnum_stop, long time_limit); +char_u *get_callback(typval_T *arg, partial_T **pp); void set_vim_var_nr(int idx, long val); long get_vim_var_nr(int idx); char_u *get_vim_var_str(int idx); diff --git a/src/proto/ex_cmds2.pro b/src/proto/ex_cmds2.pro --- a/src/proto/ex_cmds2.pro +++ b/src/proto/ex_cmds2.pro @@ -18,6 +18,10 @@ float_T profile_float(proftime_T *tm); void profile_setlimit(long msec, proftime_T *tm); int profile_passed_limit(proftime_T *tm); void profile_zero(proftime_T *tm); +timer_T *create_timer(long msec, int repeats); +long check_due_timer(void); +timer_T *find_timer(int id); +void stop_timer(timer_T *timer); void profile_divide(proftime_T *tm, int count, proftime_T *tm2); void profile_add(proftime_T *tm, proftime_T *tm2); void profile_self(proftime_T *self, proftime_T *total, proftime_T *children); @@ -60,9 +64,9 @@ void ex_argdelete(exarg_T *eap); void ex_listdo(exarg_T *eap); void ex_compiler(exarg_T *eap); void ex_runtime(exarg_T *eap); -int source_runtime(char_u *name, int all); +int source_runtime(char_u *name, int flags); int do_in_path(char_u *path, char_u *name, int flags, void (*callback)(char_u *fname, void *ck), void *cookie); -int do_in_runtimepath(char_u *name, int all, void (*callback)(char_u *fname, void *ck), void *cookie); +int do_in_runtimepath(char_u *name, int flags, void (*callback)(char_u *fname, void *ck), void *cookie); void ex_packloadall(exarg_T *eap); void ex_packadd(exarg_T *eap); void ex_options(exarg_T *eap); diff --git a/src/proto/screen.pro b/src/proto/screen.pro --- a/src/proto/screen.pro +++ b/src/proto/screen.pro @@ -6,6 +6,7 @@ void redraw_all_later(int type); void redraw_curbuf_later(int type); void redraw_buf_later(buf_T *buf, int type); int redraw_asap(int type); +void redraw_after_callback(void); void redrawWinline(linenr_T lnum, int invalid); void update_curbuf(int type); void update_screen(int type); diff --git a/src/screen.c b/src/screen.c --- a/src/screen.c +++ b/src/screen.c @@ -411,6 +411,27 @@ redraw_asap(int type) } /* + * Invoked after an asynchronous callback is called. + * If an echo command was used the cursor needs to be put back where + * it belongs. If highlighting was changed a redraw is needed. + */ + void +redraw_after_callback() +{ + update_screen(0); + setcursor(); + cursor_on(); + out_flush(); +#ifdef FEAT_GUI + if (gui.in_use) + { + gui_update_cursor(TRUE, FALSE); + gui_mch_flush(); + } +#endif +} + +/* * Changed something in the current window, at buffer line "lnum", that * requires that line and possibly other lines to be redrawn. * Used when entering/leaving Insert mode with the cursor on a folded line. diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -2953,3 +2953,18 @@ struct js_reader void *js_cookie; /* can be used by js_fill */ }; typedef struct js_reader js_read_T; + +typedef struct timer_S timer_T; +struct timer_S +{ + int tr_id; +#ifdef FEAT_TIMERS + timer_T *tr_next; + timer_T *tr_prev; + proftime_T tr_due; /* when the callback is to be invoked */ + int tr_repeat; /* number of times to repeat, -1 forever */ + long tr_interval; /* only set when it repeats */ + char_u *tr_callback; /* allocated */ + partial_T *tr_partial; +#endif +}; diff --git a/src/testdir/test_alot.vim b/src/testdir/test_alot.vim --- a/src/testdir/test_alot.vim +++ b/src/testdir/test_alot.vim @@ -19,5 +19,6 @@ source test_searchpos.vim source test_set.vim source test_sort.vim source test_syn_attr.vim +source test_timers.vim source test_undolevels.vim source test_unlet.vim diff --git a/src/testdir/test_timers.vim b/src/testdir/test_timers.vim new file mode 100644 --- /dev/null +++ b/src/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/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -626,6 +626,11 @@ static char *(features[]) = #else "-textobjects", #endif +#ifdef FEAT_TIMERS + "+timers", +#else + "-timers", +#endif #ifdef FEAT_TITLE "+title", #else @@ -744,6 +749,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1578, +/**/ 1577, /**/ 1576,