mirror of
https://github.com/vim/vim
synced 2025-03-25 19:25:10 +01:00
Problem: Vim9: crash with deferred function call and exception Solution: Save and restore exception state Crash when a deferred function is called after an exception and another exception is thrown closes: #13376 closes: #13377 Signed-off-by: Christian Brabandt <cb@256bit.org> Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
1154 lines
26 KiB
C
1154 lines
26 KiB
C
/* 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_fsec = 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_fsec;
|
|
# 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)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL;
|
|
# ifdef FEAT_RELTIME
|
|
if (in_vim9script() && check_for_list_arg(argvars, 0) == FAIL)
|
|
return;
|
|
|
|
proftime_T tm;
|
|
if (list2proftime(&argvars[0], &tm) == OK)
|
|
{
|
|
# ifdef MSWIN
|
|
rettv->vval.v_string = vim_strsave((char_u *)profile_msg(&tm));
|
|
# else
|
|
static char buf[50];
|
|
long usec = tm.tv_fsec / (TV_FSEC_SEC / 1000000);
|
|
vim_snprintf(buf, sizeof(buf), "%3ld.%06ld", (long)tm.tv_sec, usec);
|
|
rettv->vval.v_string = vim_strsave((char_u *)buf);
|
|
# endif
|
|
}
|
|
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, in "msec", 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_fsec - now->tv_fsec) / (TV_FSEC_SEC / 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_ex_pressedreturn = get_pressedreturn();
|
|
int save_may_garbage_collect = may_garbage_collect;
|
|
vimvars_save_T vvsave;
|
|
exception_state_T estate;
|
|
|
|
exception_state_save(&estate);
|
|
|
|
// 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;
|
|
may_garbage_collect = FALSE;
|
|
exception_state_clear();
|
|
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;
|
|
exception_state_restore(&estate);
|
|
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);
|
|
}
|
|
}
|