summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Stephenson <pws@users.sourceforge.net>2002-05-21 11:10:13 +0000
committerPeter Stephenson <pws@users.sourceforge.net>2002-05-21 11:10:13 +0000
commitafe1b00e16c7efc5c93c958fa4f357325977a2a1 (patch)
tree7a849e6fd30ece41051716bb900fc093f8792889
parent5a89ede77be4ff696a77fb609ab29f5bf1e1dc37 (diff)
downloadzsh-afe1b00e16c7efc5c93c958fa4f357325977a2a1.tar.gz
zsh-afe1b00e16c7efc5c93c958fa4f357325977a2a1.zip
17141 plus mods: add `zle -F fd handler' feature.
-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;