summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog7
-rw-r--r--Doc/Zsh/zle.yo60
-rw-r--r--Src/Zle/zle_main.c195
-rw-r--r--Src/Zle/zle_thingy.c103
-rw-r--r--Src/utils.c46
5 files changed, 343 insertions, 68 deletions
diff --git a/ChangeLog b/ChangeLog
index 985095b1b..d7c502d51 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2002-05-21  Peter Stephenson  <pws@csr.com>
+
+	* 17141 plus mods: Src/utils.c, Src/Zle/zle_main.c,
+	Src/Zle/zle_thingy.c, Doc/Zsh/zle.yo: `zle -F fd handler'
+	installs shell function handler for when data becomes available
+	on fd while zle is active.  Requires select().
+
 2002-05-21  Sven Wischnowsky  <wischnow@zsh.org>
 
 	* 17195: Src/Zle/comp.h, Src/Zle/compcore.c, Src/Zle/compctl.c,
diff --git a/Doc/Zsh/zle.yo b/Doc/Zsh/zle.yo
index aee624eb7..67e66830d 100644
--- a/Doc/Zsh/zle.yo
+++ b/Doc/Zsh/zle.yo
@@ -314,6 +314,7 @@ xitem(tt(zle) tt(-R) [ tt(-c) ] [ var(display-string) ] [ var(string) ... ])
 xitem(tt(zle) tt(-M) var(string))
 xitem(tt(zle) tt(-U) var(string))
 xitem(tt(zle) tt(-K) var(keymap))
+xitem(tt(zle) tt(-F) [ tt(-L) ] [ var(fd) [ var(handler) ] ])
 xitem(tt(zle) tt(-I))
 xitem(tt(zle) var(widget) tt([ -n) var(num) tt(]) tt([ -N ]) var(args) ...)
 item(tt(zle))(
@@ -411,6 +412,65 @@ This keymap selection affects the interpretation of following keystrokes
 within this invocation of ZLE.  Any following invocation (e.g., the next
 command line) will start as usual with the `tt(main)' keymap selected.
 )
