To: vim_dev@googlegroups.com Subject: Patch 7.4.1578 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 7.4.1578 Problem: There is no way to invoke a function later or periodically. Solution: Add timer support. Files: src/eval.c, src/ex_cmds2.c, src/screen.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/structs.h, src/version.c, src/testdir/test_alot.vim, src/testdir/test_timers.vim, runtime/doc/eval.txt *** ../vim-7.4.1577/src/eval.c 2016-03-15 19:33:30.057375668 +0100 --- src/eval.c 2016-03-15 22:25:38.837062813 +0100 *************** *** 794,799 **** --- 794,803 ---- 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,8409 **** --- 8408,8417 ---- #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,13653 **** --- 13656,13664 ---- #ifdef HAVE_TGETENT "tgetent", #endif + #ifdef FEAT_TIMERS + "timers", + #endif #ifdef FEAT_TITLE "title", #endif *************** *** 20077,20082 **** --- 20088,20169 ---- } #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 */ *** ../vim-7.4.1577/src/ex_cmds2.c 2016-03-12 22:47:10.548935254 +0100 --- src/ex_cmds2.c 2016-03-15 22:26:48.716325705 +0100 *************** *** 1088,1093 **** --- 1088,1261 ---- # 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 *** ../vim-7.4.1577/src/screen.c 2016-02-23 14:52:31.897232046 +0100 --- src/screen.c 2016-03-15 21:43:38.011555927 +0100 *************** *** 411,416 **** --- 411,437 ---- } /* + * 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. *** ../vim-7.4.1577/src/ex_docmd.c 2016-03-12 22:11:34.251300154 +0100 --- src/ex_docmd.c 2016-03-15 21:49:58.779554432 +0100 *************** *** 8894,8905 **** do_sleep(long msec) { long done; cursor_on(); out_flush(); ! for (done = 0; !got_int && done < msec; done += 1000L) { ! ui_delay(msec - done > 1000L ? 1000L : msec - done, TRUE); ui_breakcheck(); #ifdef MESSAGE_QUEUE /* Process the netbeans and clientserver messages that may have been --- 8894,8915 ---- do_sleep(long msec) { long done; + long wait_now; cursor_on(); out_flush(); ! 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 *** ../vim-7.4.1577/src/feature.h 2016-03-11 22:52:00.718438283 +0100 --- src/feature.h 2016-03-15 22:23:30.982411682 +0100 *************** *** 400,405 **** --- 400,412 ---- #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) *** ../vim-7.4.1577/src/gui.c 2016-03-12 22:11:34.239300280 +0100 --- src/gui.c 2016-03-15 22:27:16.944027974 +0100 *************** *** 2849,2854 **** --- 2849,2883 ---- } } + 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,2891 **** /* Blink when waiting for a character. Probably only does something * for showmatch() */ gui_mch_start_blink(); ! retval = gui_mch_wait_for_chars(wtime); gui_mch_stop_blink(); return retval; } --- 2914,2920 ---- /* Blink when waiting for a character. Probably only does something * for showmatch() */ gui_mch_start_blink(); ! retval = gui_wait_for_chars_or_timer(wtime); gui_mch_stop_blink(); return retval; } *************** *** 2901,2907 **** * '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) retval = OK; #ifdef FEAT_AUTOCMD else if (trigger_cursorhold()) --- 2930,2936 ---- * 'updatetime' and if nothing is typed within that time put the * K_CURSORHOLD key in the input buffer. */ ! if (gui_wait_for_chars_or_timer(p_ut) == OK) retval = OK; #ifdef FEAT_AUTOCMD else if (trigger_cursorhold()) *************** *** 2922,2928 **** { /* Blocking wait. */ before_blocking(); ! retval = gui_mch_wait_for_chars(-1L); } gui_mch_stop_blink(); --- 2951,2957 ---- { /* Blocking wait. */ before_blocking(); ! retval = gui_wait_for_chars_or_timer(-1L); } gui_mch_stop_blink(); *** ../vim-7.4.1577/src/proto/eval.pro 2016-03-14 23:04:49.698923062 +0100 --- src/proto/eval.pro 2016-03-15 21:13:31.718552008 +0100 *************** *** 91,96 **** --- 91,97 ---- 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); *** ../vim-7.4.1577/src/proto/ex_cmds2.pro 2016-03-12 21:28:22.238433457 +0100 --- src/proto/ex_cmds2.pro 2016-03-15 21:51:10.134804835 +0100 *************** *** 18,23 **** --- 18,27 ---- 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,68 **** 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 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); void ex_packloadall(exarg_T *eap); void ex_packadd(exarg_T *eap); void ex_options(exarg_T *eap); --- 64,72 ---- 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 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 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); *** ../vim-7.4.1577/src/proto/screen.pro 2016-01-19 13:21:55.845334290 +0100 --- src/proto/screen.pro 2016-03-15 21:44:51.358784922 +0100 *************** *** 6,11 **** --- 6,12 ---- 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); *** ../vim-7.4.1577/src/structs.h 2016-03-14 23:22:31.219768924 +0100 --- src/structs.h 2016-03-15 22:26:26.348561638 +0100 *************** *** 2953,2955 **** --- 2953,2970 ---- 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 + }; *** ../vim-7.4.1577/src/version.c 2016-03-15 19:33:30.057375668 +0100 --- src/version.c 2016-03-15 22:24:31.429773926 +0100 *************** *** 626,631 **** --- 626,636 ---- #else "-textobjects", #endif + #ifdef FEAT_TIMERS + "+timers", + #else + "-timers", + #endif #ifdef FEAT_TITLE "+title", #else *** ../vim-7.4.1577/src/testdir/test_alot.vim 2016-03-15 17:43:51.633786581 +0100 --- src/testdir/test_alot.vim 2016-03-15 22:55:10.360529500 +0100 *************** *** 19,23 **** --- 19,24 ---- 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 *** ../vim-7.4.1577/src/testdir/test_timers.vim 2016-03-15 23:08:06.210192169 +0100 --- src/testdir/test_timers.vim 2016-03-15 22:47:02.931529790 +0100 *************** *** 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 *** ../vim-7.4.1577/runtime/doc/eval.txt 2016-03-14 23:04:49.702923020 +0100 --- runtime/doc/eval.txt 2016-03-15 22:48:13.314788683 +0100 *************** *** 2117,2125 **** 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} 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} --- 2129,2140 ---- Number number of current window in tab page taglist( {expr}) List list of tags matching {expr} tagfiles() List tags files used 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} *************** *** 6924,6929 **** --- 7023,7055 ---- {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 *** ../vim-7.4.1577/src/version.c 2016-03-15 19:33:30.057375668 +0100 --- src/version.c 2016-03-15 22:24:31.429773926 +0100 *************** *** 745,746 **** --- 750,753 ---- { /* Add new patch number below this line */ + /**/ + 1578, /**/ -- hundred-and-one symptoms of being an internet addict: 52. You ask a plumber how much it would cost to replace the chair in front of your computer with a toilet. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///