1
0
forked from aniani/vim

patch 7.4.1578

Problem:    There is no way to invoke a function later or periodically.
Solution:   Add timer support.
This commit is contained in:
Bram Moolenaar
2016-03-15 23:10:59 +01:00
parent ab1fa3955f
commit 975b5271ee
14 changed files with 438 additions and 18 deletions

View File

@@ -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 foreground. Useful when sent from
*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}]) *systemlist()*
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 |t_RV| and |v:termresponse|.
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.

View File

@@ -794,6 +794,10 @@ static void f_test(typval_T *argvars, typval_T *rettv);
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 *rettv)
}
#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
*/

View File

@@ -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 <math.h>

View File

@@ -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)
for (done = 0; !got_int && done < msec; done += wait_now)
{
ui_delay(msec - done > 1000L ? 1000L : msec - done, TRUE);
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

View File

@@ -399,6 +399,13 @@
# define FEAT_RELTIME
#endif
/*
* +timers timer_start()
*/
#if defined(FEAT_RELTIME) && (defined(UNIX) || defined(WIN32))
# define FEAT_TIMERS
#endif
/*
* +textobjects Text objects: "vaw", "das", etc.
*/

View File

@@ -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();

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -410,6 +410,27 @@ redraw_asap(int type)
return ret;
}
/*
* 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.

View File

@@ -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
};

View File

@@ -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

View 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

View File

@@ -626,6 +626,11 @@ static char *(features[]) =
#else
"-textobjects",
#endif
#ifdef FEAT_TIMERS
"+timers",
#else
"-timers",
#endif
#ifdef FEAT_TITLE
"+title",
#else
@@ -743,6 +748,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
1578,
/**/
1577,
/**/