summary refs log tree commit diff
path: root/Src
diff options
context:
space:
mode:
Diffstat (limited to 'Src')
-rw-r--r--Src/Builtins/sched.c295
-rw-r--r--Src/Zle/zle_main.c459
-rw-r--r--Src/Zle/zle_thingy.c3
-rw-r--r--Src/init.c1
-rw-r--r--Src/subst.c29
-rw-r--r--Src/utils.c145
-rw-r--r--Src/zsh.h72
7 files changed, 721 insertions, 283 deletions
diff --git a/Src/Builtins/sched.c b/Src/Builtins/sched.c
index 558a00ba6..c32a5f219 100644
--- a/Src/Builtins/sched.c
+++ b/Src/Builtins/sched.c
@@ -34,61 +34,156 @@
 
 typedef struct schedcmd  *Schedcmd;
 
+/* Flags for each scheduled event */
+enum schedflags {
+    /* Trash zle if necessary when event is activated */
+    SCHEDFLAG_TRASH_ZLE = 1
+};
+
 struct schedcmd {
     struct schedcmd *next;
     char *cmd;			/* command to run */
     time_t time;		/* when to run it */
+    int flags;			/* flags as above */
 };
 
 /* the list of sched jobs pending */
  
 static struct schedcmd *schedcmds;
 
+/* Check scheduled commands; call this function from time to time. */
+
+/**/
+static void
+checksched(void)
+{
+    time_t t;
+    struct schedcmd *sch;
+
+    if(!schedcmds)
+	return;
+    t = time(NULL);
+    /*
+     * List is ordered, so we only need to consider the
+     * head element.
+     */
+    while (schedcmds && schedcmds->time <= t) {
+	/*
+	 * Remove the entry to be executed from the list
+	 * before execution:  this makes quite sure that
+	 * the entry hasn't been monkeyed with when we
+	 * free it.
+	 */
+	sch = schedcmds;
+	schedcmds = sch->next;
+	/*
+	 * Delete from the timed function list now in case
+	 * the called code reschedules.
+	 */
+	deltimedfn(checksched);
+
+	if ((sch->flags & SCHEDFLAG_TRASH_ZLE) && zleactive)
+	    trashzleptr();
+	execstring(sch->cmd, 0, 0);
+	zsfree(sch->cmd);
+	zfree(sch, sizeof(struct schedcmd));
+
+	/*
+	 * Fix time for future events.
+	 * I had this outside the loop, for a little extra efficiency.
+	 * However, it then occurred to me that having the list of
+	 * forthcoming entries up to date could be regarded as
+	 * a feature, and the inefficiency is negligible.
+	 */
+	if (schedcmds) {
+	    /*
+	     * We need to delete the function from the list again,
+	     * in case called code rescheduled.  This is almost
+	     * as cheap as checking if it's in the list already.
+	     */
+	    deltimedfn(checksched);
+	    DPUTS(timedfns && firstnode(timedfns), "BUG: already timed fn (1)");	    addtimedfn(checksched, schedcmds->time);
+	}
+    }
+}
+
 /**/
 static int
