diff options
Diffstat (limited to 'Src/Zle/zle_main.c')
-rw-r--r-- | Src/Zle/zle_main.c | 907 |
1 files changed, 907 insertions, 0 deletions
diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c new file mode 100644 index 000000000..338cdef41 --- /dev/null +++ b/Src/Zle/zle_main.c @@ -0,0 +1,907 @@ +/* + * zle_main.c - main routines for line editor + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "zle.mdh" +#include "zle_main.pro" + +/* != 0 if we're done editing */ + +/**/ +int done; + +/* location of mark */ + +/**/ +int mark; + +/* last character pressed */ + +/**/ +int c; + +/* the binding for this key */ + +/**/ +Thingy bindk; + +/* insert mode/overwrite mode flag */ + +/**/ +int insmode; + +static int eofchar, eofsent; +static long keytimeout; + +#ifdef HAVE_SELECT +/* Terminal baud rate */ + +static int baud; +#endif + +/* flags associated with last command */ + +/**/ +int lastcmd; + +/* the status line, and its length */ + +/**/ +char *statusline; +/**/ +int statusll; + +/* The current history line and cursor position for the top line * + * on the buffer stack. */ + +/**/ +int stackhist, stackcs; + +/* != 0 if we are making undo records */ + +/**/ +int undoing; + +/* current modifier status */ + +/**/ +struct modifier zmod; + +/* Current command prefix status. This is normally 0. Prefixes set * + * this to 1. Each time round the main loop, this is checked: if it * + * is 0, the modifier status is reset; if it is 1, the modifier * + * status is left unchanged, and this flag is reset to 0. The * + * effect is that several prefix commands can be executed, and have * + * cumulative effect, but any other command execution will clear the * + * modifiers. */ + +/**/ +int prefixflag; + +/* != 0 if there is a pending beep (usually indicating an error) */ + +/**/ +int feepflag; + +/* set up terminal */ + +/**/ +void +setterm(void) +{ + struct ttyinfo ti; + +#if defined(CLOBBERS_TYPEAHEAD) && defined(FIONREAD) + int val; + + ioctl(SHTTY, FIONREAD, (char *)&val); + if (val) + return; +#endif + +/* sanitize the tty */ +#ifdef HAS_TIO + shttyinfo.tio.c_lflag |= ICANON | ECHO; +# ifdef FLUSHO + shttyinfo.tio.c_lflag &= ~FLUSHO; +# endif +#else /* not HAS_TIO */ + shttyinfo.sgttyb.sg_flags = (shttyinfo.sgttyb.sg_flags & ~CBREAK) | ECHO; + shttyinfo.lmodes &= ~LFLUSHO; +#endif + + attachtty(mypgrp); + ti = shttyinfo; +#ifdef HAS_TIO + if (unset(FLOWCONTROL)) + ti.tio.c_iflag &= ~IXON; + ti.tio.c_lflag &= ~(ICANON | ECHO +# ifdef FLUSHO + | FLUSHO +# endif + ); +# ifdef TAB3 + ti.tio.c_oflag &= ~TAB3; +# else +# ifdef OXTABS + ti.tio.c_oflag &= ~OXTABS; +# else + ti.tio.c_oflag &= ~XTABS; +# endif +# endif + ti.tio.c_oflag |= ONLCR; + ti.tio.c_cc[VQUIT] = +# ifdef VDISCARD + ti.tio.c_cc[VDISCARD] = +# endif +# ifdef VSUSP + ti.tio.c_cc[VSUSP] = +# endif +# ifdef VDSUSP + ti.tio.c_cc[VDSUSP] = +# endif +# ifdef VSWTCH + ti.tio.c_cc[VSWTCH] = +# endif +# ifdef VLNEXT + ti.tio.c_cc[VLNEXT] = +# endif + VDISABLEVAL; +# if defined(VSTART) && defined(VSTOP) + if (unset(FLOWCONTROL)) + ti.tio.c_cc[VSTART] = ti.tio.c_cc[VSTOP] = VDISABLEVAL; +# endif + eofchar = ti.tio.c_cc[VEOF]; + ti.tio.c_cc[VMIN] = 1; + ti.tio.c_cc[VTIME] = 0; + ti.tio.c_iflag |= (INLCR | ICRNL); + /* this line exchanges \n and \r; it's changed back in getkey + so that the net effect is no change at all inside the shell. + This double swap is to allow typeahead in common cases, eg. + + % bindkey -s '^J' 'echo foo^M' + % sleep 10 + echo foo<return> <--- typed before sleep returns + + The shell sees \n instead of \r, since it was changed by the kernel + while zsh wasn't looking. Then in getkey() \n is changed back to \r, + and it sees "echo foo<accept line>", as expected. Without the double + swap the shell would see "echo foo\n", which is translated to + "echo fooecho foo<accept line>" because of the binding. + Note that if you type <line-feed> during the sleep the shell just sees + \n, which is translated to \r in getkey(), and you just get another + prompt. For type-ahead to work in ALL cases you have to use + stty inlcr. + + Unfortunately it's IMPOSSIBLE to have a general solution if both + <return> and <line-feed> are mapped to the same character. The shell + could check if there is input and read it before setting it's own + terminal modes but if we get a \n we don't know whether to keep it or + change to \r :-( + */ + +#else /* not HAS_TIO */ + ti.sgttyb.sg_flags = (ti.sgttyb.sg_flags | CBREAK) & ~ECHO & ~XTABS; + ti.lmodes &= ~LFLUSHO; + eofchar = ti.tchars.t_eofc; + ti.tchars.t_quitc = + ti.ltchars.t_suspc = + ti.ltchars.t_flushc = + ti.ltchars.t_dsuspc = ti.ltchars.t_lnextc = -1; +#endif + +#if defined(TTY_NEEDS_DRAINING) && defined(TIOCOUTQ) && defined(HAVE_SELECT) + if (baud) { /**/ + int n = 0; + + while ((ioctl(SHTTY, TIOCOUTQ, (char *)&n) >= 0) && n) { + struct timeval tv; + + tv.tv_sec = n / baud; + tv.tv_usec = ((n % baud) * 1000000) / baud; + select(0, NULL, NULL, NULL, &tv); + } + } +#endif + + settyinfo(&ti); +} + +static char *kungetbuf; +static int kungetct, kungetsz; + +/**/ +void +ungetkey(int ch) +{ + if (kungetct == kungetsz) + kungetbuf = realloc(kungetbuf, kungetsz *= 2); + kungetbuf[kungetct++] = ch; +} + +/**/ +void +ungetkeys(char *s, int len) +{ + s += len; + while (len--) + ungetkey(*--s); +} + +#if defined(pyr) && defined(HAVE_SELECT) +static int +breakread(int fd, char *buf, int n) +{ + fd_set f; + + FD_ZERO(&f); + FD_SET(fd, &f); + return (select(fd + 1, (SELECT_ARG_2_T) & f, NULL, NULL, NULL) == -1 ? + EOF : read(fd, buf, n)); +} + +# define read breakread +#endif + +/**/ +int +getkey(int keytmout) +{ + char cc; + unsigned int ret; + long exp100ths; + int die = 0, r, icnt = 0; + int old_errno = errno; + +#ifdef HAVE_SELECT + fd_set foofd; + +#else +# ifdef HAS_TIO + struct ttyinfo ti; + +# endif +#endif + + if (kungetct) + ret = STOUC(kungetbuf[--kungetct]); + else { + if (keytmout) { + if (keytimeout > 500) + exp100ths = 500; + else if (keytimeout > 0) + exp100ths = keytimeout; + else + exp100ths = 0; +#ifdef HAVE_SELECT + if (exp100ths) { + struct timeval expire_tv; + + expire_tv.tv_sec = exp100ths / 100; + expire_tv.tv_usec = (exp100ths % 100) * 10000L; + FD_ZERO(&foofd); + FD_SET(SHTTY, &foofd); + if (select(SHTTY+1, (SELECT_ARG_2_T) & foofd, + NULL, NULL, &expire_tv) <= 0) + return EOF; + } +#else +# ifdef HAS_TIO + 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); +# else + ioctl(SHTTY, TCSETA, &ti.tio); +# endif + r = read(SHTTY, &cc, 1); +# ifdef HAVE_TERMIOS_H + tcsetattr(SHTTY, TCSANOW, &shttyinfo.tio); +# else + ioctl(SHTTY, TCSETA, &shttyinfo.tio); +# endif + return (r <= 0) ? EOF : cc; +# endif +#endif + } + while ((r = read(SHTTY, &cc, 1)) != 1) { + if (r == 0) { + /* The test for IGNOREEOF was added to make zsh ignore ^Ds + that were typed while commands are running. Unfortuantely + this caused trouble under at least one system (SunOS 4.1). + Here shells that lost their xterm (e.g. if it was killed + with -9) didn't fail to read from the terminal but instead + happily continued to read EOFs, so that the above read + returned with 0, and, with IGNOREEOF set, this caused + an infinite loop. The simple way around this was to add + the counter (icnt) so that this happens 20 times and than + the shell gives up (yes, this is a bit dirty...). */ + if (isset(IGNOREEOF) && icnt++ < 20) + continue; + stopmsg = 1; + zexit(1, 0); + } + icnt = 0; + if (errno == EINTR) { + die = 0; + if (!errflag && !retflag && !breaks) + continue; + errflag = 0; + errno = old_errno; + return EOF; + } else if (errno == EWOULDBLOCK) { + fcntl(0, F_SETFL, 0); + } else if (errno == EIO && !die) { + ret = opts[MONITOR]; + opts[MONITOR] = 1; + attachtty(mypgrp); + refresh(); /* kludge! */ + opts[MONITOR] = ret; + die = 1; + } else if (errno != 0) { + zerr("error on TTY read: %e", NULL, errno); + stopmsg = 1; + zexit(1, 0); + } + } + if (cc == '\r') /* undo the exchange of \n and \r determined by */ + cc = '\n'; /* setterm() */ + else if (cc == '\n') + cc = '\r'; + + ret = STOUC(cc); + } + if (vichgflag) { + if (vichgbufptr == vichgbufsz) + vichgbuf = realloc(vichgbuf, vichgbufsz *= 2); + vichgbuf[vichgbufptr++] = ret; + } + errno = old_errno; + return ret; +} + +/* Read a line. It is returned metafied. */ + +/**/ +unsigned char * +zleread(char *lp, char *rp, int ha) +{ + unsigned char *s; + int old_errno = errno; + int tmout = getiparam("TMOUT"); + +#ifdef HAVE_SELECT + long costmult; + struct timeval tv; + fd_set foofd; + + baud = getiparam("BAUD"); + costmult = (baud) ? 3840000L / baud : 0; + tv.tv_sec = 0; +#endif + + /* ZLE doesn't currently work recursively. This is needed in case a * + * select loop is used in a function called from ZLE. vared handles * + * this differently itself. */ + if(zleactive) { + char *pptbuf; + int pptlen; + + pptbuf = unmetafy(promptexpand(lp, 0, NULL, NULL), &pptlen); + write(2, (WRITE_ARG_2_T)pptbuf, pptlen); + free(pptbuf); + return (unsigned char *)shingetline(); + } + + keytimeout = getiparam("KEYTIMEOUT"); + if (!shout) { + if (SHTTY != -1) + init_shout(); + + if (!shout) + return NULL; + /* We could be smarter and default to a system read. */ + + /* If we just got a new shout, make sure the terminal is set up. */ + if (termflags & TERM_UNKNOWN) + init_term(); + } + + fflush(shout); + fflush(stderr); + intr(); + insmode = unset(OVERSTRIKE); + eofsent = 0; + resetneeded = 0; + lpptbuf = promptexpand(lp, 1, NULL, NULL); + pmpt_attr = txtchange; + rpptbuf = promptexpand(rp, 1, NULL, NULL); + rpmpt_attr = txtchange; + histallowed = ha; + PERMALLOC { + histline = curhist; +#ifdef HAVE_SELECT + FD_ZERO(&foofd); +#endif + undoing = 1; + line = (unsigned char *)zalloc((linesz = 256) + 2); + virangeflag = lastcmd = done = cs = ll = mark = 0; + curhistline = NULL; + vichgflag = 0; + viinsbegin = 0; + statusline = NULL; + selectkeymap("main", 1); + fixsuffix(); + if ((s = (unsigned char *)getlinknode(bufstack))) { + setline((char *)s); + zsfree((char *)s); + if (stackcs != -1) { + cs = stackcs; + stackcs = -1; + if (cs > ll) + cs = ll; + } + if (stackhist != -1) { + histline = stackhist; + stackhist = -1; + } + } + initundo(); + if (isset(PROMPTCR)) + putc('\r', shout); + if (tmout) + alarm(tmout); + zleactive = 1; + resetneeded = 1; + errflag = retflag = 0; + lastcol = -1; + initmodifier(&zmod); + prefixflag = 0; + feepflag = 0; + refresh(); + while (!done && !errflag) { + + statusline = NULL; + vilinerange = 0; + reselectkeymap(); + bindk = getkeycmd(); + if (!ll && isfirstln && c == eofchar) { + eofsent = 1; + break; + } + if (bindk) { + execzlefunc(bindk); + handleprefixes(); + /* for vi mode, make sure the cursor isn't somewhere illegal */ + if (invicmdmode() && cs > findbol() && + (cs == ll || line[cs] == '\n')) + cs--; + if (undoing) + handleundo(); + } else { + errflag = 1; + break; + } +#ifdef HAVE_SELECT + if (baud && !(lastcmd & ZLE_MENUCMP)) { + FD_SET(SHTTY, &foofd); + if ((tv.tv_usec = cost * costmult) > 500000) + tv.tv_usec = 500000; + if (!kungetct && select(SHTTY+1, (SELECT_ARG_2_T) & foofd, + NULL, NULL, &tv) <= 0) + refresh(); + } else +#endif + if (!kungetct) + refresh(); + handlefeep(); + } + statusline = NULL; + invalidatelist(); + trashzle(); + free(lpptbuf); + free(rpptbuf); + zleactive = 0; + alarm(0); + } LASTALLOC; + zsfree(curhistline); + freeundo(); + if (eofsent) { + free(line); + line = NULL; + } else { + line[ll++] = '\n'; + line = (unsigned char *) metafy((char *) line, ll, META_REALLOC); + } + forget_edits(); + errno = old_errno; + return line; +} + +/* execute a widget */ + +/**/ +void +execzlefunc(Thingy func) +{ + Widget w; + + if(func->flags & DISABLED) { + /* this thingy is not the name of a widget */ + char *nm = niceztrdup(func->nam); + char *msg = tricat("No such widget `", nm, "'"); + + zsfree(nm); + showmsg(msg); + zsfree(msg); + feep(); + } else if((w = func->widget)->flags & WIDGET_INT) { + int wflags = w->flags; + + if(!(wflags & ZLE_KEEPSUFFIX)) + removesuffix(); + if(!(wflags & ZLE_MENUCMP)) { + fixsuffix(); + invalidatelist(); + } + if (wflags & ZLE_LINEMOVE) + vilinerange = 1; + if(!(wflags & ZLE_LASTCOL)) + lastcol = -1; + w->u.fn(); + lastcmd = wflags; + } else { + List l = getshfunc(w->u.fnnam); + + if(l == &dummy_list) { + /* the shell function doesn't exist */ + char *nm = niceztrdup(w->u.fnnam); + char *msg = tricat("No such shell function `", nm, "'"); + + zsfree(nm); + showmsg(msg); + zsfree(msg); + feep(); + } else { + startparamscope(); + makezleparams(); + doshfunc(l, NULL, 0, 1); + endparamscope(); + lastcmd = 0; + } + } +} + +/* initialise command modifiers */ + +/**/ +static void +initmodifier(struct modifier *mp) +{ + mp->flags = 0; + mp->mult = 1; + mp->tmult = 1; + mp->vibuf = 0; +} + +/* Reset command modifiers, unless the command just executed was a prefix. * + * Also set zmult, if the multiplier has been amended. */ + +/**/ +static void +handleprefixes(void) +{ + if (prefixflag) { + prefixflag = 0; + if(zmod.flags & MOD_TMULT) { + zmod.flags |= MOD_MULT; + zmod.mult = zmod.tmult; + } + } else + initmodifier(&zmod); +} + +/* vared: edit (literally) a parameter value */ + +/**/ +static int +bin_vared(char *name, char **args, char *ops, int func) +{ + char *s; + char *t; + Param pm; + int create = 0; + char *p1 = NULL, *p2 = NULL; + + /* all options are handled as arguments */ + while (*args && **args == '-') { + while (*++(*args)) + switch (**args) { + case 'c': + /* -c option -- allow creation of the parameter if it doesn't + yet exist */ + create = 1; + break; + case 'p': + /* -p option -- set main prompt string */ + if ((*args)[1]) + p1 = *args + 1, *args = "" - 1; + else if (args[1]) + p1 = *(++args), *args = "" - 1; + else { + zwarnnam(name, "prompt string expected after -%c", NULL, + **args); + return 1; + } + break; + case 'r': + /* -r option -- set right prompt string */ + if ((*args)[1]) + p2 = *args + 1, *args = "" - 1; + else if (args[1]) + p2 = *(++args), *args = "" - 1; + else { + zwarnnam(name, "prompt string expected after -%c", NULL, + **args); + return 1; + } + break; + case 'h': + /* -h option -- enable history */ + ops['h'] = 1; + break; + default: + /* unrecognised option character */ + zwarnnam(name, "unknown option: %s", *args, 0); + return 1; + } + args++; + } + + /* check we have a parameter name */ + if (!*args) { + zwarnnam(name, "missing variable", NULL, 0); + return 1; + } + /* handle non-existent parameter */ + if (!(s = getsparam(args[0]))) { + if (create) + createparam(args[0], PM_SCALAR); + else { + zwarnnam(name, "no such variable: %s", args[0], 0); + return 1; + } + } + + if(zleactive) { + zwarnnam(name, "ZLE cannot be used recursively (yet)", NULL, 0); + return 1; + } + + /* edit the parameter value */ + PERMALLOC { + pushnode(bufstack, ztrdup(s)); + } LASTALLOC; + t = (char *) zleread(p1, p2, ops['h']); + if (!t || errflag) { + /* error in editing */ + errflag = 0; + return 1; + } + /* strip off trailing newline, if any */ + if (t[strlen(t) - 1] == '\n') + t[strlen(t) - 1] = '\0'; + /* final assignment of parameter value */ + pm = (Param) paramtab->getnode(paramtab, args[0]); + if (pm && PM_TYPE(pm->flags) == PM_ARRAY) { + char **a; + + PERMALLOC { + a = spacesplit(t, 1); + } LASTALLOC; + setaparam(args[0], a); + } else + setsparam(args[0], t); + return 0; +} + +/**/ +void +describekeybriefly(void) +{ + char *seq, *str, *msg, *is; + Thingy func; + + if (statusline) + return; + statusline = "Describe key briefly: _"; + statusll = strlen(statusline); + refresh(); + seq = getkeymapcmd(curkeymap, &func, &str); + statusline = NULL; + if(!*seq) + return; + msg = bindztrdup(seq); + msg = appstr(msg, " is "); + if (!func) + is = bindztrdup(str); + else + is = niceztrdup(func->nam); + msg = appstr(msg, is); + zsfree(is); + showmsg(msg); + zsfree(msg); +} + +#define MAXFOUND 4 + +struct findfunc { + Thingy func; + int found; + char *msg; +}; + +/**/ +static void +scanfindfunc(char *seq, Thingy func, char *str, void *magic) +{ + struct findfunc *ff = magic; + + if(func != ff->func) + return; + if (!ff->found++) + ff->msg = appstr(ff->msg, " is on"); + if(ff->found <= MAXFOUND) { + char *b = bindztrdup(seq); + + ff->msg = appstr(ff->msg, " "); + ff->msg = appstr(ff->msg, b); + zsfree(b); + } +} + +/**/ +void +whereis(void) +{ + struct findfunc ff; + + if (!(ff.func = executenamedcommand("Where is: "))) + return; + ff.found = 0; + ff.msg = niceztrdup(ff.func->nam); + scankeymap(curkeymap, 1, scanfindfunc, &ff); + if (!ff.found) + ff.msg = appstr(ff.msg, " is not bound to any key"); + else if(ff.found > MAXFOUND) + ff.msg = appstr(ff.msg, " et al"); + showmsg(ff.msg); + zsfree(ff.msg); +} + +/**/ +void +trashzle(void) +{ + if (zleactive) { + /* This refresh() 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 * + * recursion [at least, it would if refresh() didn't have its * + * extra `inlist' check]). */ + int sl = showinglist; + showinglist = 0; + refresh(); + showinglist = sl; + moveto(nlnct, 0); + if (clearflag && tccan(TCCLEAREOD)) { + tcout(TCCLEAREOD); + clearflag = 0; + } + if (postedit) + fprintf(shout, "%s", postedit); + fflush(shout); + resetneeded = 1; + settyinfo(&shttyinfo); + } + if (errflag) + kungetct = 0; +} + +static struct builtin bintab[] = { + BUILTIN("bindkey", 0, bin_bindkey, 0, -1, 0, "evaMldDANmrsLR", NULL), + BUILTIN("vared", 0, bin_vared, 1, 7, 0, NULL, NULL), + BUILTIN("zle", 0, bin_zle, 0, -1, 0, "lDANL", NULL), +}; + +/**/ +int +boot_zle(Module m) +{ + /* Set up editor entry points */ + trashzleptr = trashzle; + gotwordptr = gotword; + refreshptr = refresh; + spaceinlineptr = spaceinline; + zlereadptr = zleread; + + /* initialise the thingies */ + init_thingies(); + + /* miscellaneous initialisations */ + stackhist = stackcs = -1; + kungetbuf = (char *) zalloc(kungetsz = 32); + + /* initialise the keymap system */ + init_keymaps(); + + addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + return 0; +} + +#ifdef MODULE + +/**/ +int +cleanup_zle(Module m) +{ + int i; + + if(zleactive) { + zerrnam(m->nam, "can't unload the zle module while zle is active", + NULL, 0); + return 1; + } + + deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + cleanup_keymaps(); + deletehashtable(thingytab); + + zfree(vichgbuf, vichgbufsz); + zfree(kungetbuf, kungetsz); + free_isrch_spots(); + + zfree(cutbuf.buf, cutbuf.len); + for(i = KRINGCT; i--; ) + zfree(kring[i].buf, kring[i].len); + for(i = 35; i--; ) + zfree(vibuf[i].buf, vibuf[i].len); + + /* editor entry points */ + trashzleptr = noop_function; + gotwordptr = noop_function; + refreshptr = noop_function; + spaceinlineptr = noop_function_int; + zlereadptr = fallback_zleread; + + return 0; +} + +#endif /* MODULE */ |