changeset 8577:63dc856bd13d v7.4.1578

commit https://github.com/vim/vim/commit/975b5271eed4fa0500c24a8f37be0b1797cb9db7 Author: Bram Moolenaar <Bram@vim.org> 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.
author Christian Brabandt <cb@256bit.org>
date Tue, 15 Mar 2016 23:15:05 +0100
parents 3fd90fff8ab5
children 44fc12f442da
files 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 src/version.c
diffstat 14 files changed, 440 insertions(+), 20 deletions(-) [+]
line wrap: on
line diff
--- 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.
--- 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
  */
--- 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 <math.h>
--- 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
--- 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)
--- 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();
--- 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);
--- 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);
--- 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);
--- 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.
--- 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
+};
--- 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
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
--- 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,