-bin_sched(UNUSED(char *nam), char **argv, UNUSED(Options ops), UNUSED(int func))
+bin_sched(char *nam, char **argv, UNUSED(Options ops), UNUSED(int func))
 {
-    char *s = *argv++;
+    char *s, **argptr;
     time_t t;
-    long h, m;
+    long h, m, sec;
     struct tm *tm;
     struct schedcmd *sch, *sch2, *schl;
-    int sn;
+    int sn, flags = 0;
 
     /* If the argument begins with a -, remove the specified item from the
     schedule. */
-    if (s && *s == '-') {
-	sn = atoi(s + 1);
+    for (argptr = argv; *argptr && **argptr == '-'; argptr++) {
+	char *arg = *argptr + 1;
+	if (idigit(*arg)) {
+	    sn = atoi(arg);
 
-	if (!sn) {
-	    zwarnnam("sched", "usage for delete: sched -<item#>.");
-	    return 1;
-	}
-	for (schl = (struct schedcmd *)&schedcmds, sch = schedcmds, sn--;
-	     sch && sn; sch = (schl = sch)->next, sn--);
-	if (!sch) {
-	    zwarnnam("sched", "not that many entries");
+	    if (!sn) {
+		zwarnnam("sched", "usage for delete: sched -<item#>.");
+		return 1;
+	    }
+	    for (schl = NULL, sch = schedcmds, sn--;
+		 sch && sn; sch = (schl = sch)->next, sn--);
+	    if (!sch) {
+		zwarnnam("sched", "not that many entries");
+		return 1;
+	    }
+	    if (schl)
+		schl->next = sch->next;
+	    else {
+		deltimedfn(checksched);
+		schedcmds = sch->next;
+		if (schedcmds) {
+		    DPUTS(timedfns && firstnode(timedfns), "BUG: already timed fn (2)");
+		    addtimedfn(checksched, schedcmds->time);
+		}
+	    }
+	    zsfree(sch->cmd);
+	    zfree(sch, sizeof(struct schedcmd));
+
+	    return 0;
+	} else if (*arg == '-') {
+	    /* end of options */
+	    argptr++;
+	    break;
+	} else if (!strcmp(arg, "o")) {
+	    flags |= SCHEDFLAG_TRASH_ZLE;
+	} else {
+	    if (*arg)
+		zwarnnam(nam, "bad option: -%c", *arg);
+	    else
+		zwarnnam(nam, "option expected");
 	    return 1;
 	}
-	schl->next = sch->next;
-	zsfree(sch->cmd);
-	zfree(sch, sizeof(struct schedcmd));
-
-	return 0;
     }
 
     /* given no arguments, display the schedule list */
-    if (!s) {
-	char tbuf[40];
+    if (!*argptr) {
+	char tbuf[40], *flagstr, *endstr;
 
 	for (sn = 1, sch = schedcmds; sch; sch = sch->next, sn++) {
 	    t = sch->time;
 	    tm = localtime(&t);
 	    ztrftime(tbuf, 20, "%a %b %e %k:%M:%S", tm);
-	    printf("%3d %s %s\n", sn, tbuf, sch->cmd);
+	    if (sch->flags & SCHEDFLAG_TRASH_ZLE)
+		flagstr = "-o ";
+	    else
+		flagstr = "";
+	    if (*sch->cmd == '-')
+		endstr = "-- ";
+	    else
+		endstr = "";
+	    printf("%3d %s %s%s%s\n", sn, tbuf, flagstr, endstr, sch->cmd);
 	}
 	return 0;
-    } else if (!*argv) {
+    } else if (!argptr[1]) {
 	/* other than the two cases above, sched *
 	 *requires at least two arguments        */
 	zwarnnam("sched", "not enough arguments");
@@ -97,86 +192,108 @@ bin_sched(UNUSED(char *nam), char **argv, UNUSED(Options ops), UNUSED(int func))
 
     /* The first argument specifies the time to schedule the command for.  The
     remaining arguments form the command. */
+    s = *argptr++;
     if (*s == '+') {
-	/* + introduces a relative time.  The rest of the argument is an
-	hour:minute offset from the current time.  Once the hour and minute
-	numbers have been extracted, and the format verified, the resulting
-	offset is simply added to the current time. */
-	h = zstrtol(s + 1, &s, 10);
-	if (*s != ':') {
+	/*
+	 * + introduces a relative time.  The rest of the argument may be an
+	 * hour:minute offset from the current time.  Once the hour and minute
+	 * numbers have been extracted, and the format verified, the resulting
+	 * offset is simply added to the current time.
+	 */
+	zlong zl = zstrtol(s + 1, &s, 10);
+	if (*s == ':') {
+	    m = (long)zstrtol(s + 1, &s, 10);
+	    if (*s == ':')
+		sec = (long)zstrtol(s + 1, &s, 10);
+	    else
+		sec = 0;
+	    if (*s) {
+		zwarnnam("sched", "bad time specifier");
+		return 1;
+	    }
+	    t = time(NULL) + (long)zl * 3600 + m * 60 + sec;
+	} else if (!*s) {
+	    /*
+	     * Alternatively, it may simply be a number of seconds.
+	     * This is here for consistency with absolute times.
+	     */
+	    t = time(NULL) + (time_t)zl;
+	} else {
 	    zwarnnam("sched", "bad time specifier");
 	    return 1;
 	}
-	m = zstrtol(s + 1, &s, 10);
-	if (*s) {
-	    zwarnnam("sched", "bad time specifier");
-	    return 1;
-	}
-	t = time(NULL) + h * 3600 + m * 60;
     } else {
-	/* If there is no +, an absolute time of day must have been given.
-	This is in hour:minute format, optionally followed by a string starting
-	with `a' or `p' (for a.m. or p.m.).  Characters after the `a' or `p'
-	are ignored. */
-	h = zstrtol(s, &s, 10);
-	if (*s != ':') {
-	    zwarnnam("sched", "bad time specifier");
-	    return 1;
-	}
-	m = zstrtol(s + 1, &s, 10);
-	if (*s && *s != 'a' && *s != 'A' && *s != 'p' && *s != 'P') {
+	/*
+	 * If there is no +, an absolute time must have been given.
+	 * This may be in hour:minute format, optionally followed by a string
+	 * starting with `a' or `p' (for a.m. or p.m.).  Characters after the
+	 * `a' or `p' are ignored.
+	 */
+	zlong zl = zstrtol(s, &s, 10);
+	if (*s == ':') {
+	    h = (long)zl;
+	    m = (long)zstrtol(s + 1, &s, 10);
+	    if (*s == ':')
+		sec = (long)zstrtol(s + 1, &s, 10);
+	    else
+		sec = 0;
+	    if (*s && *s != 'a' && *s != 'A' && *s != 'p' && *s != 'P') {
+		zwarnnam("sched", "bad time specifier");
+		return 1;
+	    }
+	    t = time(NULL);
+	    tm = localtime(&t);
+	    t -= tm->tm_sec + tm->tm_min * 60 + tm->tm_hour * 3600;
+	    if (*s == 'p' || *s == 'P')
+		h += 12;
+	    t += h * 3600 + m * 60 + sec;
+	    /*
+	     * If the specified time is before the current time, it must refer
+	     * to tomorrow.
+	     */
+	    if (t < time(NULL))
+		t += 3600 * 24;
+	} else if (!*s) {
+	    /*
+	     * Otherwise, it must be a raw time specifier.
+	     */
+	    t = (long)zl;
+	} else {
 	    zwarnnam("sched", "bad time specifier");
 	    return 1;
 	}
-	t = time(NULL);
-	tm = localtime(&t);
-	t -= tm->tm_sec + tm->tm_min * 60 + tm->tm_hour * 3600;
-	if (*s == 'p' || *s == 'P')
-	    h += 12;
-	t += h * 3600 + m * 60;
-	/* If the specified time is before the current time, it must refer to
-	tomorrow. */
-	if (t < time(NULL))
-	    t += 3600 * 24;
     }
     /* The time has been calculated; now add the new entry to the linked list
     of scheduled commands. */
-    sch = (struct schedcmd *) zshcalloc(sizeof *sch);
+    sch = (struct schedcmd *) zalloc(sizeof *sch);
     sch->time = t;
-    sch->cmd = zjoin(argv, ' ', 0);
-    sch->next = NULL;
-    for (sch2 = (struct schedcmd *)&schedcmds; sch2->next; sch2 = sch2->next);
-    sch2->next = sch;
-    return 0;
-}
-
-/* Check scheduled commands; call this function from time to time. */
-
-/**/
-static void
-checksched(void)
-{
-    time_t t;
-    struct schedcmd *sch, *schl;
-
-    if(!schedcmds)
-	return;
-    t = time(NULL);
-    for (schl = (struct schedcmd *)&schedcmds, sch = schedcmds; sch;
-	 sch = (schl = sch)->next) {
-	if (sch->time <= t) {
-	    execstring(sch->cmd, 0, 0);
-	    schl->next = sch->next;
-	    zsfree(sch->cmd);
-	    zfree(sch, sizeof(struct schedcmd));
-	    sch = schl;
+    sch->cmd = zjoin(argptr, ' ', 0);
+    sch->flags = flags;
+    /* Insert into list in time order */
+    if (schedcmds) {
+	if (sch->time < schedcmds->time) {
+	    deltimedfn(checksched);
+	    sch->next = schedcmds;
+	    schedcmds = sch;
+	    DPUTS(timedfns && firstnode(timedfns), "BUG: already timed fn (3)");
+	    addtimedfn(checksched, t);
+	} else {
+	    for (sch2 = schedcmds;
+		 sch2->next && sch2->next->time < sch->time;
+		 sch2 = sch2->next)
+		;
+	    sch->next = sch2->next;
+	    sch2->next = sch;
 	}
+    } else {
+	sch->next = NULL;
+	schedcmds = sch;
+	DPUTS(timedfns && firstnode(timedfns), "BUG: already timed fn (4)");
+	addtimedfn(checksched, t);
     }
+    return 0;
 }
 
-static void (*p_checksched) _((void)) = checksched;
-static struct linknode n_checksched = { NULL, NULL, &p_checksched };
-
 static struct builtin bintab[] = {
     BUILTIN("sched", 0, bin_sched, 0, -1, 0, NULL, NULL),
 };
@@ -194,7 +311,7 @@ boot_(Module m)
 {
     if(!addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)))
 	return 1;
-    uaddlinknode(prepromptfns, &n_checksched);
+    addprepromptfn(&checksched);
     return 0;
 }
 
@@ -209,7 +326,7 @@ cleanup_(Module m)
 	zsfree(sch->cmd);
 	zfree(sch, sizeof(*sch));
     }
-    uremnode(prepromptfns, &n_checksched);
+    delprepromptfn(&checksched);
     deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
     return 0;
 }
diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c
index 1d4636937..0e34a3fc3 100644
--- a/Src/Zle/zle_main.c
+++ b/Src/Zle/zle_main.c
@@ -122,7 +122,11 @@ int insmode;
 mod_export int eofchar;
 
 static int eofsent;
-static long keytimeout;
+/*
+ * Key timeout in hundredths of a second:  we use time_t so
+ * that we only have the limits on one integer type to worry about.
+ */
+static time_t keytimeout;
 
 #if defined(HAVE_SELECT) || defined(HAVE_POLL)
 /* Terminal baud rate */
@@ -387,11 +391,110 @@ breakread(int fd, char *buf, int n)
 # define read    breakread
 #endif
 
+/*
+ * Possible forms of timeout.
+ */
+enum ztmouttp {
+    /* No timeout in use. */
+    ZTM_NONE,
+    /*
+     * Key timeout in use (do_keytmout flag set).  If this goes off
+     * we return without anything being read.
+     */
+    ZTM_KEY,
+    /*
+     * Function timeout in use (from timedfns list).
+     * If this goes off we call any functions which have reached
+     * the time and then continue processing.
+     */
+    ZTM_FUNC,
+    /*
+     * Timeout hit the maximum allowed; if it fires we
+     * need to recalculate.  As we may use poll() for the timeout,
+     * which takes an int value in milliseconds, we might need this
+     * for times long in the future.  (We make no attempt to extend
+     * the range of time beyond that of time_t, however; that seems
+     * like a losing battle.)
+     *
+     * For key timeouts we just limit the value to
+     * ZMAXTIMEOUT; that's already absurdly large.
+     *
+     * The following is the maximum signed range over 1024 (2^10), which
+     * is a little more convenient than 1000, but done differently
+     * to avoid problems with unsigned integers.  We assume 8-bit bytes;
+     * there's no general way to fix up if that's wrong.
+     */
+    ZTM_MAX
+#define	ZMAXTIMEOUT	((time_t)(1 << (sizeof(time_t)*8-11)))
+};
+
+struct ztmout {
+    /* Type of timeout setting, see enum above */
+    enum ztmouttp tp;
+    /*
+     * Value for timeout in 100ths of a second if type is not ZTM_NONE.
+     */
+    time_t exp100ths;
+};
+
+/*
+ * See if we need a timeout either for a key press or for a
+ * timed function.
+ */
+
+static void
+calc_timeout(struct ztmout *tmoutp, int do_keytmout)
+{
+    if (do_keytmout && keytimeout > 0) {
+	if (keytimeout > ZMAXTIMEOUT * 100 /* 24 days for a keypress???? */)
+	    tmoutp->exp100ths = ZMAXTIMEOUT * 100;
+	else
+	    tmoutp->exp100ths = keytimeout;
+	tmoutp->tp = ZTM_KEY;
+    } else
+	tmoutp->tp = ZTM_NONE;
+
+    if (timedfns) {
+	for (;;) {
+	    LinkNode tfnode = firstnode(timedfns);
+	    Timedfn tfdat;
+	    time_t diff, exp100ths;
+
+	    if (!tfnode)
+		break;
+
+	    tfdat = (Timedfn)getdata(tfnode);
+	    diff = tfdat->when - time(NULL);
+	    if (diff < 0) {
+		/* Already due; call it and rescan. */
+		tfdat->func();
+		continue;
+	    }
+
+	    if (diff > ZMAXTIMEOUT) {
+		tmoutp->exp100ths = ZMAXTIMEOUT * 100;
+		tmoutp->tp = ZTM_MAX;
+	    } else if (diff > 0) {
+		exp100ths = diff * 100;
+		if (tmoutp->tp != ZTM_KEY ||
+		    exp100ths < tmoutp->exp100ths) {
+		    tmoutp->exp100ths = exp100ths;
+		    tmoutp->tp = ZTM_FUNC;
+		}
+	    }
+	    break;
+	}
+	/* In case we called a function which messed up the display... */
+	if (resetneeded)
+	    zrefresh();
+    }
+}
+
 static int
-raw_getbyte(int keytmout, char *cptr)
+raw_getbyte(int do_keytmout, char *cptr)
 {
-    long exp100ths;
     int ret;
+    struct ztmout tmout;
 #if defined(HAS_TIO) && \
   (defined(sun) || (!defined(HAVE_POLL) && !defined(HAVE_SELECT)))
     struct ttyinfo ti;
@@ -402,204 +505,254 @@ raw_getbyte(int keytmout, char *cptr)
 # endif
 #endif
 
+    calc_timeout(&tmout, do_keytmout);
+
     /*
-     * Handle timeouts and watched fd's.  We only do one at once;
-     * key timeouts take precedence.  This saves tricky timing
-     * problems with the key timeout.
+     * Handle timeouts and watched fd's.  If a watched fd or a function
+     * timeout triggers we restart any key timeout.  This is likely to
+     * be harmless: the combination is extremely rare and a function
+     * is likely to occupy the user for a little while anyway.  We used
+     * to make timeouts take precedence, but we can't now that the
+     * timeouts may be external, so we may have both a permanent watched
+     * fd and a long-term timeout.
      */
-    if ((nwatch || keytmout)
+    if ((nwatch || tmout.tp != ZTM_NONE)
 #ifdef FIONREAD
 	&& ! delayzsetterm
 #endif
 	) {
-	if (!keytmout || keytimeout <= 0)
-	    exp100ths = 0;
-	else if (keytimeout > 500)
-	    exp100ths = 500;
-	else
-	    exp100ths = keytimeout;
 #if defined(HAVE_SELECT) || defined(HAVE_POLL)
-	if (!keytmout || exp100ths) {
-	    int i, errtry = 0, selret;
+	int i, errtry = 0, selret;
 # ifdef HAVE_POLL
-	    int poll_timeout;
-	    int nfds;
-	    struct pollfd *fds;
-# else
-	    int fdmax;
-	    struct timeval *tvptr;
-	    struct timeval expire_tv;
+	int nfds;
+	struct pollfd *fds;
 # endif
 # if defined(HAS_TIO) && defined(sun)
-	    /*
-	     * Yes, I know this is complicated.  Yes, I know we
-	     * already have three bits of code to poll the terminal
-	     * down below.  No, I don't want to do this either.
-	     * However, it turns out on certain OSes, specifically
-	     * Solaris, that you can't poll typeahead for love nor
-	     * money without actually trying to read it.  But
-	     * if we are trying to select (and we need to if we
-	     * are watching other fd's) we won't pick that up.
-	     * So we just try and read it without blocking in
-	     * the time-honoured (i.e. absurdly baroque) termios
-	     * fashion.
-	     */
-	    gettyinfo(&ti);
-	    ti.tio.c_cc[VMIN] = 0;
-	    settyinfo(&ti);
-	    ret = read(SHTTY, cptr, 1);
-	    ti.tio.c_cc[VMIN] = 1;
-	    settyinfo(&ti);
-	    if (ret > 0)
-		return 1;
+	/*
+	 * Yes, I know this is complicated.  Yes, I know we
+	 * already have three bits of code to poll the terminal
+	 * down below.  No, I don't want to do this either.
+	 * However, it turns out on certain OSes, specifically
+	 * Solaris, that you can't poll typeahead for love nor
+	 * money without actually trying to read it.  But
+	 * if we are trying to select (and we need to if we
+	 * are watching other fd's) we won't pick that up.
+	 * So we just try and read it without blocking in
+	 * the time-honoured (i.e. absurdly baroque) termios
+	 * fashion.
+	 */
+	gettyinfo(&ti);
+	ti.tio.c_cc[VMIN] = 0;
+	settyinfo(&ti);
+	ret = read(SHTTY, cptr, 1);
+	ti.tio.c_cc[VMIN] = 1;
+	settyinfo(&ti);
+	if (ret > 0)
+	    return 1;
 # endif
 # ifdef HAVE_POLL
-	    nfds = keytmout ? 1 : 1 + nwatch;
-	    /* First pollfd is SHTTY, following are the nwatch fds */
-	    fds = zalloc(sizeof(struct pollfd) * nfds);
-	    if (exp100ths)
-		poll_timeout = exp100ths * 10;
+	nfds = 1 + nwatch;
+	/* First pollfd is SHTTY, following are the nwatch fds */
+	fds = zalloc(sizeof(struct pollfd) * nfds);
+	fds[0].fd = SHTTY;
+	/*
+	 * POLLIN, POLLIN, POLLIN,
+	 * Keep those fd's POLLIN...
+	 */
+	fds[0].events = POLLIN;
+	for (i = 0; i < nwatch; i++) {
+	    fds[i+1].fd = watch_fds[i];
+	    fds[i+1].events = POLLIN;
+	}
+# endif
+	do {
+# ifdef HAVE_POLL
+	    int poll_timeout;
+
+	    if (tmout.tp != ZTM_NONE)
+		poll_timeout = tmout.exp100ths * 10;
 	    else
 		poll_timeout = -1;
 
-	    fds[0].fd = SHTTY;
-	    /*
-	     * POLLIN, POLLIN, POLLIN,
-	     * Keep those fd's POLLIN...
-	     */
-	    fds[0].events = POLLIN;
-	    if (!keytmout) {
+	    selret = poll(fds, errtry ? 1 : nfds, poll_timeout);
+# else
+	    int fdmax = SHTTY;
+	    struct timeval *tvptr;
+	    struct timeval expire_tv;
+
+	    FD_ZERO(&foofd);
+	    FD_SET(SHTTY, &foofd);
+	    if (!errtry) {
 		for (i = 0; i < nwatch; i++) {
-		    fds[i+1].fd = watch_fds[i];
-		    fds[i+1].events = POLLIN;
+		    int fd = watch_fds[i];
+		    FD_SET(fd, &foofd);
+		    if (fd > fdmax)
+			fdmax = fd;
 		}
 	    }
-# else
-	    fdmax = SHTTY;
-	    tvptr = NULL;
-	    if (exp100ths) {
-		expire_tv.tv_sec = exp100ths / 100;
-		expire_tv.tv_usec = (exp100ths % 100) * 10000L;
+
+	    if (tmout.tp != ZTM_NONE) {
+		expire_tv.tv_sec = tmout.exp100ths / 100;
+		expire_tv.tv_usec = (tmout.exp100ths % 100) * 10000L;
 		tvptr = &expire_tv;
 	    }
+	    else
+		tvptr = NULL;
+
+	    selret = select(fdmax+1, (SELECT_ARG_2_T) & foofd,
+			    NULL, NULL, tvptr);
 # endif
-	    do {
-# ifdef HAVE_POLL
-		selret = poll(fds, errtry ? 1 : nfds, poll_timeout);
-# else
-		FD_ZERO(&foofd);
-		FD_SET(SHTTY, &foofd);
-		if (!keytmout && !errtry) {
-		    for (i = 0; i < nwatch; i++) {
-			int fd = watch_fds[i];
-			FD_SET(fd, &foofd);
-			if (fd > fdmax)
-			    fdmax = fd;
-		    }
-		}
-		selret = select(fdmax+1, (SELECT_ARG_2_T) & foofd,
-				NULL, NULL, tvptr);
-# endif
-		/*
-		 * Make sure a user interrupt gets passed on straight away.
-		 */
-		if (selret < 0 && errflag)
-		    break;
+	    /*
+	     * Make sure a user interrupt gets passed on straight away.
+	     */
+	    if (selret < 0 && errflag)
+		break;
+	    /*
+	     * Try to avoid errors on our special fd's from
+	     * messing up reads from the terminal.  Try first
+	     * with all fds, then try unsetting the special ones.
+	     */
+	    if (selret < 0 && !errtry) {
+		errtry = 1;
+		continue;
+	    }
+	    if (selret == 0) {
 		/*
-		 * Try to avoid errors on our special fd's from
-		 * messing up reads from the terminal.  Try first
-		 * with all fds, then try unsetting the special ones.
+		 * Nothing ready and no error, so we timed out.
 		 */
-		if (selret < 0 && !keytmout && !errtry) {
-		    errtry = 1;
-		    continue;
-		}
-		if (selret == 0) {
+		switch (tmout.tp) {
+		case ZTM_NONE:
+		    /* keeps compiler happy if not debugging */
+#ifdef DEBUG
+		    dputs("BUG: timeout fired with no timeout set.");
+#endif
+		    /* treat as if a key timeout triggered */
+		    /*FALLTHROUGH*/
+		case ZTM_KEY:
 		    /* Special value -2 signals nothing ready */
 		    selret = -2;
-		}
-		if (selret < 0)
 		    break;
-		if (!keytmout && nwatch) {
+
+		case ZTM_FUNC:
+		    while (firstnode(timedfns)) {
+			Timedfn tfdat = (Timedfn)getdata(firstnode(timedfns));
+			/*
+			 * It's possible a previous function took
+			 * a long time to run (though it can't
+			 * call zle recursively), so recalculate
+			 * the time on each iteration.
+			 */
+			time_t now = time(NULL);
+			if (tfdat->when > now)
+			    break;
+			tfdat->func();
+		    }
+		    /* Function may have messed up the display */
+		    if (resetneeded)
+			zrefresh();
+		    /* We need to recalculate the timeout */
+		    /*FALLTHROUGH*/
+		case ZTM_MAX:
 		    /*
-		     * Copy the details of the watch fds in case the
-		     * user decides to delete one from inside the
-		     * handler function.
+		     * Reached the limit of our range, but not the
+		     * actual timeout; recalculate the timeout.
+		     * We're cheating with the key timeout here:
+		     * if one clashed with a function timeout we
+		     * reconsider the key timeout from scratch.
+		     * The effect of this is microscopic.
 		     */
-		    int lnwatch = nwatch;
-		    int *lwatch_fds = zalloc(lnwatch*sizeof(int));
-		    char **lwatch_funcs = zarrdup(watch_funcs);
-		    memcpy(lwatch_fds, watch_fds, lnwatch*sizeof(int));
-		    for (i = 0; i < lnwatch; i++) {
-			if (
+		    calc_timeout(&tmout, do_keytmout);
+		    break;
+		}
+		/*
+		 * If we handled the timeout successfully,
+		 * carry on.
+		 */
+		if (selret == 0)
+		    continue;
+	    }
+	    /* If error or unhandled timeout, give up. */
+	    if (selret < 0)
+		break;
+	    if (nwatch && !errtry) {
+		/*
+		 * Copy the details of the watch fds in case the
+		 * user decides to delete one from inside the
+		 * handler function.
+		 */
+		int lnwatch = nwatch;
+		int *lwatch_fds = zalloc(lnwatch*sizeof(int));
+		char **lwatch_funcs = zarrdup(watch_funcs);
+		memcpy(lwatch_fds, watch_fds, lnwatch*sizeof(int));
+		for (i = 0; i < lnwatch; i++) {
+		    if (
 # ifdef HAVE_POLL
-			    (fds[i+1].revents & POLLIN)
+			(fds[i+1].revents & POLLIN)
 # else
-			    FD_ISSET(lwatch_fds[i], &foofd)
+			FD_ISSET(lwatch_fds[i], &foofd)
 # endif
-			    ) {
-			    /* Handle the fd. */
-			    LinkList funcargs = znewlinklist();
-			    zaddlinknode(funcargs, ztrdup(lwatch_funcs[i]));
-			    {
-				char buf[BDIGBUFSIZE];
-				convbase(buf, lwatch_fds[i], 10);
-				zaddlinknode(funcargs, ztrdup(buf));
-			    }
+			) {
+			/* Handle the fd. */
+			LinkList funcargs = znewlinklist();
+			zaddlinknode(funcargs, ztrdup(lwatch_funcs[i]));
+			{
+			    char buf[BDIGBUFSIZE];
+			    convbase(buf, lwatch_fds[i], 10);
+			    zaddlinknode(funcargs, ztrdup(buf));
+			}
 # ifdef HAVE_POLL
 #  ifdef POLLERR
-			    if (fds[i+1].revents & POLLERR)
-				zaddlinknode(funcargs, ztrdup("err"));
+			if (fds[i+1].revents & POLLERR)
+			    zaddlinknode(funcargs, ztrdup("err"));
 #  endif
 #  ifdef POLLHUP
-			    if (fds[i+1].revents & POLLHUP)
-				zaddlinknode(funcargs, ztrdup("hup"));
+			if (fds[i+1].revents & POLLHUP)
+			    zaddlinknode(funcargs, ztrdup("hup"));
 #  endif
 #  ifdef POLLNVAL
-			    if (fds[i+1].revents & POLLNVAL)
-				zaddlinknode(funcargs, ztrdup("nval"));
+			if (fds[i+1].revents & POLLNVAL)
+			    zaddlinknode(funcargs, ztrdup("nval"));
 #  endif
 # endif
 
 
-			    callhookfunc(lwatch_funcs[i], funcargs);
-			    if (errflag) {
-				/* No sensible way of handling errors here */
-				errflag = 0;
-				/*
-				 * Paranoia: don't run the hooks again this
-				 * time.
-				 */
-				errtry = 1;
-			    }
-			    freelinklist(funcargs, freestr);
+			callhookfunc(lwatch_funcs[i], funcargs);
+			if (errflag) {
+			    /* No sensible way of handling errors here */
+			    errflag = 0;
+			    /*
+			     * Paranoia: don't run the hooks again this
+			     * time.
+			     */
+			    errtry = 1;
 			}
+			freelinklist(funcargs, freestr);
 		    }
-		    /* Function may have invalidated the display. */
-		    if (resetneeded)
-			zrefresh();
-		    zfree(lwatch_fds, lnwatch*sizeof(int));
-		    freearray(lwatch_funcs);
 		}
-	    } while (!
+		/* Function may have invalidated the display. */
+		if (resetneeded)
+		    zrefresh();
+		zfree(lwatch_fds, lnwatch*sizeof(int));
+		freearray(lwatch_funcs);
+	    }
+	} while (!
 # ifdef HAVE_POLL
-		     (fds[0].revents & POLLIN)
+		 (fds[0].revents & POLLIN)
 # else
-		     FD_ISSET(SHTTY, &foofd)
+		 FD_ISSET(SHTTY, &foofd)
 # endif
-		);
+		 );
 # ifdef HAVE_POLL
-	    zfree(fds, sizeof(struct pollfd) * nfds);
+	zfree(fds, sizeof(struct pollfd) * nfds);
 # endif
-	    if (selret < 0)
-		return selret;
-	}
+	if (selret < 0)
+	    return selret;
 #else
 # ifdef HAS_TIO
 	ti = shttyinfo;
 	ti.tio.c_lflag &= ~ICANON;
 	ti.tio.c_cc[VMIN] = 0;
-	ti.tio.c_cc[VTIME] = exp100ths / 10;
+	ti.tio.c_cc[VTIME] = tmout.exp100ths / 10;
 #  ifdef HAVE_TERMIOS_H
 	tcsetattr(SHTTY, TCSANOW, &ti.tio);
 #  else
@@ -623,7 +776,7 @@ raw_getbyte(int keytmout, char *cptr)
 
 /**/
 mod_export int
-getbyte(int keytmout, int *timeout)
+getbyte(int do_keytmout, int *timeout)
 {
     char cc;
     unsigned int ret;
@@ -656,7 +809,7 @@ getbyte(int keytmout, int *timeout)
 	for (;;) {
 	    int q = queue_signal_level();
 	    dont_queue_signals();
-	    r = raw_getbyte(keytmout, &cc);
+	    r = raw_getbyte(do_keytmout, &cc);
 	    restore_queue_signals(q);
 	    if (r == -2) {
 		/* timeout */
@@ -733,9 +886,9 @@ getbyte(int keytmout, int *timeout)
 
 /**/
 mod_export ZLE_INT_T
-getfullchar(int keytmout)
+getfullchar(int do_keytmout)
 {
-    int inchar = getbyte(keytmout, NULL);
+    int inchar = getbyte(do_keytmout, NULL);
 
 #ifdef MULTIBYTE_SUPPORT
     return getrestchar(inchar);
@@ -938,7 +1091,7 @@ zleread(char **lp, char **rp, int flags, int context)
 	return shingetline();
     }
 
-    keytimeout = getiparam("KEYTIMEOUT");
+    keytimeout = (time_t)getiparam("KEYTIMEOUT");
     if (!shout) {
 	if (SHTTY != -1)
 	    init_shout();
@@ -1551,7 +1704,7 @@ zle_resetprompt(void)
 mod_export void
 trashzle(void)
 {
-    if (zleactive) {
+    if (zleactive && !trashedzle) {
 	/* This zrefresh() is just to get the main editor display right and *
 	 * get the cursor in the right place.  For that reason, we disable  *
 	 * list display (which would otherwise result in infinite           *
diff --git a/Src/Zle/zle_thingy.c b/Src/Zle/zle_thingy.c
index 72d3314ff..debd31a28 100644
--- a/Src/Zle/zle_thingy.c
+++ b/Src/Zle/zle_thingy.c
@@ -721,8 +721,7 @@ bin_zle_invalidate(UNUSED(char *name), UNUSED(char **args), UNUSED(Options ops),
      * true if a completion widget is active.
      */
     if (zleactive) {
-	if (!trashedzle)
-	    trashzle();
+	trashzle();
 	return 0;
     } else
 	return 1;
diff --git a/Src/init.c b/Src/init.c
index 7447842ab..9d6a0514e 100644
--- a/Src/init.c
+++ b/Src/init.c
@@ -869,7 +869,6 @@ setupvals(void)
     nohistsave = 1;
     dirstack = znewlinklist();
     bufstack = znewlinklist();
-    prepromptfns = znewlinklist();
     hsubl = hsubr = NULL;
     lastpid = 0;
     bshin = SHIN ? fdopen(SHIN, "r") : stdin;
diff --git a/Src/subst.c b/Src/subst.c
index 9f2703326..67afd0f03 100644
--- a/Src/subst.c
+++ b/Src/subst.c
@@ -70,9 +70,24 @@ prefork(LinkList list, int flags)
 		return;
 	    }
 	} else {
-	    if (isset(SHFILEEXPANSION))
-		filesub((char **)getaddrdata(node),
-			flags & (PF_TYPESET|PF_ASSIGN));
+	    if (isset(SHFILEEXPANSION)) {
+		/*
+		 * Here and below we avoid taking the address
+		 * of a void * and then pretending it's a char **
+		 * instead of a void ** by a little inefficiency.
+		 * This could be avoided with some extra linked list
+		 * machinery, but that would need quite a lot of work
+		 * to ensure consistency.  What we really need is
+		 * templates...
+		 */
+		char *cptr = (char *)getdata(node);
+		filesub(&cptr, flags & (PF_TYPESET|PF_ASSIGN));
+		/*
+		 * The assignment is so simple it's not worth
+		 * testing if cptr changed...
+		 */
+		setdata(node, cptr);
+	    }
 	    if (!(node = stringsubst(list, node, flags & PF_SINGLE, asssub))) {
 		unqueue_signals();
 		return;
@@ -92,9 +107,11 @@ prefork(LinkList list, int flags)
 		    xpandbraces(list, &node);
 		}
 	    }
-	    if (unset(SHFILEEXPANSION))
-		filesub((char **)getaddrdata(node),
-			flags & (PF_TYPESET|PF_ASSIGN));
+	    if (unset(SHFILEEXPANSION)) {
+		char *cptr = (char *)getdata(node);
+		filesub(&cptr, flags & (PF_TYPESET|PF_ASSIGN));
+		setdata(node, cptr);
+	    }
 	} else if (!(flags & PF_SINGLE) && !keep)
 	    uremnode(list, node);
 	if (errflag) {
diff --git a/Src/utils.c b/Src/utils.c
index 2bfae667c..3a5246bd1 100644
--- a/Src/utils.c
+++ b/Src/utils.c
@@ -911,10 +911,141 @@ dircmp(char *s, char *t)
     return 1;
 }
 
-/* extra functions to call before displaying the prompt */
+/*
+ * Extra functions to call before displaying the prompt.
+ * The data is a Prepromptfn.
+ */
+
+static LinkList prepromptfns;
+
+/* Add a function to the list of pre-prompt functions. */
 
 /**/
-mod_export LinkList prepromptfns;
+mod_export void
+addprepromptfn(voidvoidfnptr_t func)
+{
+    Prepromptfn ppdat = (Prepromptfn)zalloc(sizeof(struct prepromptfn));
+    ppdat->func = func;
+    if (!prepromptfns)
+	prepromptfns = znewlinklist();
+    zaddlinknode(prepromptfns, ppdat);
+}
+
+/* Remove a function from the list of pre-prompt functions. */
+
+/**/
+mod_export void
+delprepromptfn(voidvoidfnptr_t func)
+{
+    LinkNode ln;
+
+    for (ln = firstnode(prepromptfns); ln; ln = nextnode(ln)) {
+	Prepromptfn ppdat = (Prepromptfn)getdata(ln);
+	if (ppdat->func == func) {
+	    (void)remnode(prepromptfns, ln);
+	    zfree(ppdat, sizeof(struct prepromptfn));
+	    return;
+	}
+    }
+#ifdef DEBUG
+    dputs("BUG: failed to delete node from prepromptfns");
+#endif
+}
+
+/*
+ * Functions to call at a particular time even if not at
+ * the prompt.  This is handled by zle.  The data is a
+ * Timedfn.  The functions must be in time order, but this
+ * is enforced by addtimedfn().
+ *
+ * Note on debugging:  the code in sched.c currently assumes it's
+ * the only user of timedfns for the purposes of checking whether
+ * there's a function on the list.  If this becomes no longer the case,
+ * the DPUTS() tests in sched.c need rewriting.
+ */
+
+/**/
+mod_export LinkList timedfns;
+
+/* Add a function to the list of timed functions. */
+
+/**/
+mod_export void
+addtimedfn(voidvoidfnptr_t func, time_t when)
+{
+    Timedfn tfdat = (Timedfn)zalloc(sizeof(struct timedfn));
+    tfdat->func = func;
+    tfdat->when = when;
+
+    if (!timedfns) {
+	timedfns = znewlinklist();
+	zaddlinknode(timedfns, tfdat);
+    } else {
+	LinkNode ln = firstnode(timedfns);
+
+	/*
+	 * Insert the new element in the linked list.  We do
+	 * rather too much work here since the standard
+	 * functions insert after a given node, whereas we
+	 * want to insert the new data before the first element
+	 * with a greater time.
+	 *
+	 * In practice, the only use of timed functions is
+	 * sched, which only adds the one function; so this
+	 * whole branch isn't used beyond the following block.
+	 */
+	if (!ln) {
+	    zaddlinknode(timedfns, tfdat);
+	    return;
+	}
+	for (;;) {
+	    Timedfn tfdat2;
+	    LinkNode next = nextnode(ln);
+	    if (!next) {
+		zaddlinknode(timedfns, tfdat);
+		return;
+	    }
+	    tfdat2 = (Timedfn)getdata(next);
+	    if (when < tfdat2->when) {
+		zinsertlinknode(timedfns, ln, tfdat);
+		return;
+	    }
+	    ln = next;
+	}
+    }
+}
+
+/*
+ * Delete a function from the list of timed functions.
+ * Note that if the function apperas multiple times only
+ * the first occurrence will be removed.
+ *
+ * Note also that when zle calls the function it does *not*
+ * automatically delete the entry from the list.  That must
+ * be done by the function called.  This is recommended as otherwise
+ * the function will keep being called immediately.  (It just so
+ * happens this "feature" fits in well with the only current use
+ * of timed functions.)
+ */
+
+/**/
+mod_export void
+deltimedfn(voidvoidfnptr_t func)
+{
+    LinkNode ln;
+
+    for (ln = firstnode(timedfns); ln; ln = nextnode(ln)) {
+	Timedfn ppdat = (Timedfn)getdata(ln);
+	if (ppdat->func == func) {
+	    (void)remnode(timedfns, ln);
+	    zfree(ppdat, sizeof(struct timedfn));
+	    return;
+	}
+    }
+#ifdef DEBUG
+    dputs("BUG: failed to delete node from timedfns");
+#endif
+}
 
 /* the last time we checked mail */
 
@@ -1027,10 +1158,12 @@ preprompt(void)
 	lastmailcheck = time(NULL);
     }
 
-    /* Some people have claimed that C performs type    *
-     * checking, but they were later found to be lying. */
-    for(ln = firstnode(prepromptfns); ln; ln = nextnode(ln))
-	(**(void (**) _((void)))getdata(ln))();
+    if (prepromptfns) {
+	for(ln = firstnode(prepromptfns); ln; ln = nextnode(ln)) {
+	    Prepromptfn ppnode = (Prepromptfn)getdata(ln);
+	    ppnode->func();
+	}
+    }
 }
 
 /**/
diff --git a/Src/zsh.h b/Src/zsh.h
index 3c455b939..19d8a368f 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -333,40 +333,38 @@ enum {
 /* Abstract types for zsh */
 /**************************/
 
-typedef struct linknode  *LinkNode;
-typedef union  linkroot  *LinkList;
-typedef struct hashnode  *HashNode;
-typedef struct hashtable *HashTable;
-
-typedef struct optname   *Optname;
-typedef struct reswd     *Reswd;
 typedef struct alias     *Alias;
-typedef struct param     *Param;
-typedef struct paramdef  *Paramdef;
+typedef struct asgment   *Asgment;
+typedef struct builtin   *Builtin;
 typedef struct cmdnam    *Cmdnam;
-typedef struct shfunc    *Shfunc;
+typedef struct complist  *Complist;
+typedef struct conddef   *Conddef;
 typedef struct funcstack *Funcstack;
 typedef struct funcwrap  *FuncWrap;
-typedef struct options	 *Options;
-typedef struct builtin   *Builtin;
-typedef struct nameddir  *Nameddir;
-typedef struct module    *Module;
-typedef struct linkedmod *Linkedmod;
-
-typedef struct patprog   *Patprog;
-typedef struct process   *Process;
-typedef struct job       *Job;
-typedef struct value     *Value;
-typedef struct conddef   *Conddef;
-typedef struct redir     *Redir;
-typedef struct complist  *Complist;
+typedef struct hashnode  *HashNode;
+typedef struct hashtable *HashTable;
 typedef struct heap      *Heap;
 typedef struct heapstack *Heapstack;
 typedef struct histent   *Histent;
 typedef struct hookdef   *Hookdef;
-
-typedef struct asgment   *Asgment;
-
+typedef struct job       *Job;
+typedef struct linkedmod *Linkedmod;
+typedef struct linknode  *LinkNode;
+typedef union  linkroot  *LinkList;
+typedef struct module    *Module;
+typedef struct nameddir  *Nameddir;
+typedef struct options	 *Options;
+typedef struct optname   *Optname;
+typedef struct param     *Param;
+typedef struct paramdef  *Paramdef;
+typedef struct patprog   *Patprog;
+typedef struct prepromptfn *Prepromptfn;
+typedef struct process   *Process;
+typedef struct redir     *Redir;
+typedef struct reswd     *Reswd;
+typedef struct shfunc    *Shfunc;
+typedef struct timedfn   *Timedfn;
+typedef struct value     *Value;
 
 /********************************/
 /* Definitions for linked lists */
@@ -432,6 +430,28 @@ union linkroot {
         __n0.dat = (void *) (V0); \
     } while (0)
 
+/*************************************/
+/* Specific elements of linked lists */
+/*************************************/
+
+typedef void (*voidvoidfnptr_t) _((void));
+
+/*
+ * Element of the prepromptfns list.
+ */
+struct prepromptfn {
+    voidvoidfnptr_t func;
+};
+
+
+/*
+ * Element of the timedfns list.
+ */
+struct timedfn {
+    voidvoidfnptr_t func;
+    time_t when;
+};
+
 /********************************/
 /* Definitions for syntax trees */
 /********************************/