]> granicus.if.org Git - vim/commitdiff
patch 7.4.1578 v7.4.1578
authorBram Moolenaar <Bram@vim.org>
Tue, 15 Mar 2016 22:10:59 +0000 (23:10 +0100)
committerBram Moolenaar <Bram@vim.org>
Tue, 15 Mar 2016 22:10:59 +0000 (23:10 +0100)
Problem:    There is no way to invoke a function later or periodically.
Solution:   Add timer support.

14 files changed:
runtime/doc/eval.txt
src/eval.c
src/ex_cmds2.c
src/ex_docmd.c
src/feature.h
src/gui.c
src/proto/eval.pro
src/proto/ex_cmds2.pro
src/proto/screen.pro
src/screen.c
src/structs.h
src/testdir/test_alot.vim
src/testdir/test_timers.vim [new file with mode: 0644]
src/version.c

index 6cc82b445c2aea653a032893333ae687a16d68e3..43e25faad11efa9aa329f265ce02f957ef015c30 100644 (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.
index 38c192ed1b328436313a77db474d981161c55a2f..506932403a9da1a5246f6f905f310c563db3e00b 100644 (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
  */
index d7bf60901e6832f439e2761e98178a07c699f2dd..018f44cc29ad149f1717e936354ed2a4632e02db 100644 (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>
index 9b116e2bde5751cbd7f79128eee7c6e93b7a86e2..12730a93d2066eab137a91d43eb9f6b41acf5564 100644 (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
index 957bbf27d49b6cbc0e57f16d251fa8f806905385..455ab20ea06a65c863d3e587b9b62e4210d7ff12 100644 (file)
 # 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.
  */
index 2701265f1d91208388f28833726583565fbb31ab..f52d217aa8e3619aaff627924ec82d7d20a5c2f7 100644 (file)
--- 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();
index dee95c4ed2c873bbadf383de3a069eb26a9f2c4a..1de7a6d9dc83f0e9db213e6d61b1c03f72a2c6bd 100644 (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);
index 14546b50f4101f9834b08a8a6edb66b34193b4f9..5e5b4d4b7396771729e7821b5340dd9e270c5393 100644 (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);
index 6330fdcbbf8bff979dee24c902506b875bb01ea5..aad8187a1dc86d407c799e7b9095511de7ab69f1 100644 (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);
index b293869cb23934546941b72010b051bc747ce3fb..c1eb1c459c8d76292e9f400cc91f238737a1d7b9 100644 (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.
index ab707d8c3f721574feb3135e6d076af5fc05b798..6263242f691a350e4b7535a689fb6c657b7baf07 100644 (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
+};
index bd74afc4aa936bbcf8c805b73d3c09a904b1fc03..361559868cbedbc4e0aacf4296e233a63039829f 100644 (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
diff --git a/src/testdir/test_timers.vim b/src/testdir/test_timers.vim
new file mode 100644 (file)
index 0000000..9f58a35
--- /dev/null
@@ -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
index 5c26ce61081ffee13ff732e813efbebefdf1c91d..2d033163dfb1232a7e064efc42b283036ee8ae28 100644 (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,
 /**/