diff options
Diffstat (limited to 'src/time.c')
-rw-r--r-- | src/time.c | 1155 |
1 files changed, 1155 insertions, 0 deletions
diff --git a/src/time.c b/src/time.c new file mode 100644 index 0000000..c82a79a --- /dev/null +++ b/src/time.c @@ -0,0 +1,1155 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * time.c: functions related to time and timers + */ + +#include "vim.h" + +/* + * Cache of the current timezone name as retrieved from TZ, or an empty string + * where unset, up to 64 octets long including trailing null byte. + */ +#if defined(HAVE_LOCALTIME_R) && defined(HAVE_TZSET) +static char tz_cache[64]; +#endif + +#define FOR_ALL_TIMERS(t) \ + for ((t) = first_timer; (t) != NULL; (t) = (t)->tr_next) + +/* + * Call either localtime(3) or localtime_r(3) from POSIX libc time.h, with the + * latter version preferred for reentrancy. + * + * If we use localtime_r(3) and we have tzset(3) available, check to see if the + * environment variable TZ has changed since the last run, and call tzset(3) to + * update the global timezone variables if it has. This is because the POSIX + * standard doesn't require localtime_r(3) implementations to do that as it + * does with localtime(3), and we don't want to call tzset(3) every time. + */ + static struct tm * +vim_localtime( + const time_t *timep, // timestamp for local representation + struct tm *result UNUSED) // pointer to caller return buffer +{ +#ifdef HAVE_LOCALTIME_R +# ifdef HAVE_TZSET + char *tz; // pointer for TZ environment var + + tz = (char *)mch_getenv((char_u *)"TZ"); + if (tz == NULL) + tz = ""; + if (STRNCMP(tz_cache, tz, sizeof(tz_cache) - 1) != 0) + { + tzset(); + vim_strncpy((char_u *)tz_cache, (char_u *)tz, sizeof(tz_cache) - 1); + } +# endif // HAVE_TZSET + return localtime_r(timep, result); +#else + return localtime(timep); +#endif // HAVE_LOCALTIME_R +} + +/* + * Return the current time in seconds. Calls time(), unless test_settime() + * was used. + */ + time_T +vim_time(void) +{ +# ifdef FEAT_EVAL + return time_for_testing == 0 ? time(NULL) : time_for_testing; +# else + return time(NULL); +# endif +} + +/* + * Replacement for ctime(), which is not safe to use. + * Requires strftime(), otherwise returns "(unknown)". + * If "thetime" is invalid returns "(invalid)". Never returns NULL. + * When "add_newline" is TRUE add a newline like ctime() does. + * Uses a static buffer. + */ + char * +get_ctime(time_t thetime, int add_newline) +{ + static char buf[100]; // hopefully enough for every language +#ifdef HAVE_STRFTIME + struct tm tmval; + struct tm *curtime; + + curtime = vim_localtime(&thetime, &tmval); + // MSVC returns NULL for an invalid value of seconds. + if (curtime == NULL) + vim_strncpy((char_u *)buf, (char_u *)_("(Invalid)"), sizeof(buf) - 2); + else + { + // xgettext:no-c-format + if (strftime(buf, sizeof(buf) - 2, _("%a %b %d %H:%M:%S %Y"), curtime) + == 0) + { + // Quoting "man strftime": + // > If the length of the result string (including the terminating + // > null byte) would exceed max bytes, then strftime() returns 0, + // > and the contents of the array are undefined. + vim_strncpy((char_u *)buf, (char_u *)_("(Invalid)"), + sizeof(buf) - 2); + } +# ifdef MSWIN + if (enc_codepage >= 0 && (int)GetACP() != enc_codepage) + { + char_u *to_free = NULL; + int len; + + acp_to_enc((char_u *)buf, (int)strlen(buf), &to_free, &len); + if (to_free != NULL) + { + STRNCPY(buf, to_free, sizeof(buf) - 2); + vim_free(to_free); + } + } +# endif + } +#else + STRCPY(buf, "(unknown)"); +#endif + if (add_newline) + STRCAT(buf, "\n"); + return buf; +} + +#if defined(FEAT_EVAL) || defined(PROTO) + +#if defined(MACOS_X) +# include <time.h> // for time_t +#endif + +/* + * "localtime()" function + */ + void +f_localtime(typval_T *argvars UNUSED, typval_T *rettv) +{ + rettv->vval.v_number = (varnumber_T)time(NULL); +} + +# if defined(FEAT_RELTIME) +/* + * Convert a List to proftime_T. + * Return FAIL when there is something wrong. + */ + static int +list2proftime(typval_T *arg, proftime_T *tm) +{ + long n1, n2; + int error = FALSE; + + if (arg->v_type != VAR_LIST || arg->vval.v_list == NULL + || arg->vval.v_list->lv_len != 2) + return FAIL; + n1 = list_find_nr(arg->vval.v_list, 0L, &error); + n2 = list_find_nr(arg->vval.v_list, 1L, &error); +# ifdef MSWIN + tm->HighPart = n1; + tm->LowPart = n2; +# else + tm->tv_sec = n1; + tm->tv_usec = n2; +# endif + return error ? FAIL : OK; +} +# endif // FEAT_RELTIME + +/* + * "reltime()" function + */ + void +f_reltime(typval_T *argvars UNUSED, typval_T *rettv UNUSED) +{ +# ifdef FEAT_RELTIME + proftime_T res; + proftime_T start; + long n1, n2; + + if (rettv_list_alloc(rettv) == FAIL) + return; + + if (in_vim9script() + && (check_for_opt_list_arg(argvars, 0) == FAIL + || (argvars[0].v_type != VAR_UNKNOWN + && check_for_opt_list_arg(argvars, 1) == FAIL))) + return; + + if (argvars[0].v_type == VAR_UNKNOWN) + { + // No arguments: get current time. + profile_start(&res); + } + else if (argvars[1].v_type == VAR_UNKNOWN) + { + if (list2proftime(&argvars[0], &res) == FAIL) + { + if (in_vim9script()) + emsg(_(e_invalid_argument)); + return; + } + profile_end(&res); + } + else + { + // Two arguments: compute the difference. + if (list2proftime(&argvars[0], &start) == FAIL + || list2proftime(&argvars[1], &res) == FAIL) + { + if (in_vim9script()) + emsg(_(e_invalid_argument)); + return; + } + profile_sub(&res, &start); + } + +# ifdef MSWIN + n1 = res.HighPart; + n2 = res.LowPart; +# else + n1 = res.tv_sec; + n2 = res.tv_usec; +# endif + list_append_number(rettv->vval.v_list, (varnumber_T)n1); + list_append_number(rettv->vval.v_list, (varnumber_T)n2); +# endif +} + +/* + * "reltimefloat()" function + */ + void +f_reltimefloat(typval_T *argvars UNUSED, typval_T *rettv) +{ +# ifdef FEAT_RELTIME + proftime_T tm; +# endif + + rettv->v_type = VAR_FLOAT; + rettv->vval.v_float = 0; +# ifdef FEAT_RELTIME + if (in_vim9script() && check_for_list_arg(argvars, 0) == FAIL) + return; + + if (list2proftime(&argvars[0], &tm) == OK) + rettv->vval.v_float = profile_float(&tm); + else if (in_vim9script()) + emsg(_(e_invalid_argument)); +# endif +} + +/* + * "reltimestr()" function + */ + void +f_reltimestr(typval_T *argvars UNUSED, typval_T *rettv) +{ +# ifdef FEAT_RELTIME + proftime_T tm; +# endif + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; +# ifdef FEAT_RELTIME + if (in_vim9script() && check_for_list_arg(argvars, 0) == FAIL) + return; + + if (list2proftime(&argvars[0], &tm) == OK) + rettv->vval.v_string = vim_strsave((char_u *)profile_msg(&tm)); + else if (in_vim9script()) + emsg(_(e_invalid_argument)); +# endif +} + +# if defined(HAVE_STRFTIME) || defined(PROTO) +/* + * "strftime({format}[, {time}])" function + */ + void +f_strftime(typval_T *argvars, typval_T *rettv) +{ + struct tm tmval; + struct tm *curtime; + time_t seconds; + char_u *p; + + if (in_vim9script() + && (check_for_string_arg(argvars, 0) == FAIL + || check_for_opt_number_arg(argvars, 1) == FAIL)) + return; + + rettv->v_type = VAR_STRING; + + p = tv_get_string(&argvars[0]); + if (argvars[1].v_type == VAR_UNKNOWN) + seconds = time(NULL); + else + seconds = (time_t)tv_get_number(&argvars[1]); + curtime = vim_localtime(&seconds, &tmval); + // MSVC returns NULL for an invalid value of seconds. + if (curtime == NULL) + { + rettv->vval.v_string = vim_strsave((char_u *)_("(Invalid)")); + return; + } + +# ifdef MSWIN + WCHAR result_buf[256]; + WCHAR *wp; + + wp = enc_to_utf16(p, NULL); + if (wp != NULL) + (void)wcsftime(result_buf, ARRAY_LENGTH(result_buf), wp, curtime); + else + result_buf[0] = NUL; + rettv->vval.v_string = utf16_to_enc(result_buf, NULL); + vim_free(wp); +# else + char_u result_buf[256]; + vimconv_T conv; + char_u *enc; + + conv.vc_type = CONV_NONE; + enc = enc_locale(); + convert_setup(&conv, p_enc, enc); + if (conv.vc_type != CONV_NONE) + p = string_convert(&conv, p, NULL); + if (p == NULL || strftime((char *)result_buf, sizeof(result_buf), + (char *)p, curtime) == 0) + result_buf[0] = NUL; + + if (conv.vc_type != CONV_NONE) + vim_free(p); + convert_setup(&conv, enc, p_enc); + if (conv.vc_type != CONV_NONE) + rettv->vval.v_string = string_convert(&conv, result_buf, NULL); + else + rettv->vval.v_string = vim_strsave(result_buf); + + // Release conversion descriptors + convert_setup(&conv, NULL, NULL); + vim_free(enc); +# endif +} +# endif + +# if defined(HAVE_STRPTIME) || defined(PROTO) +/* + * "strptime({format}, {timestring})" function + */ + void +f_strptime(typval_T *argvars, typval_T *rettv) +{ + struct tm tmval; + char_u *fmt; + char_u *str; + vimconv_T conv; + char_u *enc; + + if (in_vim9script() + && (check_for_string_arg(argvars, 0) == FAIL + || check_for_string_arg(argvars, 1) == FAIL)) + return; + + CLEAR_FIELD(tmval); + tmval.tm_isdst = -1; + fmt = tv_get_string(&argvars[0]); + str = tv_get_string(&argvars[1]); + + conv.vc_type = CONV_NONE; + enc = enc_locale(); + convert_setup(&conv, p_enc, enc); + if (conv.vc_type != CONV_NONE) + fmt = string_convert(&conv, fmt, NULL); + if (fmt == NULL + || strptime((char *)str, (char *)fmt, &tmval) == NULL + || (rettv->vval.v_number = mktime(&tmval)) == -1) + rettv->vval.v_number = 0; + + if (conv.vc_type != CONV_NONE) + vim_free(fmt); + convert_setup(&conv, NULL, NULL); + vim_free(enc); +} +# endif + +# if defined(FEAT_TIMERS) || defined(PROTO) +static timer_T *first_timer = NULL; +static long last_timer_id = 0; + +/* + * Return time left until "due". Negative if past "due". + */ + long +proftime_time_left(proftime_T *due, proftime_T *now) +{ +# ifdef MSWIN + LARGE_INTEGER fr; + + if (now->QuadPart > due->QuadPart) + return 0; + QueryPerformanceFrequency(&fr); + return (long)(((double)(due->QuadPart - now->QuadPart) + / (double)fr.QuadPart) * 1000); +# else + if (now->tv_sec > due->tv_sec) + return 0; + return (due->tv_sec - now->tv_sec) * 1000 + + (due->tv_usec - now->tv_usec) / 1000; +# endif +} + +/* + * 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; + did_add_timer = TRUE; +} + +/* + * 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) +{ + free_callback(&timer->tr_callback); + 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 = ALLOC_CLEAR_ONE(timer_T); + long prev_id = last_timer_id; + + if (timer == NULL) + return NULL; + if (++last_timer_id <= prev_id) + // Overflow! Might cause duplicates... + last_timer_id = 0; + timer->tr_id = last_timer_id; + insert_timer(timer); + if (repeat != 0) + timer->tr_repeat = repeat - 1; + timer->tr_interval = msec; + + timer_start(timer); + return timer; +} + +/* + * (Re)start a timer. + */ + void +timer_start(timer_T *timer) +{ + profile_setlimit(timer->tr_interval, &timer->tr_due); + timer->tr_paused = FALSE; +} + +/* + * Invoke the callback of "timer". + */ + static void +timer_callback(timer_T *timer) +{ + typval_T rettv; + typval_T argv[2]; + +#ifdef FEAT_EVAL + if (ch_log_active()) + { + callback_T *cb = &timer->tr_callback; + + ch_log(NULL, "invoking timer callback %s", + cb->cb_partial != NULL ? cb->cb_partial->pt_name : cb->cb_name); + } +#endif + + argv[0].v_type = VAR_NUMBER; + argv[0].vval.v_number = (varnumber_T)timer->tr_id; + argv[1].v_type = VAR_UNKNOWN; + + rettv.v_type = VAR_UNKNOWN; + call_callback(&timer->tr_callback, -1, &rettv, 1, argv); + clear_tv(&rettv); + +#ifdef FEAT_EVAL + ch_log(NULL, "timer callback finished"); +#endif +} + +/* + * Call timers that are due. + * Return the time in msec until the next timer is due. + * Returns -1 if there are no pending timers. + */ + long +check_due_timer(void) +{ + timer_T *timer; + timer_T *timer_next; + long this_due; + long next_due = -1; + proftime_T now; + int did_one = FALSE; + int need_update_screen = FALSE; + long current_id = last_timer_id; + + // Don't run any timers while exiting, dealing with an error or at the + // debug prompt. + if (exiting || aborting() || debug_mode) + return next_due; + + profile_start(&now); + for (timer = first_timer; timer != NULL && !got_int; timer = timer_next) + { + timer_next = timer->tr_next; + + if (timer->tr_id == -1 || timer->tr_firing || timer->tr_paused) + continue; + this_due = proftime_time_left(&timer->tr_due, &now); + if (this_due <= 1) + { + // Save and restore a lot of flags, because the timer fires while + // waiting for a character, which might be halfway a command. + int save_timer_busy = timer_busy; + int save_vgetc_busy = vgetc_busy; + int save_did_emsg = did_emsg; + int prev_uncaught_emsg = uncaught_emsg; + int save_called_emsg = called_emsg; + int save_must_redraw = must_redraw; + int save_trylevel = trylevel; + int save_did_throw = did_throw; + int save_need_rethrow = need_rethrow; + int save_ex_pressedreturn = get_pressedreturn(); + int save_may_garbage_collect = may_garbage_collect; + except_T *save_current_exception = current_exception; + vimvars_save_T vvsave; + + // Create a scope for running the timer callback, ignoring most of + // the current scope, such as being inside a try/catch. + timer_busy = timer_busy > 0 || vgetc_busy > 0; + vgetc_busy = 0; + called_emsg = 0; + did_emsg = FALSE; + must_redraw = 0; + trylevel = 0; + did_throw = FALSE; + need_rethrow = FALSE; + current_exception = NULL; + may_garbage_collect = FALSE; + save_vimvars(&vvsave); + + // Invoke the callback. + timer->tr_firing = TRUE; + timer_callback(timer); + timer->tr_firing = FALSE; + + // Restore stuff. + timer_next = timer->tr_next; + did_one = TRUE; + timer_busy = save_timer_busy; + vgetc_busy = save_vgetc_busy; + if (uncaught_emsg > prev_uncaught_emsg) + ++timer->tr_emsg_count; + did_emsg = save_did_emsg; + called_emsg = save_called_emsg; + trylevel = save_trylevel; + did_throw = save_did_throw; + need_rethrow = save_need_rethrow; + current_exception = save_current_exception; + restore_vimvars(&vvsave); + if (must_redraw != 0) + need_update_screen = TRUE; + must_redraw = must_redraw > save_must_redraw + ? must_redraw : save_must_redraw; + set_pressedreturn(save_ex_pressedreturn); + may_garbage_collect = save_may_garbage_collect; + + // Only fire the timer again if it repeats and stop_timer() wasn't + // called while inside the callback (tr_id == -1). + if (timer->tr_repeat != 0 && timer->tr_id != -1 + && timer->tr_emsg_count < 3) + { + profile_setlimit(timer->tr_interval, &timer->tr_due); + this_due = proftime_time_left(&timer->tr_due, &now); + if (this_due < 1) + this_due = 1; + if (timer->tr_repeat > 0) + --timer->tr_repeat; + } + else + { + this_due = -1; + if (timer->tr_keep) + timer->tr_paused = TRUE; + else + { + remove_timer(timer); + free_timer(timer); + } + } + } + if (this_due > 0 && (next_due == -1 || next_due > this_due)) + next_due = this_due; + } + + if (did_one) + redraw_after_callback(need_update_screen, FALSE); + +#ifdef FEAT_BEVAL_TERM + if (bevalexpr_due_set) + { + this_due = proftime_time_left(&bevalexpr_due, &now); + if (this_due <= 1) + { + bevalexpr_due_set = FALSE; + if (balloonEval == NULL) + { + balloonEval = ALLOC_CLEAR_ONE(BalloonEval); + balloonEvalForTerm = TRUE; + } + if (balloonEval != NULL) + { + general_beval_cb(balloonEval, 0); + setcursor(); + out_flush(); + } + } + else if (next_due == -1 || next_due > this_due) + next_due = this_due; + } +#endif +#ifdef FEAT_TERMINAL + // Some terminal windows may need their buffer updated. + next_due = term_check_timers(next_due, &now); +#endif + + return current_id != last_timer_id ? 1 : next_due; +} + +/* + * Find a timer by ID. Returns NULL if not found; + */ + static timer_T * +find_timer(long id) +{ + timer_T *timer; + + if (id < 0) + return NULL; + + FOR_ALL_TIMERS(timer) + if (timer->tr_id == id) + return timer; + return NULL; +} + + +/* + * Stop a timer and delete it. + */ + void +stop_timer(timer_T *timer) +{ + if (timer->tr_firing) + // Free the timer after the callback returns. + timer->tr_id = -1; + else + { + remove_timer(timer); + free_timer(timer); + } +} + + static void +stop_all_timers(void) +{ + timer_T *timer; + timer_T *timer_next; + + for (timer = first_timer; timer != NULL; timer = timer_next) + { + timer_next = timer->tr_next; + stop_timer(timer); + } +} + + static void +add_timer_info(typval_T *rettv, timer_T *timer) +{ + list_T *list = rettv->vval.v_list; + dict_T *dict = dict_alloc(); + dictitem_T *di; + long remaining; + proftime_T now; + + if (dict == NULL) + return; + list_append_dict(list, dict); + + dict_add_number(dict, "id", timer->tr_id); + dict_add_number(dict, "time", (long)timer->tr_interval); + + profile_start(&now); + remaining = proftime_time_left(&timer->tr_due, &now); + dict_add_number(dict, "remaining", (long)remaining); + + dict_add_number(dict, "repeat", + (long)(timer->tr_repeat < 0 ? -1 + : timer->tr_repeat + (timer->tr_firing ? 0 : 1))); + dict_add_number(dict, "paused", (long)(timer->tr_paused)); + + di = dictitem_alloc((char_u *)"callback"); + if (di != NULL) + { + if (dict_add(dict, di) == FAIL) + vim_free(di); + else + put_callback(&timer->tr_callback, &di->di_tv); + } +} + + static void +add_timer_info_all(typval_T *rettv) +{ + timer_T *timer; + + FOR_ALL_TIMERS(timer) + if (timer->tr_id != -1) + add_timer_info(rettv, timer); +} + +/* + * Mark references in partials of timers. + */ + int +set_ref_in_timer(int copyID) +{ + int abort = FALSE; + timer_T *timer; + typval_T tv; + + for (timer = first_timer; !abort && timer != NULL; timer = timer->tr_next) + { + if (timer->tr_callback.cb_partial != NULL) + { + tv.v_type = VAR_PARTIAL; + tv.vval.v_partial = timer->tr_callback.cb_partial; + } + else + { + tv.v_type = VAR_FUNC; + tv.vval.v_string = timer->tr_callback.cb_name; + } + abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL); + } + return abort; +} + +/* + * Return TRUE if "timer" exists in the list of timers. + */ + int +timer_valid(timer_T *timer) +{ + if (timer == NULL) + return FALSE; + + timer_T *t; + FOR_ALL_TIMERS(t) + if (t == timer) + return TRUE; + return FALSE; +} + +# if defined(EXITFREE) || defined(PROTO) + void +timer_free_all(void) +{ + while (first_timer != NULL) + { + timer_T *timer = first_timer; + remove_timer(timer); + free_timer(timer); + } +} +# endif + +/* + * "timer_info([timer])" function + */ + void +f_timer_info(typval_T *argvars, typval_T *rettv) +{ + timer_T *timer = NULL; + + if (rettv_list_alloc(rettv) == FAIL) + return; + + if (check_for_opt_number_arg(argvars, 0) == FAIL) + return; + + if (argvars[0].v_type != VAR_UNKNOWN) + { + timer = find_timer((int)tv_get_number(&argvars[0])); + if (timer != NULL) + add_timer_info(rettv, timer); + } + else + add_timer_info_all(rettv); +} + +/* + * "timer_pause(timer, paused)" function + */ + void +f_timer_pause(typval_T *argvars, typval_T *rettv UNUSED) +{ + timer_T *timer = NULL; + + if (in_vim9script() + && (check_for_number_arg(argvars, 0) == FAIL + || check_for_bool_arg(argvars, 1) == FAIL)) + return; + + if (argvars[0].v_type != VAR_NUMBER) + { + emsg(_(e_number_expected)); + return; + } + + int paused = (int)tv_get_bool(&argvars[1]); + + timer = find_timer((int)tv_get_number(&argvars[0])); + if (timer != NULL) + timer->tr_paused = paused; +} + +/* + * "timer_start(time, callback [, options])" function + */ + void +f_timer_start(typval_T *argvars, typval_T *rettv) +{ + long msec; + timer_T *timer; + int repeat = 0; + callback_T callback; + dict_T *dict; + + rettv->vval.v_number = -1; + if (check_secure()) + return; + + if (in_vim9script() + && (check_for_number_arg(argvars, 0) == FAIL + || check_for_opt_dict_arg(argvars, 2) == FAIL)) + return; + + msec = (long)tv_get_number(&argvars[0]); + if (argvars[2].v_type != VAR_UNKNOWN) + { + if (check_for_nonnull_dict_arg(argvars, 2) == FAIL) + return; + + dict = argvars[2].vval.v_dict; + if (dict_has_key(dict, "repeat")) + repeat = dict_get_number(dict, "repeat"); + } + + callback = get_callback(&argvars[1]); + if (callback.cb_name == NULL) + return; + if (in_vim9script() && *callback.cb_name == NUL) + { + // empty callback is not useful for a timer + emsg(_(e_invalid_callback_argument)); + free_callback(&callback); + return; + } + + timer = create_timer(msec, repeat); + if (timer == NULL) + { + free_callback(&callback); + return; + } + set_callback(&timer->tr_callback, &callback); + if (callback.cb_free_name) + vim_free(callback.cb_name); + rettv->vval.v_number = (varnumber_T)timer->tr_id; +} + +/* + * "timer_stop(timer)" function + */ + void +f_timer_stop(typval_T *argvars, typval_T *rettv UNUSED) +{ + timer_T *timer; + + if (check_for_number_arg(argvars, 0) == FAIL) + return; + + timer = find_timer((int)tv_get_number(&argvars[0])); + if (timer != NULL) + stop_timer(timer); +} + +/* + * "timer_stopall()" function + */ + void +f_timer_stopall(typval_T *argvars UNUSED, typval_T *rettv UNUSED) +{ + stop_all_timers(); +} + +# endif // FEAT_TIMERS + +# if defined(STARTUPTIME) || defined(PROTO) +static struct timeval prev_timeval; + +# ifdef MSWIN +/* + * Windows doesn't have gettimeofday(), although it does have struct timeval. + */ + static int +gettimeofday(struct timeval *tv, char *dummy UNUSED) +{ + long t = clock(); + tv->tv_sec = t / CLOCKS_PER_SEC; + tv->tv_usec = (t - tv->tv_sec * CLOCKS_PER_SEC) * 1000000 / CLOCKS_PER_SEC; + return 0; +} +# endif + +/* + * Save the previous time before doing something that could nest. + * set "*tv_rel" to the time elapsed so far. + */ + void +time_push(void *tv_rel, void *tv_start) +{ + *((struct timeval *)tv_rel) = prev_timeval; + gettimeofday(&prev_timeval, NULL); + ((struct timeval *)tv_rel)->tv_usec = prev_timeval.tv_usec + - ((struct timeval *)tv_rel)->tv_usec; + ((struct timeval *)tv_rel)->tv_sec = prev_timeval.tv_sec + - ((struct timeval *)tv_rel)->tv_sec; + if (((struct timeval *)tv_rel)->tv_usec < 0) + { + ((struct timeval *)tv_rel)->tv_usec += 1000000; + --((struct timeval *)tv_rel)->tv_sec; + } + *(struct timeval *)tv_start = prev_timeval; +} + +/* + * Compute the previous time after doing something that could nest. + * Subtract "*tp" from prev_timeval; + * Note: The arguments are (void *) to avoid trouble with systems that don't + * have struct timeval. + */ + void +time_pop( + void *tp) // actually (struct timeval *) +{ + prev_timeval.tv_usec -= ((struct timeval *)tp)->tv_usec; + prev_timeval.tv_sec -= ((struct timeval *)tp)->tv_sec; + if (prev_timeval.tv_usec < 0) + { + prev_timeval.tv_usec += 1000000; + --prev_timeval.tv_sec; + } +} + + static void +time_diff(struct timeval *then, struct timeval *now) +{ + long usec; + long msec; + + usec = now->tv_usec - then->tv_usec; + msec = (now->tv_sec - then->tv_sec) * 1000L + usec / 1000L, + usec = usec % 1000L; + fprintf(time_fd, "%03ld.%03ld", msec, usec >= 0 ? usec : usec + 1000L); +} + + void +time_msg( + char *mesg, + void *tv_start) // only for do_source: start time; actually + // (struct timeval *) +{ + static struct timeval start; + struct timeval now; + + if (time_fd == NULL) + return; + + if (strstr(mesg, "STARTING") != NULL) + { + gettimeofday(&start, NULL); + prev_timeval = start; + fprintf(time_fd, "\n\ntimes in msec\n"); + fprintf(time_fd, " clock self+sourced self: sourced script\n"); + fprintf(time_fd, " clock elapsed: other lines\n\n"); + } + gettimeofday(&now, NULL); + time_diff(&start, &now); + if (((struct timeval *)tv_start) != NULL) + { + fprintf(time_fd, " "); + time_diff(((struct timeval *)tv_start), &now); + } + fprintf(time_fd, " "); + time_diff(&prev_timeval, &now); + prev_timeval = now; + fprintf(time_fd, ": %s\n", mesg); +} +# endif // STARTUPTIME +#endif // FEAT_EVAL + +#if defined(FEAT_SPELL) || defined(FEAT_PERSISTENT_UNDO) || defined(PROTO) +/* + * Read 8 bytes from "fd" and turn them into a time_T, MSB first. + * Returns -1 when encountering EOF. + */ + time_T +get8ctime(FILE *fd) +{ + int c; + time_T n = 0; + int i; + + for (i = 0; i < 8; ++i) + { + c = getc(fd); + if (c == EOF) return -1; + n = (n << 8) + c; + } + return n; +} + +/* + * Write time_T to file "fd" in 8 bytes. + * Returns FAIL when the write failed. + */ + int +put_time(FILE *fd, time_T the_time) +{ + char_u buf[8]; + + time_to_bytes(the_time, buf); + return fwrite(buf, (size_t)8, (size_t)1, fd) == 1 ? OK : FAIL; +} + +/* + * Write time_T to "buf[8]". + */ + void +time_to_bytes(time_T the_time, char_u *buf) +{ + int c; + int i; + int bi = 0; + time_T wtime = the_time; + + // time_T can be up to 8 bytes in size, more than long_u, thus we + // can't use put_bytes() here. + // Another problem is that ">>" may do an arithmetic shift that keeps the + // sign. This happens for large values of wtime. A cast to long_u may + // truncate if time_T is 8 bytes. So only use a cast when it is 4 bytes, + // it's safe to assume that long_u is 4 bytes or more and when using 8 + // bytes the top bit won't be set. + for (i = 7; i >= 0; --i) + { + if (i + 1 > (int)sizeof(time_T)) + // ">>" doesn't work well when shifting more bits than avail + buf[bi++] = 0; + else + { +# if defined(SIZEOF_TIME_T) && SIZEOF_TIME_T > 4 + c = (int)(wtime >> (i * 8)); +# else + c = (int)((long_u)wtime >> (i * 8)); +# endif + buf[bi++] = c; + } + } +} + +#endif + +/* + * Put timestamp "tt" in "buf[buflen]" in a nice format. + */ + void +add_time(char_u *buf, size_t buflen, time_t tt) +{ +#ifdef HAVE_STRFTIME + struct tm tmval; + struct tm *curtime; + size_t n; + + if (vim_time() - tt >= 100) + { + curtime = vim_localtime(&tt, &tmval); + if (vim_time() - tt < (60L * 60L * 12L)) + // within 12 hours + n = strftime((char *)buf, buflen, "%H:%M:%S", curtime); + else + // longer ago + n = strftime((char *)buf, buflen, "%Y/%m/%d %H:%M:%S", curtime); + if (n == 0) + buf[0] = NUL; + } + else +#endif + { + long seconds = (long)(vim_time() - tt); + + vim_snprintf((char *)buf, buflen, + NGETTEXT("%ld second ago", "%ld seconds ago", seconds), + seconds); + } +} |