+item(tt(-F) [ tt(-L) ] [ var(fd) [ var(handler) ] ])(
+Only available if your system support the `select' system call; most
+modern systems do.
+
+Installs var(handler) (the name of a shell function) to handle input from
+file descriptor var(fd).  When zle is attempting to read data, it will
+examine both the terminal and the list of handled var(fd)'s.  If data
+becomes available on a handled var(fd), zle will call var(handler) with
+the fd which is ready for reading as the only argument.  If the handler
+produces output to the terminal, it should call `tt(zle -I)' before doing
+so (see below).  The handler should not attempt to read from the terminal.
+Note that zle makes no attempt to check whether this fd is actually
+readable when installing the handler.  The user must make their own
+arrangments for handling the file descriptor when zle is not active.
+
+Any number of handlers for any number of readable file descriptors may be
+installed.  Installing a handler for an var(fd) which is already handled
+causes the existing handler to be replaced.
+
+If no var(handler) is given, but an var(fd) is present, any handler for
+that var(fd) is removed.  If there is none, an error message is printed
+and status 1 is returned.
+
+If no arguments are given, or the tt(-L) option is supplied, a list of
+handlers is printed in a form which can be stored for later execution.
+
+An var(fd) (but not a var(handler)) may optionally be given with the tt(-L)
+option; in this case, the function will list the handler if any, else
+silently return status 1.
+
+Note that this feature should be used with care.  Activity on one of the
+var(fd)'s which is not properly handled can cause the terminal to become
+unusable.
+
+Here is a simple example of using this feature.  A connection to a remote
+TCP port is created using the ztcp command; see 
+ifzman(the description of the tt(zsh/net/tcp) module in zmanref(zshmodules))\
+ifnzman(noderef(The zsh/net/tcp Module)).  Then a handler is installed
+which simply prints out any data which arrives on this connection.  Note
+that `select' will indicate that the file descriptor needs handling
+if the remote side has closed the connection; we handle that by testing
+for a failed read.
+example(if ztcp pwspc 2811; then
+  tcpfd=$REPLY
+  handler() {
+    zle -I
+    local line
+    if ! read -r line <&$1; then
+      # select marks this fd if we reach EOF,
+      # so handle this specially.
+      print "[Read on fd $1 failed, removing.]" >&2
+      zle -F $1
+      return 1
+    fi
+    print -r - $line
+  }
+  zle -F $tcpfd handler
+fi)
+)
 item(tt(-I))(
 Unusually, this option is only useful em(outside) ordinary widget functions.
 It invalidates the current zle display in preparation for output; usually
diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c
index 5af18fbac..56797a9a9 100644
--- a/Src/Zle/zle_main.c
+++ b/Src/Zle/zle_main.c
@@ -141,6 +141,17 @@ mod_export char *zlenoargs[1] = { NULL };
 static int delayzsetterm;
 #endif
 
+/*
+ * File descriptors we are watching as well as the terminal fd. 
+ * These are all for reading; we don't watch for writes or exceptions.
+ */
+/**/
+int nwatch;		/* Number of fd's we are watching */
+/**/
+int *watch_fds;		/* The list of fds, not terminated! */
+/**/
+char **watch_funcs;	/* The corresponding functions to call, normal array */
+
 /* set up terminal */
 
 /**/
@@ -324,86 +335,174 @@ breakread(int fd, char *buf, int n)
 # define read    breakread
 #endif
 
-/**/
-mod_export int
-getkey(int keytmout)
+static int
+raw_getkey(int keytmout, char *cptr)
 {
-    char cc;
-    unsigned int ret;
     long exp100ths;
-    int die = 0, r, icnt = 0;
-    int old_errno = errno, obreaks = breaks;
-
+    int ret;
 #ifdef HAVE_SELECT
     fd_set foofd;
-
 #else
 # ifdef HAS_TIO
     struct ttyinfo ti;
-
 # endif
 #endif
 
-    if (kungetct)
-	ret = STOUC(kungetbuf[--kungetct]);
-    else {
+    /*
+     * 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.
+     */
+    if ((nwatch || keytmout)
 #ifdef FIONREAD
-	if (delayzsetterm) {
-	    int val;
-	    ioctl(SHTTY, FIONREAD, (char *)&val);
-	    if (!val)
-		zsetterm();
-	}
+	&& ! delayzsetterm
 #endif
-	if (keytmout
-#ifdef FIONREAD
-	    && ! delayzsetterm
-#endif
-	    ) {
-	    if (keytimeout > 500)
-		exp100ths = 500;
-	    else if (keytimeout > 0)
-		exp100ths = keytimeout;
-	    else
-		exp100ths = 0;
+	) {
+	if (!keytmout || keytimeout <= 0)
+	    exp100ths = 0;
+	else if (keytimeout > 500)
+	    exp100ths = 500;
+	else
+	    exp100ths = keytimeout;
 #ifdef HAVE_SELECT
+	if (!keytmout || exp100ths) {
+	    struct timeval *tvptr = NULL;
+	    struct timeval expire_tv;
+	    int i, fdmax = SHTTY, errtry = 0;
 	    if (exp100ths) {
-		struct timeval expire_tv;
-
 		expire_tv.tv_sec = exp100ths / 100;
 		expire_tv.tv_usec = (exp100ths % 100) * 10000L;
+		tvptr = &expire_tv;
+	    }
+	    do {
+		int selret;
 		FD_ZERO(&foofd);
 		FD_SET(SHTTY, &foofd);
-		if (select(SHTTY+1, (SELECT_ARG_2_T) & foofd,
-			   NULL, NULL, &expire_tv) <= 0)
-		    return EOF;
-	    }
+		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);
+		/*
+		 * Make sure a user interrupt gets passed on straight away.
+		 */
+		if (selret < 0 && errflag)
+		    return selret;
+		/*
+		 * 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 && !keytmout && !errtry) {
+		    errtry = 1;
+		    continue;
+		}
+		if (selret == 0) {
+		    /* Special value -2 signals nothing ready */
+		    return -2;
+		} else if (selret < 0)
+		    return selret;
+		if (!keytmout && nwatch) {
+		    /*
+		     * 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 (FD_ISSET(lwatch_fds[i], &foofd)) {
+			    /* 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));
+			    }
+
+			    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 (!FD_ISSET(SHTTY, &foofd));
+	}
 #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 = shttyinfo;
+	ti.tio.c_lflag &= ~ICANON;
+	ti.tio.c_cc[VMIN] = 0;
+	ti.tio.c_cc[VTIME] = exp100ths / 10;
 #  ifdef HAVE_TERMIOS_H
-	    tcsetattr(SHTTY, TCSANOW, &ti.tio);
+	tcsetattr(SHTTY, TCSANOW, &ti.tio);
 #  else
-	    ioctl(SHTTY, TCSETA, &ti.tio);
+	ioctl(SHTTY, TCSETA, &ti.tio);
 #  endif
-	    r = read(SHTTY, &cc, 1);
+	ret = read(SHTTY, cptr, 1);
 #  ifdef HAVE_TERMIOS_H
-	    tcsetattr(SHTTY, TCSANOW, &shttyinfo.tio);
+	tcsetattr(SHTTY, TCSANOW, &shttyinfo.tio);
 #  else
-	    ioctl(SHTTY, TCSETA, &shttyinfo.tio);
+	ioctl(SHTTY, TCSETA, &shttyinfo.tio);
 #  endif
-	    return (r <= 0) ? EOF : cc;
+	return (ret <= 0) ? ret : *cptr;
 # endif
 #endif
+    }
+
+    ret = read(SHTTY, cptr, 1);
+
+    return ret;
+}
+
+/**/
+mod_export int
+getkey(int keytmout)
+{
+    char cc;
+    unsigned int ret;
+    int die = 0, r, icnt = 0;
+    int old_errno = errno, obreaks = breaks;
+
+    if (kungetct)
+	ret = STOUC(kungetbuf[--kungetct]);
+    else {
+#ifdef FIONREAD
+	if (delayzsetterm) {
+	    int val;
+	    ioctl(SHTTY, FIONREAD, (char *)&val);
+	    if (!val)
+		zsetterm();
 	}
+#endif
 	for (;;) {
 	    int q = queue_signal_level();
 	    dont_queue_signals();
-	    r = read(SHTTY, &cc, 1);
+	    r = raw_getkey(keytmout, &cc);
 	    restore_queue_signals(q);
+	    if (r == -2)	/* timeout */
+		return EOF;
 	    if (r == 1)
 		break;
 	    if (r == 0) {
@@ -1101,7 +1200,7 @@ zleaftertrap(Hookdef dummy, void *dat)
 static struct builtin bintab[] = {
     BUILTIN("bindkey", 0, bin_bindkey, 0, -1, 0, "evaMldDANmrsLRp", NULL),
     BUILTIN("vared",   0, bin_vared,   1,  7, 0, NULL,             NULL),
-    BUILTIN("zle",     0, bin_zle,     0, -1, 0, "lDANCLmMgGcRaUKI", NULL),
+    BUILTIN("zle",     0, bin_zle,     0, -1, 0, "aAcCDFgGIKlLmMNRU", NULL),
 };
 
 /* The order of the entries in this table has to match the *HOOK
diff --git a/Src/Zle/zle_thingy.c b/Src/Zle/zle_thingy.c
index dd2aea40d..7cf558f97 100644
--- a/Src/Zle/zle_thingy.c
+++ b/Src/Zle/zle_thingy.c
@@ -341,6 +341,7 @@ bin_zle(char *name, char **args, char *ops, int func)
 	{ 'U', bin_zle_unget, 1, 1 },
 	{ 'K', bin_zle_keymap, 1, 1 },
 	{ 'I', bin_zle_invalidate, 0, 0 },
+	{ 'F', bin_zle_fd, 0, 2 },
 	{ 0,   bin_zle_call, 0, -1 },
     };
     struct opn const *op, *opp;
@@ -678,6 +679,108 @@ bin_zle_invalidate(char *name, char **args, char *ops, char func)
 	return 1;
 }
 
+/**/
+static int
+bin_zle_fd(char *name, char **args, char *ops, char func)
+{
+    int fd = 0, i, found = 0;
+    char *endptr;
+
+    if (*args) {
+	fd = (int)zstrtol(*args, &endptr, 10);
+
+	if (*endptr || fd < 0) {
+	    zwarnnam(name, "Bad file descriptor number for -F: %s", *args, 0);
+	    return 1;
+	}
+    }
+
+    if (ops['L'] || !*args) {
+	/* Listing handlers. */
+	if (args[1]) {
+	    zwarnnam(name, "too many arguments for -FL", NULL, 0);
+	    return 1;
+	}
+	for (i = 0; i < nwatch; i++) {
+	    if (*args && watch_fds[i] != fd)
+		continue;
+	    found = 1;
+	    printf("%s -F %d %s\n", name, watch_fds[i], watch_funcs[i]);
+	}
+	/* only return status 1 if fd given and not found */
+	return *args && !found;
+    }
+
+    if (args[1]) {
+	/* Adding or replacing a handler */
+	char *funcnam = ztrdup(args[1]);
+	if (nwatch) {
+	    for (i = 0; i < nwatch; i++) {
+		if (watch_fds[i] == fd) {
+		    zsfree(watch_funcs[i]);
+		    watch_funcs[i] = funcnam;
+		    found = 1;
+		    break;
+		}
+	    }
+	}
+	if (!found) {
+	    /* zrealloc handles NULL pointers, so OK for first time through */
+	    int newnwatch = nwatch+1;
+	    watch_fds = (int *)zrealloc(watch_fds, 
+					newnwatch * sizeof(int));
+	    watch_funcs = (char **)zrealloc(watch_funcs,
+					    (newnwatch+1) * sizeof(char *));
+	    watch_fds[nwatch] = fd;
+	    watch_funcs[nwatch] = funcnam;
+	    watch_funcs[newnwatch] = NULL;
+	    nwatch = newnwatch;
+	}
+    } else {
+	/* Deleting a handler */
+	for (i = 0; i < nwatch; i++) {
+	    if (watch_fds[i] == fd) {
+		int newnwatch = nwatch-1;
+		int *new_fds;
+		char **new_funcs;
+
+		zsfree(watch_funcs[i]);
+		if (newnwatch) {
+		    new_fds = zalloc(newnwatch*sizeof(int));
+		    new_funcs = zalloc((newnwatch+1)*sizeof(char*));
+		    if (i) {
+			memcpy(new_fds, watch_fds, i*sizeof(int));
+			memcpy(new_funcs, watch_funcs, i*sizeof(char *));
+		    }
+		    if (i < newnwatch) {
+			memcpy(new_fds+i, watch_fds+i+1,
+			       (newnwatch-i)*sizeof(int));
+			memcpy(new_funcs+i, watch_funcs+i+1,
+			       (newnwatch-i)*sizeof(char *));
+		    }
+		    new_funcs[newnwatch] = NULL;
+		} else {
+		    new_fds = NULL;
+		    new_funcs = NULL;
+		}
+		zfree(watch_fds, nwatch*sizeof(int));
+		zfree(watch_funcs, (nwatch+1)*sizeof(char *));
+		watch_fds = new_fds;
+		watch_funcs = new_funcs;
+		nwatch = newnwatch;
+		found = 1;
+		break;
+	    }
+	}
+	if (!found) {
+	    zwarnnam(name, "No handler installed for fd %d", NULL, fd);
+	    return 1;
+	}
+    }
+
+    return 0;
+}
+
 /*******************/
 /* initialiasation */
 /*******************/
diff --git a/Src/utils.c b/Src/utils.c
index 6a0c27e29..24d336b7a 100644
--- a/Src/utils.c
+++ b/Src/utils.c
@@ -624,6 +624,30 @@ time_t lastmailcheck;
 /**/
 time_t lastwatch;
 
+/**/
+mod_export int
+callhookfunc(char *name, LinkList lnklst)
+{
+    Eprog prog;
+
+    if ((prog = getshfunc(name)) != &dummy_eprog) {
+	/*
+	 * Save stopmsg, since user doesn't get a chance to respond
+	 * to a list of jobs generated in a hook.
+	 */
+	int osc = sfcontext, osm = stopmsg;
+
+	sfcontext = SFC_HOOK;
+	doshfunc(name, prog, lnklst, 0, 1);
+	sfcontext = osc;
+	stopmsg = osm;
+
+	return 0;
+    }
+
+    return 1;
+}
+
 /* do pre-prompt stuff */
 
 /**/
@@ -632,7 +656,6 @@ preprompt(void)
 {
     static time_t lastperiodic;
     LinkNode ln;
-    Eprog prog;
     int period = getiparam("PERIOD");
     int mailcheck = getiparam("MAILCHECK");
 
@@ -645,18 +668,7 @@ preprompt(void)
 
     /* If a shell function named "precmd" exists, *
      * then execute it.                           */
-    if ((prog = getshfunc("precmd")) != &dummy_eprog) {
-	/*
-	 * Save stopmsg, since user doesn't get a chance to respond
-	 * to a list of jobs generated in precmd.
-	 */
-	int osc = sfcontext, osm = stopmsg;
-
-	sfcontext = SFC_HOOK;
-	doshfunc("precmd", prog, NULL, 0, 1);
-	sfcontext = osc;
-	stopmsg = osm;
-    }
+    callhookfunc("precmd", NULL);
     if (errflag)
 	return;
 
@@ -664,14 +676,8 @@ preprompt(void)
      * "periodic" exists, 3) it's been greater than PERIOD since we *
      * executed "periodic", then execute it now.                    */
     if (period && (time(NULL) > lastperiodic + period) &&
-	(prog = getshfunc("periodic")) != &dummy_eprog) {
-	int osc = sfcontext;
-
-	sfcontext = SFC_HOOK;
-	doshfunc("periodic", prog, NULL, 0, 1);
-	sfcontext = osc;
+	!callhookfunc("periodic", NULL))
 	lastperiodic = time(NULL);
-    }
     if (errflag)
 	return;