diff options
Diffstat (limited to 'Src')
63 files changed, 3808 insertions, 1350 deletions
diff --git a/Src/Builtins/rlimits.c b/Src/Builtins/rlimits.c index 9da31831d..85ec1811c 100644 --- a/Src/Builtins/rlimits.c +++ b/Src/Builtins/rlimits.c @@ -518,7 +518,7 @@ do_limit(char *nam, int lim, rlim_t val, int hard, int soft, int set) } } else limits[lim].rlim_cur = val; - if (set && zsetlimit(lim, "limit")) + if (set && zsetlimit(lim, nam)) return 1; } } diff --git a/Src/Builtins/sched.c b/Src/Builtins/sched.c index c1cc98354..bcf7661f4 100644 --- a/Src/Builtins/sched.c +++ b/Src/Builtins/sched.c @@ -346,7 +346,7 @@ schedgetfn(UNUSED(Param pm)) for (i = 0, sch = schedcmds; sch; sch = sch->next, i++) ; - aptr = ret = zhalloc(sizeof(char **) * (i+1)); + aptr = ret = zhalloc(sizeof(char *) * (i+1)); for (sch = schedcmds; sch; sch = sch->next, aptr++) { char tbuf[40], *flagstr; time_t t; diff --git a/Src/Modules/attr.c b/Src/Modules/attr.c index 6e08b10c5..78c1104a9 100644 --- a/Src/Modules/attr.c +++ b/Src/Modules/attr.c @@ -98,36 +98,33 @@ static int bin_getattr(char *nam, char **argv, Options ops, UNUSED(int func)) { int ret = 0; - int list_len, val_len = 0, attr_len = 0, slen; + int val_len = 0, attr_len = 0, slen; char *value, *file = argv[0], *attr = argv[1], *param = argv[2]; int symlink = OPT_ISSET(ops, 'h'); unmetafy(file, &slen); unmetafy(attr, NULL); - list_len = xlistxattr(file, NULL, 0, symlink); - if (list_len > 0) { - val_len = xgetxattr(file, attr, NULL, 0, symlink); - if (val_len == 0) { + val_len = xgetxattr(file, attr, NULL, 0, symlink); + if (val_len == 0) { + if (param) + unsetparam(param); + return 0; + } + if (val_len > 0) { + value = (char *)zalloc(val_len+1); + attr_len = xgetxattr(file, attr, value, val_len, symlink); + if (attr_len > 0 && attr_len <= val_len) { + value[attr_len] = '\0'; if (param) - unsetparam(param); - return 0; - } - if (val_len > 0) { - value = (char *)zalloc(val_len+1); - attr_len = xgetxattr(file, attr, value, val_len, symlink); - if (attr_len > 0 && attr_len <= val_len) { - value[attr_len] = '\0'; - if (param) - setsparam(param, metafy(value, attr_len, META_DUP)); - else - printf("%s\n", value); - } - zfree(value, val_len+1); + setsparam(param, metafy(value, attr_len, META_DUP)); + else + printf("%s\n", value); } + zfree(value, val_len+1); } - if (list_len < 0 || val_len < 0 || attr_len < 0 || attr_len > val_len) { + if (val_len < 0 || attr_len < 0 || attr_len > val_len) { zwarnnam(nam, "%s: %e", metafy(file, slen, META_NOALLOC), errno); - ret = 1 + (attr_len > val_len || attr_len < 0); + ret = 1 + ((val_len > 0 && attr_len > val_len) || attr_len < 0); } return ret; } @@ -189,24 +186,20 @@ bin_listattr(char *nam, char **argv, Options ops, UNUSED(int func)) if (list_len > 0 && list_len <= val_len) { char *p = value; if (param) { - if (strlen(value) + 1 == list_len) - setsparam(param, metafy(value, list_len-1, META_DUP)); - else { - int arrlen = 0; - char **array = NULL, **arrptr = NULL; + int arrlen = 0; + char **array = NULL, **arrptr = NULL; - while (p < &value[list_len]) { - arrlen++; - p += strlen(p) + 1; - } - arrptr = array = (char **)zshcalloc((arrlen+1) * sizeof(char *)); - p = value; - while (p < &value[list_len]) { - *arrptr++ = metafy(p, -1, META_DUP); - p += strlen(p) + 1; - } - setaparam(param, array); + while (p < &value[list_len]) { + arrlen++; + p += strlen(p) + 1; + } + arrptr = array = (char **)zshcalloc((arrlen+1) * sizeof(char *)); + p = value; + while (p < &value[list_len]) { + *arrptr++ = metafy(p, -1, META_DUP); + p += strlen(p) + 1; } + setaparam(param, array); } else while (p < &value[list_len]) { printf("%s\n", p); p += strlen(p) + 1; diff --git a/Src/Modules/datetime.c b/Src/Modules/datetime.c index 00ebd2b49..63a04dc89 100644 --- a/Src/Modules/datetime.c +++ b/Src/Modules/datetime.c @@ -94,7 +94,7 @@ reverse_strftime(char *nam, char **argv, char *scalar, int quiet) } static int -bin_strftime(char *nam, char **argv, Options ops, UNUSED(int func)) +output_strftime(char *nam, char **argv, Options ops, UNUSED(int func)) { int bufsize, x; char *endptr = NULL, *scalar = NULL, *buffer; @@ -145,6 +145,25 @@ bin_strftime(char *nam, char **argv, Options ops, UNUSED(int func)) return 0; } +static int +bin_strftime(char *nam, char **argv, Options ops, int func) +{ + int result = 1; + char *tz = getsparam("TZ"); + + startparamscope(); + if (tz && *tz) { + Param pm = createparam("TZ", PM_LOCAL|PM_SCALAR|PM_EXPORTED); + if (pm) + pm->level = locallevel; /* because createparam() doesn't */ + setsparam("TZ", ztrdup(tz)); + } + result = output_strftime(nam, argv, ops, func); + endparamscope(); + + return result; +} + static zlong getcurrentsecs(UNUSED(Param pm)) { diff --git a/Src/Modules/db_gdbm.c b/Src/Modules/db_gdbm.c index 9a2a7a5b9..76d4751bf 100644 --- a/Src/Modules/db_gdbm.c +++ b/Src/Modules/db_gdbm.c @@ -39,16 +39,17 @@ #include <gdbm.h> -#if 0 /* what is this for? */ static char *backtype = "db/gdbm"; -#endif static const struct gsu_scalar gdbm_gsu = { gdbmgetfn, gdbmsetfn, gdbmunsetfn }; +/**/ +static const struct gsu_hash gdbm_hash_gsu = +{ hashgetfn, gdbmhashsetfn, gdbmhashunsetfn }; static struct builtin bintab[] = { - BUILTIN("ztie", 0, bin_ztie, 1, -1, 0, "d:f:", NULL), - BUILTIN("zuntie", 0, bin_zuntie, 1, -1, 0, NULL, NULL), + BUILTIN("ztie", 0, bin_ztie, 1, -1, 0, "d:f:r", NULL), + BUILTIN("zuntie", 0, bin_zuntie, 1, -1, 0, "u", NULL), }; /**/ @@ -57,36 +58,67 @@ bin_ztie(char *nam, char **args, Options ops, UNUSED(int func)) { char *resource_name, *pmname; GDBM_FILE dbf = NULL; + int read_write = GDBM_SYNC, pmflags = PM_REMOVABLE; Param tied_param; if(!OPT_ISSET(ops,'d')) { - zwarnnam(nam, "you must pass `-d db/gdbm' to ztie", NULL); + zwarnnam(nam, "you must pass `-d %s'", backtype); return 1; } if(!OPT_ISSET(ops,'f')) { - zwarnnam(nam, "you must pass `-f' with a filename to ztie", NULL); + zwarnnam(nam, "you must pass `-f' with a filename", NULL); return 1; } + if (OPT_ISSET(ops,'r')) { + read_write |= GDBM_READER; + pmflags |= PM_READONLY; + } else { + read_write |= GDBM_WRCREAT; + } /* Here should be a lookup of the backend type against * a registry. */ - - pmname = ztrdup(*args); + if (strcmp(OPT_ARG(ops, 'd'), backtype) != 0) { + zwarnnam(nam, "unsupported backend type `%s'", OPT_ARG(ops, 'd')); + return 1; + } resource_name = OPT_ARG(ops, 'f'); + pmname = *args; + + if ((tied_param = (Param)paramtab->getnode(paramtab, pmname)) && + !(tied_param->node.flags & PM_UNSET)) { + /* + * Unset any existing parameter. Note there's no implicit + * "local" here, but if the existing parameter is local + * that will be reflected in the new one. + * + * We need to do this before attempting to open the DB + * in case this variable is already tied to a DB. + * + * This can fail if the variable is readonly or restricted. + * We could call unsetparam() and check errflag instead + * of the return status. + */ + if (unsetparam_pm(tied_param, 0, 1)) + return 1; + } - if (!(tied_param = createspecialhash(pmname, &getgdbmnode, &scangdbmkeys, 0))) { - zwarnnam(nam, "cannot create the requested parameter name", NULL); + dbf = gdbm_open(resource_name, 0, read_write, 0666, 0); + if(!dbf) { + zwarnnam(nam, "error opening database file %s", resource_name); return 1; } - dbf = gdbm_open(resource_name, 0, GDBM_WRCREAT | GDBM_SYNC, 0666, 0); - if(!dbf) { - zwarnnam(nam, "error opening database file %s", resource_name); + if (!(tied_param = createspecialhash(pmname, &getgdbmnode, &scangdbmkeys, + pmflags))) { + zwarnnam(nam, "cannot create the requested parameter %s", pmname); + gdbm_close(dbf); return 1; } + tied_param->gsu.h = &gdbm_hash_gsu; tied_param->u.hash->tmpdata = (void *)dbf; return 0; @@ -97,20 +129,33 @@ static int bin_zuntie(char *nam, char **args, Options ops, UNUSED(int func)) { Param pm; - GDBM_FILE dbf; - - pm = (Param) paramtab->getnode(paramtab, args[0]); - if(!pm) { - zwarnnam(nam, "cannot untie %s", args[0]); - return 1; + char *pmname; + int ret = 0; + + for (pmname = *args; *args++; pmname = *args) { + pm = (Param) paramtab->getnode(paramtab, pmname); + if(!pm) { + zwarnnam(nam, "cannot untie %s", pmname); + ret = 1; + continue; + } + if (pm->gsu.h != &gdbm_hash_gsu) { + zwarnnam(nam, "not a tied gdbm hash: %s", pmname); + ret = 1; + continue; + } + + queue_signals(); + if (OPT_ISSET(ops,'u')) + gdbmuntie(pm); /* clear read-only-ness */ + if (unsetparam_pm(pm, 0, 1)) { + /* assume already reported */ + ret = 1; + } + unqueue_signals(); } - dbf = (GDBM_FILE)(pm->u.hash->tmpdata); - gdbm_close(dbf); -/* free(pm->u.hash->tmpdata); */ - paramtab->removenode(paramtab, pm->node.nam); - - return 0; + return ret; } /**/ @@ -153,7 +198,7 @@ gdbmsetfn(Param pm, char *val) /**/ static void -gdbmunsetfn(Param pm, int um) +gdbmunsetfn(Param pm, UNUSED(int um)) { datum key; GDBM_FILE dbf; @@ -191,9 +236,7 @@ scangdbmkeys(HashTable ht, ScanFunc func, int flags) { Param pm = NULL; datum key, content; - GDBM_FILE dbf; - - dbf = (GDBM_FILE)(ht->tmpdata); + GDBM_FILE dbf = (GDBM_FILE)(ht->tmpdata); pm = (Param) hcalloc(sizeof(struct param)); @@ -216,6 +259,89 @@ scangdbmkeys(HashTable ht, ScanFunc func, int flags) } +/**/ +static void +gdbmhashsetfn(Param pm, HashTable ht) +{ + int i; + HashNode hn; + GDBM_FILE dbf; + datum key, content; + + if (!pm->u.hash || pm->u.hash == ht) + return; + + if (!(dbf = (GDBM_FILE)(pm->u.hash->tmpdata))) + return; + + key = gdbm_firstkey(dbf); + while (key.dptr) { + queue_signals(); + (void)gdbm_delete(dbf, key); + free(key.dptr); + unqueue_signals(); + key = gdbm_firstkey(dbf); + } + + /* just deleted everything, clean up */ + (void)gdbm_reorganize(dbf); + + if (!ht) + return; + + for (i = 0; i < ht->hsize; i++) + for (hn = ht->nodes[i]; hn; hn = hn->next) { + struct value v; + + v.isarr = v.flags = v.start = 0; + v.end = -1; + v.arr = NULL; + v.pm = (Param) hn; + + key.dptr = v.pm->node.nam; + key.dsize = strlen(key.dptr) + 1; + + queue_signals(); + + content.dptr = getstrvalue(&v); + content.dsize = strlen(content.dptr) + 1; + + (void)gdbm_store(dbf, key, content, GDBM_REPLACE); + + unqueue_signals(); + } +} + +/**/ +static void +gdbmuntie(Param pm) +{ + GDBM_FILE dbf = (GDBM_FILE)(pm->u.hash->tmpdata); + HashTable ht = pm->u.hash; + + if (dbf) /* paranoia */ + gdbm_close(dbf); + + ht->tmpdata = NULL; + + /* for completeness ... createspecialhash() should have an inverse */ + ht->getnode = ht->getnode2 = gethashnode2; + ht->scantab = NULL; + + pm->node.flags &= ~(PM_SPECIAL|PM_READONLY); + pm->gsu.h = &stdhash_gsu; +} + +/**/ +static void +gdbmhashunsetfn(Param pm, UNUSED(int exp)) +{ + gdbmuntie(pm); + /* hash table is now normal, so proceed normally... */ + pm->gsu.h->setfn(pm, NULL); + pm->node.flags |= PM_UNSET; +} + #else # error no gdbm #endif /* have gdbm */ diff --git a/Src/Modules/langinfo.c b/Src/Modules/langinfo.c index f10fdfef5..eb45d161b 100644 --- a/Src/Modules/langinfo.c +++ b/Src/Modules/langinfo.c @@ -395,7 +395,8 @@ liitem(const char *name) static HashNode getlanginfo(UNUSED(HashTable ht), const char *name) { - int len, *elem; + int len; + nl_item *elem; char *listr, *nameu; Param pm = NULL; diff --git a/Src/Modules/tcp.c b/Src/Modules/tcp.c index 3f92050ae..0d9522047 100644 --- a/Src/Modules/tcp.c +++ b/Src/Modules/tcp.c @@ -623,6 +623,7 @@ bin_ztcp(char *nam, char **args, Options ops, UNUSED(int func)) zthost = zsh_getipnodebyname(desthost, AF_INET, 0, &herrno); if (!zthost || errflag) { zwarnnam(nam, "host resolution failure: %s", desthost); + zsfree(desthost); return 1; } @@ -630,6 +631,7 @@ bin_ztcp(char *nam, char **args, Options ops, UNUSED(int func)) if (!sess) { zwarnnam(nam, "unable to allocate a TCP session slot"); + zsfree(desthost); return 1; } @@ -665,6 +667,8 @@ bin_ztcp(char *nam, char **args, Options ops, UNUSED(int func)) sess->fd = redup(sess->fd, targetfd); if (sess->fd < 0) { zerrnam(nam, "could not duplicate socket fd to %d: %e", targetfd, errno); + zsfree(desthost); + tcp_close(sess); return 1; } } diff --git a/Src/Modules/zftp.c b/Src/Modules/zftp.c index d16e2f618..09d4bd703 100644 --- a/Src/Modules/zftp.c +++ b/Src/Modules/zftp.c @@ -50,7 +50,6 @@ struct zftp_session; typedef struct zftp_session *Zftp_session; #include "tcp.h" -#include "tcp.mdh" #include "zftp.mdh" #include "zftp.pro" diff --git a/Src/Modules/zpty.c b/Src/Modules/zpty.c index d119658c3..7b6130c6f 100644 --- a/Src/Modules/zpty.c +++ b/Src/Modules/zpty.c @@ -189,7 +189,11 @@ get_pty(int master, int *retfd) #endif if (master) { +#ifdef HAVE_POSIX_OPENPT + if ((mfd = posix_openpt(O_RDWR|O_NOCTTY)) < 0) +#else if ((mfd = open("/dev/ptmx", O_RDWR|O_NOCTTY)) < 0) +#endif return 1; if (grantpt(mfd) || unlockpt(mfd) || !(name = ptsname(mfd))) { @@ -304,7 +308,7 @@ newptycmd(char *nam, char *pname, char **args, int echo, int nblock) prog = parse_string(zjoin(args, ' ', 1), 0); if (!prog) { - errflag = 0; + errflag &= ~ERRFLAG_ERROR; scriptname = oscriptname; ineval = oineval; return 1; diff --git a/Src/Modules/zselect.c b/Src/Modules/zselect.c index c02074646..30a3f51a5 100644 --- a/Src/Modules/zselect.c +++ b/Src/Modules/zselect.c @@ -251,7 +251,7 @@ bin_zselect(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) outptr = outdata = (char **)zalloc((fdcount+1)*sizeof(char *)); while (nonempty(fdlist)) *outptr++ = getlinknode(fdlist); - *outptr = '\0'; + *outptr = NULL; /* and store in array parameter */ if (outhash) sethparam(outhash, outdata); diff --git a/Src/Modules/zutil.c b/Src/Modules/zutil.c index 1cca0c4b8..c89495070 100644 --- a/Src/Modules/zutil.c +++ b/Src/Modules/zutil.c @@ -301,7 +301,8 @@ setstypat(Style s, char *pat, Patprog prog, char **vals, int eval) int ef = errflag; eprog = parse_string(zjoin(vals, ' ', 1), 0); - errflag = ef; + /* Keep any user interrupt error status */ + errflag = ef | (errflag & ERRFLAG_INT); if (!eprog) { @@ -394,10 +395,11 @@ evalstyle(Stypat p) unsetparam("reply"); execode(p->eval, 1, 0, "style"); if (errflag) { - errflag = ef; + /* Keep any user interrupt error status */ + errflag = ef | (errflag & ERRFLAG_INT); return NULL; } - errflag = ef; + errflag = ef | (errflag & ERRFLAG_INT); queue_signals(); if ((ret = getaparam("reply"))) diff --git a/Src/Zle/compcore.c b/Src/Zle/compcore.c index 35d410cc6..000f9da2a 100644 --- a/Src/Zle/compcore.c +++ b/Src/Zle/compcore.c @@ -1524,7 +1524,7 @@ set_comp_sep(void) ol = zlemetaline; addedx = 1; noerrs = 1; - lexsave(); + zcontext_save(); lexflags = LEXFLAGS_ZLE; /* * tl is the length of the temporary string including @@ -1671,9 +1671,9 @@ set_comp_sep(void) noaliases = ona; strinend(); inpop(); - errflag = 0; + errflag &= ~ERRFLAG_ERROR; noerrs = ne; - lexrestore(); + zcontext_restore(); wb = owb; we = owe; zlemetaline = ol; @@ -3492,7 +3492,7 @@ freematch(Cmatch m, int nbeg, int nend) if (m->brsl) zfree(m->brsl, nend * sizeof(int)); - zfree(m, sizeof(m)); + zfree(m, sizeof(*m)); } /* This frees the groups of matches. */ diff --git a/Src/Zle/compctl.c b/Src/Zle/compctl.c index 0b7a32445..189582d22 100644 --- a/Src/Zle/compctl.c +++ b/Src/Zle/compctl.c @@ -1515,7 +1515,7 @@ printcompctl(char *s, Compctl cc, int printflags, int ispat) if (cclist & COMP_LIST) printf(" --"); } - if (cc && cc->xor) { + if (cc->xor) { /* print xor'd (+) completions */ printf(" +"); if (cc->xor != &cc_default) @@ -1879,7 +1879,7 @@ ccmakehookfn(UNUSED(Hookdef dummy), struct ccmakedat *dat) if (!m || !(m = m->next)) break; - errflag = 0; + errflag &= ~ERRFLAG_ERROR; } redup(osi, 0); dat->lst = 1; @@ -2121,7 +2121,7 @@ getreal(char *str) if (!errflag && nonempty(l) && ((char *) peekfirst(l)) && ((char *) peekfirst(l))[0]) return dupstring(peekfirst(l)); - errflag = 0; + errflag &= ~ERRFLAG_ERROR; return dupstring(str); } @@ -2599,7 +2599,7 @@ makecomplistlist(Compctl cc, char *s, int incmd, int compadd) makecomplistflags(cc, s, incmd, compadd); /* Reset some information variables for the next try. */ - errflag = 0; + errflag &= ~ERRFLAG_ERROR; offs = oloffs; wb = owb; we = owe; @@ -2795,7 +2795,7 @@ sep_comp_string(char *ss, char *s, int noffs) * get the words we have to expand. */ addedx = 1; noerrs = 1; - lexsave(); + zcontext_save(); lexflags = LEXFLAGS_ZLE; tmp = (char *) zhalloc(tl = sl + 3 + strlen(s)); strcpy(tmp, ss); @@ -2847,9 +2847,9 @@ sep_comp_string(char *ss, char *s, int noffs) noaliases = ona; strinend(); inpop(); - errflag = 0; + errflag &= ~ERRFLAG_ERROR; noerrs = ne; - lexrestore(); + zcontext_restore(); wb = owb; we = owe; zlemetacs = ocs; @@ -3685,7 +3685,7 @@ makecomplistflags(Compctl cc, char *s, int incmd, int compadd) for (i = 0; i <= maxjob; i++) if ((jobtab[i].stat & STAT_INUSE) && - jobtab[i].procs && jobtab[i].procs->text) { + jobtab[i].procs && jobtab[i].procs->text[0]) { int stopped = jobtab[i].stat & STAT_STOPPED; j = dupstring(jobtab[i].procs->text); @@ -3707,7 +3707,7 @@ makecomplistflags(Compctl cc, char *s, int incmd, int compadd) /* Put the string in the lexer buffer and call the lexer to * * get the words we have to expand. */ - lexsave(); + zcontext_save(); lexflags = LEXFLAGS_ZLE; tmpbuf = (char *)zhalloc(strlen(cc->str) + 5); sprintf(tmpbuf, "foo %s", cc->str); /* KLUDGE! */ @@ -3725,8 +3725,8 @@ makecomplistflags(Compctl cc, char *s, int incmd, int compadd) noaliases = ona; strinend(); inpop(); - errflag = 0; - lexrestore(); + errflag &= ~ERRFLAG_ERROR; + zcontext_restore(); /* Fine, now do full expansion. */ prefork(foo, 0); if (!errflag) { @@ -3853,7 +3853,7 @@ makecomplistflags(Compctl cc, char *s, int incmd, int compadd) yaptr = get_user_var(uv); if ((tt = cc->explain)) { tt = dupstring(tt); - if ((cc->mask & CC_EXPANDEXPL) && !parsestr(tt)) { + if ((cc->mask & CC_EXPANDEXPL) && !parsestr(&tt)) { singsub(&tt); untokenize(tt); } @@ -3873,7 +3873,7 @@ makecomplistflags(Compctl cc, char *s, int incmd, int compadd) } } else if ((tt = cc->explain)) { tt = dupstring(tt); - if ((cc->mask & CC_EXPANDEXPL) && !parsestr(tt)) { + if ((cc->mask & CC_EXPANDEXPL) && !parsestr(&tt)) { singsub(&tt); untokenize(tt); } diff --git a/Src/Zle/complist.c b/Src/Zle/complist.c index 5e5ba9f20..f54206619 100644 --- a/Src/Zle/complist.c +++ b/Src/Zle/complist.c @@ -780,6 +780,7 @@ clnicezputs(int do_colors, char *s, int ml) /* Is the screen full? */ if (ml == mlend - 1 && col == zterm_columns - 1) { mlprinted = ml - oml; + free(ums); return 0; } if (t < wptr) { @@ -804,6 +805,7 @@ clnicezputs(int do_colors, char *s, int ml) ml++; if (mscroll && !--mrestlines && (ask = asklistscroll(ml))) { mlprinted = ml - oml; + free(ums); return ask; } col -= zterm_columns; @@ -1375,7 +1377,7 @@ compprintlist(int showall) tcout(TCCLEAREOD); g = ((lasttype && lastg) ? lastg : amatches); - while (g) { + while (g && !errflag) { char **pp = g->ylist; #ifdef ZSH_HEAP_DEBUG @@ -1389,7 +1391,7 @@ compprintlist(int showall) ml = lastml; lastused = 1; } - while (*e) { + while (*e && !errflag) { if (((*e)->count || (*e)->always) && (!listdat.onlyexpl || (listdat.onlyexpl & ((*e)->always > 0 ? 2 : 1)))) { @@ -1469,11 +1471,11 @@ compprintlist(int showall) nl = nc = g->lins; - while (n && nl--) { + while (n && nl-- && !errflag) { i = g->cols; mc = 0; pq = pp; - while (n && i--) { + while (n && i-- && !errflag) { if (pq - g->ylist >= g->lcount) break; if (compzputs(*pq, mscroll)) @@ -1582,7 +1584,7 @@ compprintlist(int showall) } else p = skipnolist(g->matches, showall); - while (n && nl--) { + while (n && nl-- && !errflag) { if (!lasttype && ml >= mlbeg) { lasttype = 3; lastg = g; @@ -1596,7 +1598,7 @@ compprintlist(int showall) i = g->cols; mc = 0; q = p; - while (n && i--) { + while (n && i-- && !errflag) { wid = (g->widths ? g->widths[mc] : g->width); if (!(m = *q)) { if (clprintm(g, NULL, mc, ml, (!i), wid)) @@ -2059,8 +2061,8 @@ complistmatches(UNUSED(Hookdef dummy), Chdata dat) i = zterm_columns * listdat.nlines; free(mtab); - mtab = (Cmatch **) zalloc(i * sizeof(Cmatch **)); - memset(mtab, 0, i * sizeof(Cmatch **)); + mtab = (Cmatch **) zalloc(i * sizeof(Cmatch *)); + memset(mtab, 0, i * sizeof(Cmatch *)); free(mgtab); mgtab = (Cmgroup *) zalloc(i * sizeof(Cmgroup)); #ifdef DEBUG @@ -2790,7 +2792,9 @@ domenuselect(Hookdef dummy, Chdata dat) Menustack s = (Menustack) zhalloc(sizeof(*s)); int ol; - mode = 0; + if (mode == MM_INTER) + do_single(*minfo.cur); + mode = 0; s->prev = u; u = s; s->line = dupstring(zlemetaline); @@ -2880,7 +2884,8 @@ domenuselect(Hookdef dummy, Chdata dat) brend = dupbrinfo(u->brend, &lastbrend, 0); nbrbeg = u->nbrbeg; nbrend = u->nbrend; - origline = u->origline; + zsfree(origline); + origline = ztrdup(u->origline); origcs = u->origcs; origll = u->origll; strcpy(status, u->status); @@ -3234,7 +3239,8 @@ domenuselect(Hookdef dummy, Chdata dat) * don't want that, just what the user typed, * so restore the information. */ - origline = modeline; + zsfree(origline); + origline = ztrdup(modeline); origcs = modecs; origll = modell; zlemetacs = 0; diff --git a/Src/Zle/compmatch.c b/Src/Zle/compmatch.c index 4cd3b9ffe..05ae43ae6 100644 --- a/Src/Zle/compmatch.c +++ b/Src/Zle/compmatch.c @@ -1014,6 +1014,7 @@ comp_match(char *pfx, char *sfx, char *w, Patprog cp, Cline *clp, int qu, Brinfo *bpl, int bcp, Brinfo *bsl, int bcs, int *exact) { char *r = NULL; + int onoerrs = noerrs; if (cp) { /* We have a globcomplete-like pattern, just use that. */ @@ -1033,12 +1034,14 @@ comp_match(char *pfx, char *sfx, char *w, Patprog cp, Cline *clp, int qu, */ teststr = dupstring(r); tokenize(teststr); + noerrs = 1; if (parse_subst_string(teststr)) teststr = r; else { remnulargs(teststr); untokenize(teststr); } + noerrs = onoerrs; } else teststr = r; if (!pattry(cp, teststr)) diff --git a/Src/Zle/compresult.c b/Src/Zle/compresult.c index fcceb670c..9f383f4b8 100644 --- a/Src/Zle/compresult.c +++ b/Src/Zle/compresult.c @@ -1090,15 +1090,16 @@ do_single(Cmatch m) } if (tryit) { noerrs = 1; - parsestr(p); + p = dupstring(p); + parsestr(&p); singsub(&p); - errflag = 0; + errflag &= ~ERRFLAG_ERROR; noerrs = ne; } } else { p = (char *) zhalloc(strlen(prpre) + strlen(str) + strlen(psuf) + 3); - sprintf(p, "%s%s%s", ((prpre && *prpre) ? + sprintf(p, "%s%s%s", (*prpre ? prpre : "./"), str, psuf); } /* And do the stat. */ diff --git a/Src/Zle/computil.c b/Src/Zle/computil.c index b11c39f25..a81d1ddad 100644 --- a/Src/Zle/computil.c +++ b/Src/Zle/computil.c @@ -4060,7 +4060,8 @@ cfp_test_exact(LinkList names, char **accept, char *skipped) if (sl > PATH_MAX2) return NULL; - suf = dyncat(skipped, rembslash(dyncat(compprefix, compsuffix))); + suf = dyncat(skipped, rembslash(dyncat(compprefix ? compprefix : "", + compsuffix ? compsuffix : ""))); for (node = firstnode(names); node; incnode(node)) { l = strlen(p = (char *) getdata(node)); diff --git a/Src/Zle/iwidgets.list b/Src/Zle/iwidgets.list index e3ffe3e8c..b41661a7d 100644 --- a/Src/Zle/iwidgets.list +++ b/Src/Zle/iwidgets.list @@ -24,7 +24,7 @@ "backward-kill-word", backwardkillword, ZLE_KILL | ZLE_KEEPSUFFIX "backward-word", backwardword, 0 "beep", handlefeep, 0 -"beginning-of-buffer-or-history", beginningofbufferorhistory, 0 +"beginning-of-buffer-or-history", beginningofbufferorhistory, ZLE_LINEMOVE "beginning-of-history", beginningofhistory, 0 "beginning-of-line", beginningofline, 0 "beginning-of-line-hist", beginningoflinehist, 0 @@ -41,11 +41,12 @@ "digit-argument", digitargument, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL | ZLE_NOTCOMMAND "down-case-word", downcaseword, 0 "down-history", downhistory, 0 +"down-line", downline, ZLE_LINEMOVE | ZLE_LASTCOL "down-line-or-history", downlineorhistory, ZLE_LINEMOVE | ZLE_LASTCOL "down-line-or-search", downlineorsearch, ZLE_LINEMOVE | ZLE_LASTCOL "emacs-backward-word", emacsbackwardword, 0 "emacs-forward-word", emacsforwardword, 0 -"end-of-buffer-or-history", endofbufferorhistory, 0 +"end-of-buffer-or-history", endofbufferorhistory, ZLE_LINEMOVE "end-of-history", endofhistory, 0 "end-of-line", endofline, 0 "end-of-line-hist", endoflinehist, 0 @@ -88,6 +89,7 @@ "push-input", pushinput, 0 "push-line", pushline, 0 "push-line-or-edit", pushlineoredit, 0 +"put-replace-selection", putreplaceselection, ZLE_KEEPSUFFIX "quoted-insert", quotedinsert, ZLE_MENUCMP | ZLE_KEEPSUFFIX "quote-line", quoteline, 0 "quote-region", quoteregion, 0 @@ -98,13 +100,19 @@ "reset-prompt", resetprompt, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL "reverse-menu-complete", reversemenucomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_ISCOMP "run-help", processcmd, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"select-a-word", selectword, ZLE_KEEPSUFFIX +"select-in-word", selectword, ZLE_KEEPSUFFIX +"select-a-blank-word", selectword, ZLE_KEEPSUFFIX +"select-in-blank-word", selectword, ZLE_KEEPSUFFIX +"select-a-shell-word", selectargument, ZLE_KEEPSUFFIX +"select-in-shell-word", selectargument, ZLE_KEEPSUFFIX "self-insert", selfinsert, ZLE_MENUCMP | ZLE_KEEPSUFFIX "self-insert-unmeta", selfinsertunmeta, ZLE_MENUCMP | ZLE_KEEPSUFFIX "send-break", sendbreak, 0 "set-mark-command", setmarkcommand, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL -"split-undo", splitundo, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_NOTCOMMAND +"split-undo", splitundo, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL | ZLE_NOTCOMMAND "spell-word", spellword, 0 -"set-local-history", setlocalhistory, 0 +"set-local-history", setlocalhistory, ZLE_LASTCOL "transpose-chars", transposechars, 0 "transpose-words", transposewords, 0 "undefined-key", undefinedkey, 0 @@ -112,6 +120,7 @@ "universal-argument", universalargument, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL | ZLE_NOTCOMMAND "up-case-word", upcaseword, 0 "up-history", uphistory, 0 +"up-line", upline, ZLE_LINEMOVE | ZLE_LASTCOL "up-line-or-history", uplineorhistory, ZLE_LINEMOVE | ZLE_LASTCOL "up-line-or-search", uplineorsearch, ZLE_LINEMOVE | ZLE_LASTCOL "vi-add-eol", viaddeol, 0 @@ -119,20 +128,22 @@ "vi-backward-blank-word", vibackwardblankword, 0 "vi-backward-char", vibackwardchar, 0 "vi-backward-delete-char", vibackwarddeletechar, ZLE_KEEPSUFFIX -"vi-backward-kill-word", vibackwardkillword, ZLE_KILL | ZLE_KEEPSUFFIX +"vi-backward-kill-word", vibackwardkillword, ZLE_KEEPSUFFIX "vi-backward-word", vibackwardword, 0 +"vi-backward-word-end", vibackwardwordend, 0 +"vi-backward-blank-word-end", vibackwardblankwordend, 0 "vi-beginning-of-line", vibeginningofline, 0 -"vi-caps-lock-panic", vicapslockpanic, 0 -"vi-change", vichange, 0 +"vi-caps-lock-panic", vicapslockpanic, ZLE_LASTCOL +"vi-change", vichange, ZLE_LASTCOL | ZLE_VIOPER "vi-change-eol", vichangeeol, 0 "vi-change-whole-line", vichangewholeline, 0 "vi-cmd-mode", vicmdmode, 0 -"vi-delete", videlete, ZLE_KILL | ZLE_KEEPSUFFIX +"vi-delete", videlete, ZLE_KEEPSUFFIX | ZLE_LASTCOL | ZLE_VIOPER "vi-delete-char", videletechar, ZLE_KEEPSUFFIX "vi-digit-or-beginning-of-line", vidigitorbeginningofline, 0 "vi-down-line-or-history", vidownlineorhistory, ZLE_LINEMOVE "vi-end-of-line", viendofline, ZLE_LASTCOL -"vi-fetch-history", vifetchhistory, 0 +"vi-fetch-history", vifetchhistory, ZLE_LINEMOVE "vi-find-next-char", vifindnextchar, 0 "vi-find-next-char-skip", vifindnextcharskip, 0 "vi-find-prev-char", vifindprevchar, 0 @@ -145,22 +156,22 @@ "vi-forward-word-end", viforwardwordend, 0 "vi-goto-column", vigotocolumn, 0 "vi-goto-mark", vigotomark, 0 -"vi-goto-mark-line", vigotomarkline, 0 +"vi-goto-mark-line", vigotomarkline, ZLE_LINEMOVE "vi-history-search-backward", vihistorysearchbackward, 0 "vi-history-search-forward", vihistorysearchforward, 0 -"vi-indent", viindent, 0 +"vi-indent", viindent, ZLE_LASTCOL | ZLE_VIOPER "vi-insert", viinsert, 0 "vi-insert-bol", viinsertbol, 0 "vi-join", vijoin, 0 -"vi-kill-eol", vikilleol, ZLE_KILL | ZLE_KEEPSUFFIX -"vi-kill-line", vikillline, ZLE_KILL | ZLE_KEEPSUFFIX +"vi-kill-eol", vikilleol, ZLE_KEEPSUFFIX +"vi-kill-line", vikillline, ZLE_KEEPSUFFIX "vi-match-bracket", vimatchbracket, 0 "vi-open-line-above", viopenlineabove, 0 "vi-open-line-below", viopenlinebelow, 0 -"vi-oper-swap-case", vioperswapcase, 0 +"vi-oper-swap-case", vioperswapcase, ZLE_LASTCOL | ZLE_VIOPER "vi-pound-insert", vipoundinsert, 0 -"vi-put-after", viputafter, ZLE_YANK | ZLE_KEEPSUFFIX -"vi-put-before", viputbefore, ZLE_YANK | ZLE_KEEPSUFFIX +"vi-put-after", viputafter, ZLE_YANKAFTER | ZLE_KEEPSUFFIX +"vi-put-before", viputbefore, ZLE_YANKBEFORE | ZLE_KEEPSUFFIX "vi-quoted-insert", viquotedinsert, ZLE_MENUCMP | ZLE_KEEPSUFFIX "vi-repeat-change", virepeatchange, 0 "vi-repeat-find", virepeatfind, 0 @@ -172,15 +183,17 @@ "vi-set-buffer", visetbuffer, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL "vi-set-mark", visetmark, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL "vi-substitute", visubstitute, 0 -"vi-swap-case", viswapcase, 0 +"vi-swap-case", viswapcase, ZLE_LASTCOL "vi-undo-change", viundochange, ZLE_KEEPSUFFIX -"vi-unindent", viunindent, 0 +"vi-unindent", viunindent, ZLE_LASTCOL | ZLE_VIOPER "vi-up-line-or-history", viuplineorhistory, ZLE_LINEMOVE -"vi-yank", viyank, 0 +"vi-yank", viyank, ZLE_LASTCOL | ZLE_VIOPER "vi-yank-eol", viyankeol, 0 "vi-yank-whole-line", viyankwholeline, 0 +"visual-line-mode", visuallinemode, ZLE_MENUCMP | ZLE_LASTCOL +"visual-mode", visualmode, ZLE_MENUCMP | ZLE_LASTCOL "what-cursor-position", whatcursorposition, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL "where-is", whereis, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL "which-command", processcmd, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL -"yank", yank, ZLE_YANK | ZLE_KEEPSUFFIX -"yank-pop", yankpop, ZLE_YANK | ZLE_KEEPSUFFIX +"yank", yank, ZLE_YANKBEFORE | ZLE_KEEPSUFFIX +"yank-pop", yankpop, ZLE_KEEPSUFFIX | ZLE_NOTCOMMAND diff --git a/Src/Zle/textobjects.c b/Src/Zle/textobjects.c new file mode 100644 index 000000000..9b3277a97 --- /dev/null +++ b/Src/Zle/textobjects.c @@ -0,0 +1,322 @@ +/* + * textobjects.c - ZLE module implementing Vim style text objects + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 2014 Oliver Kiddle + * 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 Oliver Kiddle 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 Oliver Kiddle and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Oliver Kiddle 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 Oliver Kiddle and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "zle.mdh" +#include "textobjects.pro" + +/* class of character: 0 is whitespace, 1 is word character, 2 is other */ +static int +wordclass(ZLE_CHAR_T x) +{ + return (ZC_iblank(x) ? 0 : ((ZC_ialnum(x) || (ZWC('_') == x)) ? 1 : 2)); +} + +static int +blankwordclass(ZLE_CHAR_T x) +{ + return (ZC_iblank(x) ? 0 : 1); +} + +/**/ +int +selectword(UNUSED(char **args)) +{ + int n = zmult; + int all = (bindk == t_selectaword || bindk == t_selectablankword); + int (*viclass)(ZLE_CHAR_T) = (bindk == t_selectaword || + bindk == t_selectinword) ? wordclass : blankwordclass; + int sclass = viclass(zleline[zlecs]); + int doblanks = all && sclass; + + if (!invicmdmode()) { + region_active = 1; + mark = zlecs; + } + if (!region_active || zlecs == mark) { + /* search back to first character of same class as the start position + * also stop at the beginning of the line */ + mark = zlecs; + while (mark) { + int pos = mark; + DECPOS(pos); + if (zleline[pos] == ZWC('\n') || viclass(zleline[pos]) != sclass) + break; + mark = pos; + } + /* similarly scan forward over characters of the same class */ + while (zlecs < zlell) { + INCCS(); + int pos = zlecs; + /* single newlines within blanks are included */ + if (all && !sclass && pos < zlell && zleline[pos] == ZWC('\n')) + INCPOS(pos); + + if (zleline[pos] == ZWC('\n') || viclass(zleline[pos]) != sclass) + break; + } + + if (all) { + int nclass = viclass(zleline[zlecs]); + /* if either start or new position is blank advance over + * a new block of characters of a common type */ + if (!nclass || !sclass) { + while (zlecs < zlell) { + INCCS(); + if (zleline[zlecs] == ZWC('\n') || + viclass(zleline[zlecs]) != nclass) + break; + } + if (n < 2) + doblanks = 0; + } + } + } else { + /* For visual mode, advance one char so repeated + * invocations select subsequent words */ + if (zlecs > mark) { + if (zlecs < zlell) + INCCS(); + } else if (zlecs) + DECCS(); + if (zlecs < mark) { + /* visual mode with the cursor before the mark: move cursor back */ + while (n-- > 0) { + int pos = zlecs; + /* first over blanks */ + if (all && (!viclass(zleline[pos]) || + zleline[pos] == ZWC('\n'))) { + all = 0; + while (pos) { + DECPOS(pos); + if (zleline[pos] == ZWC('\n')) + break; + zlecs = pos; + if (viclass(zleline[pos])) + break; + } + } else if (zlecs && zleline[zlecs] == ZWC('\n')) { + /* for in widgets pass over one newline */ + DECPOS(pos); + if (zleline[pos] != ZWC('\n')) + zlecs = pos; + } + pos = zlecs; + sclass = viclass(zleline[zlecs]); + /* now retreat over non-blanks */ + while (zleline[pos] != ZWC('\n') && + viclass(zleline[pos]) == sclass) { + zlecs = pos; + if (!pos) { + zlecs = 0; + break; + } + DECPOS(pos); + } + /* blanks again but only if there were none first time */ + if (all && zlecs) { + pos = zlecs; + DECPOS(pos); + if (!viclass(zleline[pos])) { + while (pos) { + DECPOS(pos); + if (zleline[pos] == ZWC('\n') || + viclass(zleline[pos])) + break; + zlecs = pos; + } + } + } + } + return 0; + } + n++; + doblanks = 0; + } + region_active = !!region_active; /* force to character wise */ + + /* for each digit argument, advance over further block of one class */ + while (--n > 0) { + if (zlecs < zlell && zleline[zlecs] == ZWC('\n')) + INCCS(); + sclass = viclass(zleline[zlecs]); + while (zlecs < zlell) { + INCCS(); + if (zleline[zlecs] == ZWC('\n') || + viclass(zleline[zlecs]) != sclass) + break; + } + /* for 'a' widgets, advance extra block if either consists of blanks */ + if (all) { + if (zlecs < zlell && zleline[zlecs] == ZWC('\n')) + INCCS(); + if (!sclass || !viclass(zleline[zlecs]) ) { + sclass = viclass(zleline[zlecs]); + if (n == 1 && !sclass) + doblanks = 0; + while (zlecs < zlell) { + INCCS(); + if (zleline[zlecs] == ZWC('\n') || + viclass(zleline[zlecs]) != sclass) + break; + } + } + } + } + + /* if we didn't remove blanks at either end we remove some at the start */ + if (doblanks) { + int pos = mark; + while (pos) { + DECPOS(pos); + /* don't remove blanks at the start of the line, i.e indentation */ + if (zleline[pos] == ZWC('\n')) + break; + if (!ZC_iblank(zleline[pos])) { + INCPOS(pos); + mark = pos; + break; + } + } + } + /* Adjustment: vi operators don't include the cursor position, in insert + * or emacs mode the region also doesn't but for vi visual mode it is + * included. */ + if (zlecs && zlecs > mark && !virangeflag) + DECCS(); + + return 0; +} + +/**/ +int +selectargument(UNUSED(char **args)) +{ + int ne = noerrs, ocs = zlemetacs; + int owb = wb, owe= we, oadx = addedx, ona = noaliases; + char *p; + int ll, cs; + char *linein; + int wend = 0, wcur = 0; + int n = zmult; + int *wstarts; + int tmpsz; + + if (n < 1 || 2*n > zlell + 1) + return 1; + + /* if used from emacs mode enable the region */ + if (!invicmdmode()) { + region_active = 1; + mark = zlecs; + } + + wstarts = (int *) zhalloc(n * sizeof(int)); + memset(wstarts, 0, n * sizeof(int)); + + addedx = 0; + noerrs = 1; + zcontext_save(); + lexflags = LEXFLAGS_ACTIVE; + linein = zlegetline(&ll, &cs); + zlemetall = ll; + zlemetacs = cs; + + if (!isfirstln && chline) { + p = (char *) zhalloc(hptr - chline + zlemetall + 2); + memcpy(p, chline, hptr - chline); + memcpy(p + (hptr - chline), linein, ll); + p[(hptr - chline) + ll] = '\0'; + inpush(p, 0, NULL); + zlemetacs += hptr - chline; + } else { + p = (char *) zhalloc(ll + 1); + memcpy(p, linein, ll); + p[ll] = '\0'; + inpush(p, 0, NULL); + } + if (zlemetacs) + zlemetacs--; + strinbeg(0); + noaliases = 1; + do { + wstarts[wcur++] = wend; + wcur %= n; + ctxtlex(); + if (tok == ENDINPUT || tok == LEXERR) + break; + wend = zlemetall - inbufct; + } while (tok != ENDINPUT && tok != LEXERR && wend <= zlemetacs); + noaliases = ona; + strinend(); + inpop(); + errflag &= ~ERRFLAG_ERROR; + noerrs = ne; + zcontext_restore(); + zlemetacs = ocs; + wb = owb; + we = owe; + addedx = oadx; + + /* convert offsets for mark and zlecs back to ZLE internal format */ + linein[wend] = '\0'; /* a bit of a hack to get two offsets */ + free(stringaszleline(linein, wstarts[wcur], &zlecs, &tmpsz, &mark)); + free(linein); + + if (bindk == t_selectinshellword) { + ZLE_CHAR_T *match = ZWS("`\'\""); + ZLE_CHAR_T *lmatch = ZWS("\'({"), *rmatch = ZWS("\')}"); + ZLE_CHAR_T *ematch = match, *found; + int start, end = zlecs; + /* for 'in' widget, don't include initial blanks ... */ + while (mark < zlecs && ZC_iblank(zleline[mark])) + INCPOS(mark); + /* ... or a matching pair of quotes */ + start = mark; + if (zleline[start] == ZWC('$')) { + match = lmatch; + ematch = rmatch; + INCPOS(start); + } + found = ZS_strchr(match, zleline[start]); + if (found) { + DECPOS(end); + if (zleline[end] == ematch[found-match]) { + zlecs = end; + INCPOS(start); + mark = start; + } + } + } + + /* Adjustment: vi operators don't include the cursor position */ + if (!virangeflag) + DECCS(); + + return 0; +} diff --git a/Src/Zle/zle.h b/Src/Zle/zle.h index 870e2149d..3c652909e 100644 --- a/Src/Zle/zle.h +++ b/Src/Zle/zle.h @@ -203,13 +203,16 @@ struct widget { #define WIDGET_INT (1<<0) /* widget is internally implemented */ #define WIDGET_NCOMP (1<<1) /* new style completion widget */ #define ZLE_MENUCMP (1<<2) /* DON'T invalidate completion list */ -#define ZLE_YANK (1<<3) -#define ZLE_LINEMOVE (1<<4) /* command is a line-oriented movement */ -#define ZLE_LASTCOL (1<<5) /* command maintains lastcol correctly */ -#define ZLE_KILL (1<<6) -#define ZLE_KEEPSUFFIX (1<<7) /* DON'T remove added suffix */ -#define ZLE_NOTCOMMAND (1<<8) /* widget should not alter lastcmd */ -#define ZLE_ISCOMP (1<<9) /* usable for new style completion */ +#define ZLE_YANKAFTER (1<<3) +#define ZLE_YANKBEFORE (1<<4) +#define ZLE_YANK (ZLE_YANKAFTER | ZLE_YANKBEFORE) +#define ZLE_LINEMOVE (1<<5) /* command is a line-oriented movement */ +#define ZLE_VIOPER (1<<6) /* widget reads further keys so wait if prefix */ +#define ZLE_LASTCOL (1<<7) /* command maintains lastcol correctly */ +#define ZLE_KILL (1<<8) +#define ZLE_KEEPSUFFIX (1<<9) /* DON'T remove added suffix */ +#define ZLE_NOTCOMMAND (1<<10) /* widget should not alter lastcmd */ +#define ZLE_ISCOMP (1<<11) /* usable for new style completion */ /* thingies */ @@ -240,6 +243,9 @@ struct modifier { #define MOD_VIBUF (1<<2) /* a vi cut buffer has been selected */ #define MOD_VIAPP (1<<3) /* appending to the vi cut buffer */ #define MOD_NEG (1<<4) /* last command was negate argument */ +#define MOD_NULL (1<<5) /* throw away text for the vi cut buffer */ +#define MOD_CHAR (1<<6) /* force character-wise movement */ +#define MOD_LINE (1<<7) /* force line-wise movement */ /* current modifier status */ @@ -256,6 +262,7 @@ struct modifier { * of visible characters directly input by * the user. */ +#define CUT_YANK (1<<3) /* vi yank: use register 0 instead of 1-9 */ /* undo system */ diff --git a/Src/Zle/zle.mdd b/Src/Zle/zle.mdd index c6e4d11c2..dd69eff2c 100644 --- a/Src/Zle/zle.mdd +++ b/Src/Zle/zle.mdd @@ -7,7 +7,8 @@ autofeatures="b:bindkey b:vared b:zle" objects="zle_bindings.o zle_hist.o zle_keymap.o zle_main.o \ zle_misc.o zle_move.o zle_params.o zle_refresh.o \ -zle_thingy.o zle_tricky.o zle_utils.o zle_vi.o zle_word.o" +zle_thingy.o zle_tricky.o zle_utils.o zle_vi.o zle_word.o \ +textobjects.o" headers="zle.h zle_things.h" diff --git a/Src/Zle/zle_bindings.c b/Src/Zle/zle_bindings.c index 682691347..2ae8c8764 100644 --- a/Src/Zle/zle_bindings.c +++ b/Src/Zle/zle_bindings.c @@ -278,7 +278,7 @@ int viinsbind[32] = { /* ^U */ z_vikillline, /* ^V */ z_viquotedinsert, /* ^W */ z_vibackwardkillword, - /* ^X */ z_selfinsert, + /* ^X */ z_undefinedkey, /* ^Y */ z_selfinsert, /* ^Z */ z_selfinsert, /* ^[ */ z_vicmdmode, @@ -308,7 +308,7 @@ int vicmdbind[128] = { /* ^O */ z_undefinedkey, /* ^P */ z_uphistory, /* ^Q */ z_undefinedkey, - /* ^R */ z_redisplay, + /* ^R */ z_redo, /* ^S */ z_undefinedkey, /* ^T */ z_undefinedkey, /* ^U */ z_undefinedkey, @@ -376,7 +376,7 @@ int vicmdbind[128] = { /* S */ z_vichangewholeline, /* T */ z_vifindprevcharskip, /* U */ z_undefinedkey, - /* V */ z_undefinedkey, + /* V */ z_visuallinemode, /* W */ z_viforwardblankword, /* X */ z_vibackwarddeletechar, /* Y */ z_viyankwholeline, @@ -407,8 +407,8 @@ int vicmdbind[128] = { /* r */ z_vireplacechars, /* s */ z_visubstitute, /* t */ z_vifindnextcharskip, - /* u */ z_viundochange, - /* v */ z_undefinedkey, + /* u */ z_undo, + /* v */ z_visualmode, /* w */ z_viforwardword, /* x */ z_videletechar, /* y */ z_viyank, diff --git a/Src/Zle/zle_hist.c b/Src/Zle/zle_hist.c index 44b39d186..cc66f99ae 100644 --- a/Src/Zle/zle_hist.c +++ b/Src/Zle/zle_hist.c @@ -227,14 +227,14 @@ uphistory(UNUSED(char **args)) } /**/ -static int -upline(void) +int +upline(char **args) { int n = zmult; if (n < 0) { zmult = -zmult; - n = -downline(); + n = -downline(args); zmult = -zmult; return n; } @@ -270,7 +270,7 @@ int uplineorhistory(char **args) { int ocs = zlecs; - int n = upline(); + int n = upline(args); if (n) { int m = zmult, ret; @@ -300,7 +300,7 @@ int uplineorsearch(char **args) { int ocs = zlecs; - int n = upline(); + int n = upline(args); if (n) { int m = zmult, ret; @@ -316,14 +316,14 @@ uplineorsearch(char **args) } /**/ -static int -downline(void) +int +downline(char **args) { int n = zmult; if (n < 0) { zmult = -zmult; - n = -upline(); + n = -upline(args); zmult = -zmult; return n; } @@ -358,7 +358,7 @@ int downlineorhistory(char **args) { int ocs = zlecs; - int n = downline(); + int n = downline(args); if (n) { int m = zmult, ret; @@ -388,7 +388,7 @@ int downlineorsearch(char **args) { int ocs = zlecs; - int n = downline(); + int n = downline(args); if (n) { int m = zmult, ret; @@ -821,6 +821,8 @@ pushline(UNUSED(char **args)) zpushnode(bufstack, zlelineasstring(zleline, zlell, 0, NULL, NULL, 0)); while (--n) zpushnode(bufstack, ztrdup("")); + if (invicmdmode()) + INCCS(); stackcs = zlecs; *zleline = ZWC('\0'); zlell = zlecs = 0; @@ -851,8 +853,10 @@ pushlineoredit(char **args) free(zhline); } ret = pushline(args); - if (!isfirstln) - errflag = done = 1; + if (!isfirstln) { + errflag |= ERRFLAG_ERROR|ERRFLAG_INT; + done = 1; + } clearlist = 1; return ret; } diff --git a/Src/Zle/zle_keymap.c b/Src/Zle/zle_keymap.c index e21e769bd..c6fae251d 100644 --- a/Src/Zle/zle_keymap.c +++ b/Src/Zle/zle_keymap.c @@ -503,7 +503,18 @@ selectkeymap(char *name, int fb) mod_export void selectlocalmap(Keymap m) { + Keymap oldm = localkeymap; localkeymap = m; + if (oldm && !m) + { + /* + * No local keymap; so we are returning to the global map. If + * the user ^Ced in the local map, they probably just want to go + * back to normal editing. So remove the interrupt error + * status. + */ + errflag &= ~ERRFLAG_INT; + } } /* Reopen the currently selected keymap, in case it got deleted. This * @@ -1191,7 +1202,7 @@ init_keymaps(void) { createkeymapnamtab(); default_bindings(); - keybuf = (char *)zalloc(keybufsz); + keybuf = (char *)zshcalloc(keybufsz); lastnamed = refthingy(t_undefinedkey); } @@ -1277,8 +1288,10 @@ default_bindings(void) Keymap vmap = newkeymap(NULL, "viins"); Keymap emap = newkeymap(NULL, "emacs"); Keymap amap = newkeymap(NULL, "vicmd"); + Keymap oppmap = newkeymap(NULL, "viopp"); + Keymap vismap = newkeymap(NULL, "visual"); Keymap smap = newkeymap(NULL, ".safe"); - Keymap vimaps[2], kptr; + Keymap vimaps[2], vilmaps[2], kptr; char buf[3], *ed; int i; @@ -1332,6 +1345,36 @@ default_bindings(void) add_cursor_key(kptr, TCLEFTCURSOR, t_vibackwardchar, 'D'); add_cursor_key(kptr, TCRIGHTCURSOR, t_viforwardchar, 'C'); } + vilmaps[0] = oppmap; + vilmaps[1] = vismap; + for (i = 0; i < 2; i++) { + /* vi visual selection and operator pending local maps */ + kptr = vilmaps[i]; + add_cursor_key(kptr, TCUPCURSOR, t_upline, 'A'); + add_cursor_key(kptr, TCDOWNCURSOR, t_downline, 'B'); + bindkey(kptr, "k", refthingy(t_upline), NULL); + bindkey(kptr, "j", refthingy(t_downline), NULL); + bindkey(kptr, "aa", refthingy(t_selectashellword), NULL); + bindkey(kptr, "ia", refthingy(t_selectinshellword), NULL); + bindkey(kptr, "aw", refthingy(t_selectaword), NULL); + bindkey(kptr, "iw", refthingy(t_selectinword), NULL); + bindkey(kptr, "aW", refthingy(t_selectablankword), NULL); + bindkey(kptr, "iW", refthingy(t_selectinblankword), NULL); + } + /* escape in operator pending cancels the operation */ + bindkey(oppmap, "\33", refthingy(t_vicmdmode), NULL); + bindkey(vismap, "o", refthingy(t_exchangepointandmark), NULL); + bindkey(vismap, "p", refthingy(t_putreplaceselection), NULL); + bindkey(vismap, "x", refthingy(t_videlete), NULL); + bindkey(vismap, "~", refthingy(t_vioperswapcase), NULL); + + /* vi mode: some common vim bindings */ + bindkey(amap, "ga", refthingy(t_whatcursorposition), NULL); + bindkey(amap, "ge", refthingy(t_vibackwardwordend), NULL); + bindkey(amap, "gE", refthingy(t_vibackwardblankwordend), NULL); + bindkey(amap, "gg", refthingy(t_beginningofbufferorhistory), NULL); + bindkey(amap, "g~", refthingy(t_vioperswapcase), NULL); + bindkey(amap, "g~~", NULL, "g~g~"); /* emacs mode: arrow keys */ add_cursor_key(emap, TCUPCURSOR, t_uplineorhistory, 'A'); @@ -1373,6 +1416,8 @@ default_bindings(void) linkkeymap(vmap, "viins", 0); linkkeymap(emap, "emacs", 0); linkkeymap(amap, "vicmd", 0); + linkkeymap(oppmap, "viopp", 0); + linkkeymap(vismap, "visual", 0); linkkeymap(smap, ".safe", 1); if (((ed = zgetenv("VISUAL")) && strstr(ed, "vi")) || ((ed = zgetenv("EDITOR")) && strstr(ed, "vi"))) @@ -1408,6 +1453,7 @@ getkeymapcmd(Keymap km, Thingy *funcp, char **strp) Thingy func = t_undefinedkey; char *str = NULL; int lastlen = 0, lastc = lastchar; + int timeout = 0; keybuflen = 0; keybuf[0] = 0; @@ -1425,7 +1471,7 @@ getkeymapcmd(Keymap km, Thingy *funcp, char **strp) * argument to bindkey is in the correct form for the locale. * That's beyond our control. */ - while(getkeybuf(!!lastlen) != EOF) { + while(getkeybuf(timeout) != EOF) { char *s; Thingy f; int loc = !!localkeymap; @@ -1435,7 +1481,7 @@ getkeymapcmd(Keymap km, Thingy *funcp, char **strp) loc = ((f = keybind(localkeymap, keybuf, &s)) != t_undefinedkey); ispfx = keyisprefix(localkeymap, keybuf); } - if (!loc) + if (!loc && !ispfx) f = keybind(km, keybuf, &s); ispfx |= keyisprefix(km, keybuf); @@ -1444,6 +1490,11 @@ getkeymapcmd(Keymap km, Thingy *funcp, char **strp) func = f; str = s; lastc = lastchar; + + /* can be patient with vi commands that need a motion operator: * + * they wait till a key is pressed for the movement anyway */ + timeout = !(!virangeflag && !region_active && f && f->widget && + f->widget->flags & ZLE_VIOPER); } if (!ispfx) break; diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c index 442c31995..cec44c0ed 100644 --- a/Src/Zle/zle_main.c +++ b/Src/Zle/zle_main.c @@ -187,10 +187,6 @@ mod_export char *zlenoargs[1] = { NULL }; static char **raw_lp, **raw_rp; -#ifdef FIONREAD -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. @@ -210,9 +206,6 @@ mod_export void zsetterm(void) { struct ttyinfo ti; -#if defined(FIONREAD) - int val; -#endif if (fetchttyinfo) { /* @@ -224,30 +217,6 @@ zsetterm(void) fetchttyinfo = 0; } -#if defined(FIONREAD) - ioctl(SHTTY, FIONREAD, (char *)&val); - if (val) { - /* - * Problems can occur on some systems when switching from - * canonical to non-canonical input. The former is usually - * set while running programmes, but the latter is necessary - * for zle. If there is input in canonical mode, then we - * need to read it without setting up the terminal. Furthermore, - * while that input gets processed there may be more input - * being typed (i.e. further typeahead). This means that - * we can't set up the terminal for zle *at all* until - * we are sure there is no more typeahead to come. So - * if there is typeahead, we set the flag delayzsetterm. - * Then getbyte() performs another FIONREAD call; if that is - * 0, we have finally used up all the typeahead, and it is - * safe to alter the terminal, which we do at that point. - */ - delayzsetterm = 1; - return; - } else - delayzsetterm = 0; -#endif - /* sanitize the tty */ #ifdef HAS_TIO shttyinfo.tio.c_lflag |= ICANON | ECHO; @@ -343,7 +312,7 @@ zsetterm(void) ti.ltchars.t_dsuspc = ti.ltchars.t_lnextc = -1; #endif -#if defined(TTY_NEEDS_DRAINING) && defined(TIOCOUTQ) && defined(HAVE_SELECT) +#if defined(TIOCOUTQ) && defined(HAVE_SELECT) if (baud) { /**/ int n = 0; @@ -541,11 +510,7 @@ raw_getbyte(long do_keytmout, char *cptr) * timeouts may be external, so we may have both a permanent watched * fd and a long-term timeout. */ - if ((nwatch || tmout.tp != ZTM_NONE) -#ifdef FIONREAD - && ! delayzsetterm -#endif - ) { + if ((nwatch || tmout.tp != ZTM_NONE)) { #if defined(HAVE_SELECT) || defined(HAVE_POLL) int i, errtry = 0, selret; # ifdef HAVE_POLL @@ -779,7 +744,7 @@ raw_getbyte(long do_keytmout, char *cptr) } if (errflag) { /* No sensible way of handling errors here */ - errflag = 0; + errflag &= ~ERRFLAG_ERROR; /* * Paranoia: don't run the hooks again this * time. @@ -883,14 +848,6 @@ getbyte(long do_keytmout, int *timeout) 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(); @@ -925,7 +882,7 @@ getbyte(long do_keytmout, int *timeout) die = 0; if (!errflag && !retflag && !breaks && !exit_pending) continue; - errflag = 0; + errflag &= ~ERRFLAG_ERROR; breaks = obreaks; errno = old_errno; return lastchar = EOF; @@ -1067,6 +1024,7 @@ getrestchar(int inchar) void zlecore(void) { + Keymap km; #if !defined(HAVE_POLL) && defined(HAVE_SELECT) struct timeval tv; fd_set foofd; @@ -1088,8 +1046,10 @@ zlecore(void) statusline = NULL; vilinerange = 0; reselectkeymap(); - selectlocalmap(NULL); + selectlocalmap(invicmdmode() && region_active && (km = openkeymap("visual")) + ? km : NULL); bindk = getkeycmd(); + selectlocalmap(NULL); if (bindk) { if (!zlell && isfirstln && !(zlereadflags & ZLRF_IGNOREEOF) && lastchar == eofchar) { @@ -1115,7 +1075,7 @@ zlecore(void) DECCS(); handleundo(); } else { - errflag = 1; + errflag |= ERRFLAG_ERROR; break; } #ifdef HAVE_POLL @@ -1273,6 +1233,10 @@ zleread(char **lp, char **rp, int flags, int context, char *init, char *finish) zleactive = 1; resetneeded = 1; + /* + * Start of the main zle read. + * Fully reset error conditions, including user interrupt. + */ errflag = retflag = 0; lastcol = -1; initmodifier(&zmod); @@ -1289,7 +1253,9 @@ zleread(char **lp, char **rp, int flags, int context, char *init, char *finish) zlecore(); if (errflag) - setsparam("ZLE_LINE_ABORTED", zlegetline(NULL, NULL)); + setsparam((zlecontext == ZLCON_VARED) ? + "ZLE_VARED_ABORTED" : + "ZLE_LINE_ABORTED", zlegetline(NULL, NULL)); if (done && !exit_pending && !errflag) zlecallhook(finish, NULL); @@ -1698,7 +1664,7 @@ bin_vared(char *name, char **args, Options ops, UNUSED(int func)) } if (!t || errflag) { /* error in editing */ - errflag = 0; + errflag &= ~ERRFLAG_ERROR; breaks = obreaks; if (t) zsfree(t); @@ -1818,7 +1784,7 @@ recursiveedit(UNUSED(char **args)) zrefresh(); zlecore(); - locerror = errflag; + locerror = errflag ? 1 : 0; errflag = done = eofsent = 0; return locerror; @@ -2128,7 +2094,7 @@ finish_(UNUSED(Module m)) free(kring[i].buf); zfree(kring, kringsize * sizeof(struct cutbuffer)); } - for(i = 35; i--; ) + for(i = 36; i--; ) zfree(vibuf[i].buf, vibuf[i].len); /* editor entry points */ diff --git a/Src/Zle/zle_misc.c b/Src/Zle/zle_misc.c index 9bc1cf6f5..4669ef2ad 100644 --- a/Src/Zle/zle_misc.c +++ b/Src/Zle/zle_misc.c @@ -47,13 +47,13 @@ doinsert(ZLE_STRING_T zstr, int len) iremovesuffix(c1, 0); invalidatelist(); - if (insmode) + /* In overwrite mode, don't replace newlines. */ + if (insmode || zleline[zlecs] == ZWC('\n')) spaceinline(m * len); else -#ifdef MULTIBYTE_SUPPORT { int pos = zlecs, diff, i; - +#ifdef MULTIBYTE_SUPPORT /* * Calculate the number of character positions we are * going to be using. The algorithm is that @@ -68,15 +68,18 @@ doinsert(ZLE_STRING_T zstr, int len) * useful there anyway and this doesn't cause any * particular harm. */ - for (i = 0, count = 0; i < len; i++) { + for (i = 0, count = 0; i < len * m; i++) { if (!IS_COMBINING(zstr[i])) count++; } +#else + count = len * m; +#endif /* - * Ensure we replace a complete combining character - * for each character we overwrite. + * Ensure we replace a complete combining characterfor each + * character we overwrite. Switch to inserting at first newline. */ - for (i = count; pos < zlell && i--; ) { + for (i = count; pos < zlell && zleline[pos] != ZWC('\n') && i--; ) { INCPOS(pos); } /* @@ -96,10 +99,6 @@ doinsert(ZLE_STRING_T zstr, int len) shiftchars(zlecs, diff); } } -#else - if (zlecs + m * len > zlell) - spaceinline(zlecs + m * len - zlell); -#endif while (m--) for (s = zstr, count = len; count; s++, count--) zleline[zlecs++] = *s; @@ -440,15 +439,52 @@ killline(char **args) } /**/ +void +regionlines(int *start, int *end) +{ + int origcs = zlecs; + + UNMETACHECK(); + if (zlecs < mark) { + *start = findbol(); + zlecs = (mark > zlell) ? zlell : mark; + *end = findeol(); + } else { + *end = findeol(); + zlecs = mark; + *start = findbol(); + } + zlecs = origcs; +} + +/**/ int killregion(UNUSED(char **args)) { if (mark > zlell) mark = zlell; - if (mark > zlecs) + if (region_active == 2) { + int a, b; + regionlines(&a, &b); + zlecs = a; + region_active = 0; + cut(zlecs, b - zlecs, CUT_RAW); + shiftchars(zlecs, b - zlecs); + if (zlell) { + if (zlecs == zlell) + DECCS(); + foredel(1, 0); + vifirstnonblank(zlenoargs); + } + } else if (mark > zlecs) { + if (invicmdmode()) + INCPOS(mark); forekill(mark - zlecs, CUT_RAW); - else + } else { + if (invicmdmode()) + INCCS(); backkill(zlecs - mark, CUT_FRONT|CUT_RAW); + } return 0; } @@ -456,6 +492,7 @@ killregion(UNUSED(char **args)) int copyregionaskill(char **args) { + int start, end; if (*args) { int len; ZLE_STRING_T line = stringaszleline(*args, 0, &len, NULL, NULL); @@ -464,10 +501,16 @@ copyregionaskill(char **args) } else { if (mark > zlell) mark = zlell; - if (mark > zlecs) - cut(zlecs, mark - zlecs, 0); - else - cut(mark, zlecs - mark, CUT_FRONT); + if (mark > zlecs) { + start = zlecs; + end = mark; + } else { + start = mark; + end = zlecs; + } + if (invicmdmode()) + INCPOS(end); + cut(start, end - start, mark > zlecs ? 0 : CUT_FRONT); } return 0; } @@ -475,8 +518,10 @@ copyregionaskill(char **args) /* * kct: index into kill ring, or -1 for original cutbuffer of yank. * yankb, yanke: mark the start and end of last yank in editing buffer. + * yankcs marks the cursor position preceding the last yank */ -static int kct, yankb, yanke; +static int kct, yankb, yanke, yankcs; + /* The original cutbuffer, either cutbuf or one of the vi buffers. */ static Cutbuffer kctbuf; @@ -494,8 +539,7 @@ yank(UNUSED(char **args)) kctbuf = &cutbuf; if (!kctbuf->buf) return 1; - mark = zlecs; - yankb = zlecs; + yankb = yankcs = mark = zlecs; while (n--) { kct = -1; spaceinline(kctbuf->len); @@ -506,11 +550,140 @@ yank(UNUSED(char **args)) return 0; } +/* position: 0 is before, 1 after, 2 split the line */ +static void pastebuf(Cutbuffer buf, int mult, int position) +{ + int cc; + if (buf->flags & CUTBUFFER_LINE) { + if (position == 2) { + if (!zlecs) + position = 0; + else if (zlecs == zlell) + position = 1; + } + if (position == 2) { + yankb = zlecs; + spaceinline(buf->len + 2); + zleline[zlecs++] = ZWC('\n'); + ZS_memcpy(zleline + zlecs, buf->buf, buf->len); + zlecs += buf->len; + zleline[zlecs] = ZWC('\n'); + yanke = zlecs + 1; + } else if (position != 0) { + yankb = zlecs = findeol(); + spaceinline(buf->len + 1); + zleline[zlecs++] = ZWC('\n'); + yanke = zlecs + buf->len; + ZS_memcpy(zleline + zlecs, buf->buf, buf->len); + } else { + yankb = zlecs = findbol(); + spaceinline(buf->len + 1); + ZS_memcpy(zleline + zlecs, buf->buf, buf->len); + yanke = zlecs + buf->len + 1; + zleline[zlecs + buf->len] = ZWC('\n'); + } + vifirstnonblank(zlenoargs); + } else { + if (position == 1 && zlecs != findeol()) + INCCS(); + yankb = zlecs; + cc = buf->len; + while (mult--) { + spaceinline(cc); + ZS_memcpy(zleline + zlecs, buf->buf, cc); + zlecs += cc; + } + yanke = zlecs; + if (zlecs) + DECCS(); + } +} + +/**/ +int +viputbefore(UNUSED(char **args)) +{ + int n = zmult; + + startvichange(-1); + if (n < 0 || zmod.flags & MOD_NULL) + return 1; + if (zmod.flags & MOD_VIBUF) + kctbuf = &vibuf[zmod.vibuf]; + else + kctbuf = &cutbuf; + if (!kctbuf->buf) + return 1; + kct = -1; + yankcs = zlecs; + pastebuf(kctbuf, n, 0); + return 0; +} + +/**/ +int +viputafter(UNUSED(char **args)) +{ + int n = zmult; + + startvichange(-1); + if (n < 0 || zmod.flags & MOD_NULL) + return 1; + if (zmod.flags & MOD_VIBUF) + kctbuf = &vibuf[zmod.vibuf]; + else + kctbuf = &cutbuf; + if (!kctbuf->buf) + return 1; + kct = -1; + yankcs = zlecs; + pastebuf(kctbuf, n, 1); + return 0; +} + +/**/ +int +putreplaceselection(UNUSED(char **args)) +{ + int n = zmult; + struct cutbuffer prevbuf; + Cutbuffer putbuf; + int clear = 0; + int pos = 2; + + startvichange(-1); + if (n < 0 || zmod.flags & MOD_NULL) + return 1; + putbuf = (zmod.flags & MOD_VIBUF) ? &vibuf[zmod.vibuf] : &cutbuf; + if (!putbuf->buf) + return 1; + memcpy(&prevbuf, putbuf, sizeof(prevbuf)); + + /* if "9 was specified, prevent killregion from freeing it */ + if (zmod.vibuf == 35) { + putbuf->buf = 0; + clear = 1; + } + + zmod.flags = 0; /* flags apply to paste not kill */ + if (region_active == 2 && prevbuf.flags & CUTBUFFER_LINE) { + int a, b; + regionlines(&a, &b); + pos = (b == zlell); + } + killregion(zlenoargs); + + pastebuf(&prevbuf, n, pos); + if (clear) + free(prevbuf.buf); + return 0; +} + /**/ int yankpop(UNUSED(char **args)) { - int cc, kctstart = kct; + int kctstart = kct; Cutbuffer buf; if (!(lastcmd & ZLE_YANK) || !kring || !kctbuf) { @@ -557,11 +730,8 @@ yankpop(UNUSED(char **args)) zlecs = yankb; foredel(yanke - yankb, CUT_RAW); - cc = buf->len; - spaceinline(cc); - ZS_memcpy(zleline + zlecs, buf->buf, cc); - zlecs += cc; - yanke = zlecs; + zlecs = yankcs; + pastebuf(buf, 1, !!(lastcmd & ZLE_YANKAFTER)); return 0; } @@ -871,7 +1041,7 @@ copyprevshellword(UNUSED(char **args)) int sendbreak(UNUSED(char **args)) { - errflag = 1; + errflag |= ERRFLAG_ERROR|ERRFLAG_INT; return 1; } @@ -881,15 +1051,25 @@ quoteregion(UNUSED(char **args)) { ZLE_STRING_T str; size_t len; + int extra = invicmdmode(); if (mark > zlell) mark = zlell; - if (mark < zlecs) { + if (region_active == 2) { + int a, b; + regionlines(&a, &b); + zlecs = a; + mark = b; + extra = 0; + } else if (mark < zlecs) { int tmp = mark; mark = zlecs; zlecs = tmp; } - str = (ZLE_STRING_T)hcalloc((len = mark - zlecs) * ZLE_CHAR_SIZE); + if (extra) + INCPOS(mark); + str = (ZLE_STRING_T)hcalloc((len = mark - zlecs) * + ZLE_CHAR_SIZE); ZS_memcpy(str, zleline + zlecs, len); foredel(len, CUT_RAW); str = makequote(str, &len); diff --git a/Src/Zle/zle_move.c b/Src/Zle/zle_move.c index 7312b3f20..d751c4333 100644 --- a/Src/Zle/zle_move.c +++ b/Src/Zle/zle_move.c @@ -509,6 +509,55 @@ exchangepointandmark(UNUSED(char **args)) /**/ int +visualmode(UNUSED(char **args)) +{ + if (virangeflag) { + prefixflag = 1; + zmod.flags &= ~MOD_LINE; + zmod.flags |= MOD_CHAR; + return 0; + } + switch (region_active) { + case 1: + region_active = 0; + break; + case 0: + mark = zlecs; + /* fall through */ + case 2: + region_active = 1; + break; + } + return 0; +} + +/**/ +int +visuallinemode(UNUSED(char **args)) +{ + if (virangeflag) { + prefixflag = 1; + zmod.flags &= ~MOD_CHAR; + zmod.flags |= MOD_LINE; + return 0; + } + switch (region_active) { + case 2: + region_active = 0; + break; + case 0: + mark = zlecs; + /* fall through */ + case 1: + region_active = 2; + break; + } + return 0; +} + + +/**/ +int vigotocolumn(UNUSED(char **args)) { int x, y, n = zmult; @@ -538,7 +587,8 @@ vimatchbracket(UNUSED(char **args)) if ((zlecs == zlell || zleline[zlecs] == '\n') && zlecs > 0) DECCS(); - + if (virangeflag) + mark = zlecs; otog: if (zlecs == zlell || zleline[zlecs] == '\n') { zlecs = ocs; @@ -550,7 +600,6 @@ vimatchbracket(UNUSED(char **args)) oth = '}'; break; case /*{*/ '}': - virangeflag = -virangeflag; dir = -1; oth = '{'; /*}*/ break; @@ -559,7 +608,6 @@ vimatchbracket(UNUSED(char **args)) oth = ')'; break; case ')': - virangeflag = -virangeflag; dir = -1; oth = '('; break; @@ -568,7 +616,6 @@ vimatchbracket(UNUSED(char **args)) oth = ']'; break; case ']': - virangeflag = -virangeflag; dir = -1; oth = '['; break; @@ -576,6 +623,8 @@ vimatchbracket(UNUSED(char **args)) INCCS(); goto otog; } + if (virangeflag && dir < 0) + INCPOS(mark); /* include starting position when going backwards */ ct = 1; while (zlecs >= 0 && zlecs < zlell && ct) { if (dir < 0) @@ -599,7 +648,7 @@ vimatchbracket(UNUSED(char **args)) int viforwardchar(char **args) { - int lim = findeol() - invicmdmode(); + int lim = findeol(); int n = zmult; if (n < 0) { @@ -609,6 +658,8 @@ viforwardchar(char **args) zmult = n; return ret; } + if (invicmdmode() && !virangeflag) + DECPOS(lim); if (zlecs >= lim) return 1; while (n-- && zlecs < lim) diff --git a/Src/Zle/zle_params.c b/Src/Zle/zle_params.c index 5845207fa..dc5fed4ce 100644 --- a/Src/Zle/zle_params.c +++ b/Src/Zle/zle_params.c @@ -198,7 +198,7 @@ set_buffer(UNUSED(Param pm), char *x) setline(x, 0); zsfree(x); } else - zlecs = zlell = 0; + viinsbegin = zlecs = zlell = 0; fixsuffix(); menucmp = 0; } diff --git a/Src/Zle/zle_refresh.c b/Src/Zle/zle_refresh.c index 684ac13a2..fe337993f 100644 --- a/Src/Zle/zle_refresh.c +++ b/Src/Zle/zle_refresh.c @@ -396,8 +396,10 @@ get_region_highlight(UNUSED(Param pm)) struct region_highlight *rhp; /* region_highlights may not have been set yet */ - if (arrsize) - arrsize -= N_SPECIAL_HIGHLIGHTS; + if (!arrsize) + return hmkarray(NULL); + arrsize -= N_SPECIAL_HIGHLIGHTS; + DPUTS(arrsize < 0, "arrsize is negative from n_region_highlights"); arrp = retarr = (char **)zhalloc((arrsize+1)*sizeof(char *)); /* ignore special highlighting */ @@ -449,10 +451,15 @@ set_region_highlight(UNUSED(Param pm), char **aval) len = aval ? arrlen(aval) : 0; if (n_region_highlights != len + N_SPECIAL_HIGHLIGHTS) { /* no null termination, but include special highlighting at start */ - n_region_highlights = len + N_SPECIAL_HIGHLIGHTS; + int newsize = len + N_SPECIAL_HIGHLIGHTS; + int diffsize = newsize - n_region_highlights; region_highlights = (struct region_highlight *) zrealloc(region_highlights, - sizeof(struct region_highlight) * n_region_highlights); + sizeof(struct region_highlight) * newsize); + if (diffsize > 0) + memset(region_highlights + newsize - diffsize, 0, + sizeof(struct region_highlight) * diffsize); + n_region_highlights = newsize; } if (!aval) @@ -1028,6 +1035,8 @@ zrefresh(void) /* this will create region_highlights if it's still NULL */ zle_set_highlight(); + DPUTS(!region_highlights, "region_highlights not created"); + /* check for region between point ($CURSOR) and mark ($MARK) */ if (region_active) { if (zlecs <= mark) { @@ -1037,6 +1046,15 @@ zrefresh(void) region_highlights[0].start = mark; region_highlights[0].end = zlecs; } + if (region_active == 2) { + int origcs = zlecs; + zlecs = region_highlights[0].end; + region_highlights[0].end = findeol(); + zlecs = region_highlights[0].start; + region_highlights[0].start = findbol(); + zlecs = origcs; + } else if (invicmdmode()) + INCPOS(region_highlights[0].end); } else { region_highlights[0].start = region_highlights[0].end = -1; } diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c index 499c4ae77..f18ad170e 100644 --- a/Src/Zle/zle_tricky.c +++ b/Src/Zle/zle_tricky.c @@ -634,7 +634,8 @@ docomplete(int lst) metafy_line(); ocs = zlemetacs; - origline = dupstring(zlemetaline); + zsfree(origline); + origline = ztrdup(zlemetaline); origcs = zlemetacs; origll = zlemetall; if (!isfirstln && (chline != NULL || zle_chline != NULL)) { @@ -662,8 +663,9 @@ docomplete(int lst) * NOTE: get_comp_string() calls pushheap(), but not popheap(). */ noerrs = 1; s = get_comp_string(); - DPUTS(wb < 0 || zlemetacs < wb || zlemetacs > we, - "BUG: 0 <= wb <= zlemetacs <= we is not true!"); + DPUTS3(wb < 0 || zlemetacs < wb || zlemetacs > we, + "BUG: 0 <= wb (%d) <= zlemetacs (%d) <= we (%d) is not true!", + wb, zlemetacs, we); noerrs = ne; /* For vi mode, reset the start-of-insertion pointer to the beginning * * of the word being completed, if it is currently later. Vi itself * @@ -696,7 +698,7 @@ docomplete(int lst) freeheap(); /* Save the lexer state, in case the completion code uses the lexer * * somewhere (e.g. when processing a compctl -s flag). */ - lexsave(); + zcontext_save(); if (inwhat == IN_ENV) lincmd = 0; if (s) { @@ -828,7 +830,7 @@ docomplete(int lst) if (olst == COMP_EXPAND_COMPLETE && !strcmp(ol, zlemetaline)) { zlemetacs = ocs; - errflag = 0; + errflag &= ~ERRFLAG_ERROR; if (!compfunc) { char *p; @@ -866,7 +868,7 @@ docomplete(int lst) } else ret = 1; /* Reset the lexer state, pop the heap. */ - lexrestore(); + zcontext_restore(); popheap(); dat[0] = lst; @@ -876,6 +878,19 @@ docomplete(int lst) active = 0; makecommaspecial(0); + + /* + * As a special case, we reset user interrupts here. + * That's because completion is an intensive piece of + * computation that the user might want to interrupt separately + * from anything else going on. If they do, they probably + * want to keep the line edit buffer intact. + * + * There's a race here that the user might hit ^C just + * after completion exited anyway, but that's inevitable. + */ + errflag &= ~ERRFLAG_INT; + return dat[1]; } @@ -1149,7 +1164,7 @@ get_comp_string(void) varname = NULL; insubscr = 0; clwpos = -1; - lexsave(); + zcontext_save(); lexflags = LEXFLAGS_ZLE; inpush(dupstrspace(linptr), 0, NULL); strinbeg(0); @@ -1393,7 +1408,8 @@ get_comp_string(void) } strinend(); inpop(); - errflag = lexflags = 0; + lexflags = 0; + errflag &= ~ERRFLAG_ERROR; if (parbegin != -1) { /* We are in command or process substitution if we are not in * a $((...)). */ @@ -1406,7 +1422,7 @@ get_comp_string(void) zlemetall -= parend; zlemetaline[zlemetall + addedx] = '\0'; } - lexrestore(); + zcontext_restore(); tt = NULL; goto start; } @@ -1480,12 +1496,12 @@ get_comp_string(void) if (tmp) { tmp = NULL; linptr = zlemetaline; - lexrestore(); + zcontext_restore(); addedx = 0; goto start; } noaliases = ona; - lexrestore(); + zcontext_restore(); return NULL; } @@ -1720,9 +1736,11 @@ get_comp_string(void) for (pe = p + 2; *pe && *pe != Snull && i + (pe - p) < zlemetacs; pe++) ; - if (!*pe) { + if (*pe != Snull) { /* no terminating Snull, can't substitute */ skipchars = 2; + if (*pe) + j = 1; } else { /* * Try and substitute the $'...' expression. @@ -1795,6 +1813,10 @@ get_comp_string(void) * first clue how the completion system actually works. */ skipchars = 2; + /* + * Also pretend we're in single quotes. + */ + j = 1; } } } @@ -1817,7 +1839,7 @@ get_comp_string(void) ocs = zlemetacs; zlemetacs = i; foredel(skipchars, CUT_RAW); - if ((zlemetacs = ocs) > (i -= skipchars)) + if ((zlemetacs = ocs) > --i) zlemetacs -= skipchars; we -= skipchars; } @@ -2129,7 +2151,7 @@ get_comp_string(void) offs = boffs; } } - lexrestore(); + zcontext_restore(); return (char *)s; } @@ -2769,7 +2791,7 @@ doexpandhist(void) expanding = 1; excs = zlemetacs; zlemetall = zlemetacs = 0; - lexsave(); + zcontext_save(); /* We push ol as it will remain unchanged */ inpush(ol, 0, NULL); strinbeg(1); @@ -2781,7 +2803,7 @@ doexpandhist(void) } while (tok != ENDINPUT && tok != LEXERR); while (!lexstop) hgetc(); - /* We have to save errflags because it's reset in lexrestore. Since * + /* We have to save errflags because it's reset in zcontext_restore. Since * * noerrs was set to 1 errflag is true if there was a habort() which * * means that the expanded string is unusable. */ err = errflag; @@ -2789,7 +2811,7 @@ doexpandhist(void) noaliases = ona; strinend(); inpop(); - lexrestore(); + zcontext_restore(); expanding = 0; if (!err) { @@ -2888,7 +2910,7 @@ getcurcmd(void) int curlincmd; char *s = NULL; - lexsave(); + zcontext_save(); lexflags = LEXFLAGS_ZLE; metafy_line(); inpush(dupstrspace(zlemetaline), 0, NULL); @@ -2910,9 +2932,9 @@ getcurcmd(void) popheap(); strinend(); inpop(); - errflag = 0; + errflag &= ~ERRFLAG_ERROR; unmetafy_line(); - lexrestore(); + zcontext_restore(); return s; } diff --git a/Src/Zle/zle_utils.c b/Src/Zle/zle_utils.c index 1089e274f..e4ab97a54 100644 --- a/Src/Zle/zle_utils.c +++ b/Src/Zle/zle_utils.c @@ -43,10 +43,10 @@ struct cutbuffer *kring; int kringsize, kringnum; /* Vi named cut buffers. 0-25 are the named buffers "a to "z, and * - * 26-34 are the numbered buffer stack "1 to "9. */ + * 26-35 are the numbered buffer stack "0 to "9. */ /**/ -struct cutbuffer vibuf[35]; +struct cutbuffer vibuf[36]; /* the line before last mod (for undo purposes) */ @@ -117,7 +117,7 @@ int zlecharasstring(ZLE_CHAR_T inchar, char *buf) { #ifdef MULTIBYTE_SUPPORT - size_t ret; + int ret; char *ptr; #ifdef __STDC_ISO_10646__ @@ -675,35 +675,42 @@ zle_restore_positions(void) zlell = oldpos->ll; } - /* Count number of regions and see if the array needs resizing */ - for (nreg = 0, oldrhp = oldpos->regions; - oldrhp; - nreg++, oldrhp = oldrhp->next) - ; - if (nreg + N_SPECIAL_HIGHLIGHTS != n_region_highlights) { - n_region_highlights = nreg + N_SPECIAL_HIGHLIGHTS; - region_highlights = (struct region_highlight *) - zrealloc(region_highlights, - sizeof(struct region_highlight) * n_region_highlights); - } - oldrhp = oldpos->regions; - rhp = region_highlights + N_SPECIAL_HIGHLIGHTS; - while (oldrhp) { - struct zle_region *nextrhp = oldrhp->next; - - rhp->atr = oldrhp->atr; - rhp->flags = oldrhp->flags; - if (zlemetaline) { - rhp->start_meta = oldrhp->start; - rhp->end_meta = oldrhp->end; - } else { - rhp->start = oldrhp->start; - rhp->end = oldrhp->end; + if (oldpos->regions) { + /* Count number of regions and see if the array needs resizing */ + for (nreg = 0, oldrhp = oldpos->regions; + oldrhp; + nreg++, oldrhp = oldrhp->next) + ; + if (nreg + N_SPECIAL_HIGHLIGHTS != n_region_highlights) { + n_region_highlights = nreg + N_SPECIAL_HIGHLIGHTS; + region_highlights = (struct region_highlight *) + zrealloc(region_highlights, + sizeof(struct region_highlight) * n_region_highlights); } + oldrhp = oldpos->regions; + rhp = region_highlights + N_SPECIAL_HIGHLIGHTS; + while (oldrhp) { + struct zle_region *nextrhp = oldrhp->next; - zfree(oldrhp, sizeof(*oldrhp)); - oldrhp = nextrhp; - rhp++; + rhp->atr = oldrhp->atr; + rhp->flags = oldrhp->flags; + if (zlemetaline) { + rhp->start_meta = oldrhp->start; + rhp->end_meta = oldrhp->end; + } else { + rhp->start = oldrhp->start; + rhp->end = oldrhp->end; + } + + zfree(oldrhp, sizeof(*oldrhp)); + oldrhp = nextrhp; + rhp++; + } + } else if (region_highlights) { + zfree(region_highlights, sizeof(struct region_highlight) * + n_region_highlights); + region_highlights = NULL; + n_region_highlights = 0; } zfree(oldpos, sizeof(*oldpos)); @@ -785,6 +792,8 @@ spaceinline(int ct) if (mark > zlecs) mark += ct; + if (viinsbegin > zlecs) + viinsbegin = 0; if (region_highlights) { for (rhp = region_highlights + N_SPECIAL_HIGHLIGHTS; @@ -914,7 +923,7 @@ cut(int i, int ct, int flags) void cuttext(ZLE_STRING_T line, int ct, int flags) { - if (!ct) + if (!(ct || vilinerange) || zmod.flags & MOD_NULL) return; UNMETACHECK(); @@ -941,17 +950,23 @@ cuttext(ZLE_STRING_T line, int ct, int flags) ZS_memcpy(b->buf + len, line, ct); b->len = len + ct; } - return; - } else { - /* Save in "1, shifting "1-"8 along to "2-"9 */ - int n; - free(vibuf[34].buf); - for(n=34; n>26; n--) - vibuf[n] = vibuf[n-1]; + } else if (flags & CUT_YANK) { + /* Save in "0 */ + free(vibuf[26].buf); vibuf[26].buf = (ZLE_STRING_T)zalloc(ct * ZLE_CHAR_SIZE); ZS_memcpy(vibuf[26].buf, line, ct); vibuf[26].len = ct; vibuf[26].flags = vilinerange ? CUTBUFFER_LINE : 0; + } else { + /* Save in "1, shifting "1-"8 along to "2-"9 */ + int n; + free(vibuf[35].buf); + for(n=35; n>27; n--) + vibuf[n] = vibuf[n-1]; + vibuf[27].buf = (ZLE_STRING_T)zalloc(ct * ZLE_CHAR_SIZE); + ZS_memcpy(vibuf[27].buf, line, ct); + vibuf[27].len = ct; + vibuf[27].flags = vilinerange ? CUTBUFFER_LINE : 0; } if (!cutbuf.buf) { cutbuf.buf = (ZLE_STRING_T)zalloc(ZLE_CHAR_SIZE); @@ -981,8 +996,9 @@ cuttext(ZLE_STRING_T line, int ct, int flags) cutbuf.buf = s; cutbuf.len += ct; } else { + /* don't alloc 0 bytes; length 0 occurs for blank lines in vi mode */ cutbuf.buf = realloc((char *)cutbuf.buf, - (cutbuf.len + ct) * ZLE_CHAR_SIZE); + (cutbuf.len + (ct ? ct : 1)) * ZLE_CHAR_SIZE); ZS_memcpy(cutbuf.buf + cutbuf.len, line, ct); cutbuf.len += ct; } @@ -1098,6 +1114,7 @@ setline(char *s, int flags) */ free(zleline); + viinsbegin = 0; zleline = stringaszleline(scp, 0, &zlell, &linesz, NULL); if ((flags & ZSL_TOEND) && (zlecs = zlell) && invicmdmode()) @@ -1356,7 +1373,6 @@ int handlefeep(UNUSED(char **args)) { zbeep(); - region_active = 0; return 0; } @@ -1386,7 +1402,8 @@ static struct change *nextchanges, *endnextchanges; /* incremented to provide a unique change number */ -static zlong undo_changeno; +/**/ +zlong undo_changeno; /* If non-zero, the last increment to undo_changeno was for the variable */ @@ -1659,8 +1676,7 @@ splitundo(char **args) { if (vistartchange >= 0) { mergeundo(); - vistartchange = (curchange && curchange->prev) ? - curchange->prev->changeno : 0; + vistartchange = undo_changeno; } handleundo(); return 0; @@ -1706,7 +1722,8 @@ zlecallhook(char *name, char *arg) execzlefunc(thingy, args, 1); unrefthingy(thingy); - errflag = saverrflag; + /* Retain any user interrupt error status */ + errflag = saverrflag | (errflag & ERRFLAG_INT); retflag = savretflag; } diff --git a/Src/Zle/zle_vi.c b/Src/Zle/zle_vi.c index 9e39143d0..1a11ca7d5 100644 --- a/Src/Zle/zle_vi.c +++ b/Src/Zle/zle_vi.c @@ -75,7 +75,7 @@ static int inrepeat, vichgrepeat; */ /**/ -static void +void startvichange(int im) { if (im != -1) { @@ -109,7 +109,7 @@ startvitext(int im) { startvichange(im); selectkeymap("main", 1); - vistartchange = (curchange && curchange->prev) ? curchange->prev->changeno : 0; + vistartchange = undo_changeno; viinsbegin = zlecs; } @@ -161,75 +161,87 @@ vigetkey(void) static int getvirange(int wf) { - int pos = zlecs, ret = 0; + int pos = zlecs, mpos = mark, ret = 0; + int visual = region_active; /* movement command might set it */ int mult1 = zmult, hist1 = histline; Thingy k2; - virangeflag = 1; - wordflag = wf; - /* Now we need to execute the movement command, to see where it * - * actually goes. virangeflag here indicates to the movement * - * function that it should place the cursor at the end of the * - * range, rather than where the cursor would actually go if it * - * were executed normally. This makes a difference to some * - * commands, but not all. For example, if searching forward * - * for a character, under normal circumstances the cursor lands * - * on the character. For a range, the range must include the * - * character, so the cursor gets placed after the character if * - * virangeflag is set. vi-match-bracket needs to change the * - * value of virangeflag under some circumstances, meaning that * - * we need to change the *starting* position. */ - zmod.flags &= ~MOD_TMULT; - do { - vilinerange = 0; - prefixflag = 0; - if (!(k2 = getkeycmd()) || (k2->flags & DISABLED) || - k2 == Th(z_sendbreak)) { - wordflag = 0; - virangeflag = 0; + if (visual) { + if (!zlell) + return -1; + pos = mark; + vilinerange = (visual == 2); + region_active = 0; + } else { + virangeflag = 1; + wordflag = wf; + mark = -1; + /* use operator-pending keymap if one exists */ + Keymap km = openkeymap("viopp"); + if (km) + selectlocalmap(km); + /* Now we need to execute the movement command, to see where it * + * actually goes. virangeflag here indicates to the movement * + * function that it should place the cursor at the end of the * + * range, rather than where the cursor would actually go if it * + * were executed normally. This makes a difference to some * + * commands, but not all. For example, if searching forward * + * for a character, under normal circumstances the cursor lands * + * on the character. For a range, the range must include the * + * character, so the cursor gets placed after the character if * + * virangeflag is set. */ + zmod.flags &= ~MOD_TMULT; + do { + vilinerange = 0; + prefixflag = 0; + if (!(k2 = getkeycmd()) || (k2->flags & DISABLED) || + k2 == Th(z_sendbreak)) { + wordflag = 0; + virangeflag = 0; + mark = mpos; + return -1; + } + /* + * With k2 == bindk, the command key is repeated: + * a number of lines is used. If the function used + * returns 1, we fail. + */ + if ((k2 == bindk) ? dovilinerange() : execzlefunc(k2, zlenoargs, 1)) + ret = -1; + if(vichgrepeat) + zmult = mult1; + else + zmult = mult1 * zmod.tmult; + } while(prefixflag && !ret); + wordflag = 0; + selectlocalmap(NULL); + + /* It is an error to use a non-movement command to delimit the * + * range. We here reject the case where the command modified * + * the line, or selected a different history line. */ + if (histline != hist1 || zlell != lastll || memcmp(zleline, lastline, zlell)) { + histline = hist1; + ZS_memcpy(zleline, lastline, zlell = lastll); + zlecs = pos; + mark = mpos; return -1; } - /* - * With k2 == bindk, the command key is repeated: - * a number of lines is used. If the function used - * returns 1, we fail. - */ - if ((k2 == bindk) ? dovilinerange() : execzlefunc(k2, zlenoargs, 1)) - ret = -1; - if(vichgrepeat) - zmult = mult1; - else - zmult = mult1 * zmod.tmult; - } while(prefixflag && !ret); - wordflag = 0; - virangeflag = 0; - - /* It is an error to use a non-movement command to delimit the * - * range. We here reject the case where the command modified * - * the line, or selected a different history line. */ - if (histline != hist1 || zlell != lastll || memcmp(zleline, lastline, zlell)) { - histline = hist1; - ZS_memcpy(zleline, lastline, zlell = lastll); - zlecs = pos; - return -1; - } - /* Can't handle an empty file. Also, if the movement command * - * failed, or didn't move, it is an error. */ - if (!zlell || (zlecs == pos && virangeflag != 2) || ret == -1) - return -1; + /* Can't handle an empty file. Also, if the movement command * + * failed, or didn't move, it is an error. */ + if (!zlell || (zlecs == pos && (mark == -1 || mark == zlecs) && + virangeflag != 2) || ret == -1) { + mark = mpos; + return -1; + } + virangeflag = 0; - /* vi-match-bracket changes the value of virangeflag when * - * moving to the opening bracket, meaning that we need to * - * change the *starting* position. */ - if(virangeflag == -1) - { - int origcs = zlecs; - zlecs = pos; - INCCS(); - pos = zlecs; - zlecs = origcs; + /* if the mark has moved, ignore the original cursor position * + * and use the mark. */ + if (mark != -1) + pos = mark; } + mark = mpos; /* Get the range the right way round. zlecs is placed at the * * start of the range, and pos (the return value of this * @@ -240,17 +252,31 @@ getvirange(int wf) pos = tmp; } + /* visual selection mode needs to include additional position */ + if (visual == 1 && invicmdmode()) + INCPOS(pos); + /* Was it a line-oriented move? If so, the command will have set * * the vilinerange flag. In this case, entire lines are taken, * * rather than just the sequence of characters delimited by pos * - * and zlecs. The terminating newline is left out of the range, * + * and zlecs. The terminating newline is left out of the range, * * which the real command must deal with appropriately. At this * * point we just need to make the range encompass entire lines. */ - if(vilinerange) { + vilinerange = (zmod.flags & MOD_LINE) || + (vilinerange && !(zmod.flags & MOD_CHAR)); + if (vilinerange) { int newcs = findbol(); + lastcol = zlecs - newcs; zlecs = pos; pos = findeol(); zlecs = newcs; + } else if (!visual) { + /* for a character-wise move don't include a newline at the * + * end of the range */ + int prev = pos; + DECPOS(prev); + if (zleline[prev] == ZWC('\n')) + pos = prev; } return pos; } @@ -348,6 +374,7 @@ videlete(UNUSED(char **args)) forekill(c2 - zlecs, CUT_RAW); ret = 0; if (vilinerange && zlell) { + lastcol = -1; if (zlecs == zlell) DECCS(); foredel(1, 0); @@ -365,6 +392,7 @@ videletechar(char **args) int n = zmult; startvichange(-1); + /* handle negative argument */ if (n < 0) { int ret; @@ -401,7 +429,7 @@ vichange(UNUSED(char **args)) forekill(c2 - zlecs, CUT_RAW); selectkeymap("main", 1); viinsbegin = zlecs; - vistartchange = (curchange && curchange->prev) ? curchange->prev->changeno : 0; + vistartchange = undo_changeno; } return ret; } @@ -418,12 +446,16 @@ visubstitute(UNUSED(char **args)) /* it is an error to be on the end of line */ if (zlecs == zlell || zleline[zlecs] == '\n') return 1; - /* Put argument into the acceptable range -- it is not an error to * - * specify a greater count than the number of available characters. */ - if (n > findeol() - zlecs) - n = findeol() - zlecs; - /* do the substitution */ - forekill(n, CUT_RAW); + if (region_active) { + killregion(zlenoargs); + } else { + /* Put argument into the acceptable range -- it is not an error to * + * specify a greater count than the number of available characters. */ + if (n > findeol() - zlecs) + n = findeol() - zlecs; + /* do the substitution */ + forekill(n, CUT_RAW); + } startvitext(1); return 0; } @@ -432,7 +464,15 @@ visubstitute(UNUSED(char **args)) int vichangeeol(UNUSED(char **args)) { - forekill(findeol() - zlecs, CUT_RAW); + int a, b; + if (region_active) { + regionlines(&a, &b); + zlecs = a; + region_active = 0; + cut(zlecs, b - zlecs, CUT_RAW); + shiftchars(zlecs, b - zlecs); + } else + forekill(findeol() - zlecs, CUT_RAW); startvitext(1); return 0; } @@ -449,15 +489,30 @@ vichangewholeline(char **args) int viyank(UNUSED(char **args)) { - int oldcs = zlecs, c2, ret = 1; + int c2, ret = 1; startvichange(1); if ((c2 = getvirange(0)) != -1) { - cut(zlecs, c2 - zlecs, 0); + cut(zlecs, c2 - zlecs, CUT_YANK); ret = 0; } vichgflag = 0; - zlecs = oldcs; + /* cursor now at the start of the range yanked. For line mode + * restore the column position */ + if (vilinerange && lastcol != -1) { + int x = findeol(); + + if ((zlecs += lastcol) >= x) { + zlecs = x; + if (zlecs > findbol() && invicmdmode()) + DECCS(); + } +#ifdef MULTIBYTE_SUPPORT + else + CCRIGHT(); +#endif + lastcol = -1; + } return ret; } @@ -470,7 +525,7 @@ viyankeol(UNUSED(char **args)) startvichange(-1); if (x == zlecs) return 1; - cut(zlecs, x - zlecs, 0); + cut(zlecs, x - zlecs, CUT_YANK); return 0; } @@ -492,7 +547,7 @@ viyankwholeline(UNUSED(char **args)) zlecs = findeol() + 1; } vilinerange = 1; - cut(bol, zlecs - bol - 1, 0); + cut(bol, zlecs - bol - 1, CUT_YANK); zlecs = oldcs; return 0; } @@ -526,16 +581,40 @@ vireplacechars(UNUSED(char **args)) int n = zmult, fail = 0, newchars = 0; if (n > 0) { - int pos = zlecs; - while (n-- > 0) { - if (pos == zlell || zleline[pos] == ZWC('\n')) { - fail = 1; - break; + if (region_active) { + int a, b; + if (region_active == 1) { + if (mark > zlecs) { + a = zlecs; + b = mark; + } else { + a = mark; + b = zlecs; + } + INCPOS(b); + } else + regionlines(&a, &b); + zlecs = a; + if (b > zlell) + b = zlell; + n = b - a; + while (a < b) { + newchars++; + INCPOS(a); + } + region_active = 0; + } else { + int pos = zlecs; + while (n-- > 0) { + if (pos == zlell || zleline[pos] == ZWC('\n')) { + fail = 1; + break; + } + newchars++; + INCPOS(pos); } - newchars++; - INCPOS(pos); + n = pos - zlecs; } - n = pos - zlecs; } startvichange(1); /* check argument range */ @@ -567,6 +646,8 @@ vireplacechars(UNUSED(char **args)) * buffer offset. * Use shiftchars so as not to adjust the cursor position; * we are overwriting anything that remains directly. + * With a selection this will replace newlines which vim + * doesn't do but this simplifies things a lot. */ if (n > newchars) shiftchars(zlecs, n - newchars); @@ -675,8 +756,11 @@ viindent(UNUSED(char **args)) { int oldcs = zlecs, c2; - /* get the range */ startvichange(1); + /* force line range */ + if (region_active == 1) + region_active = 2; + /* get the range */ if ((c2 = getvirange(0)) == -1) { vichgflag = 0; return 1; @@ -689,10 +773,14 @@ viindent(UNUSED(char **args)) } oldcs = zlecs; /* add a tab to the beginning of each line within range */ - while (zlecs < c2) { - spaceinline(1); - zleline[zlecs] = '\t'; - zlecs = findeol() + 1; + while (zlecs <= c2 + 1) { + if (zleline[zlecs] == '\n') { /* leave blank lines alone */ + ++zlecs; + } else { + spaceinline(1); + zleline[zlecs] = '\t'; + zlecs = findeol() + 1; + } } /* go back to the first line of the range */ zlecs = oldcs; @@ -706,8 +794,11 @@ viunindent(UNUSED(char **args)) { int oldcs = zlecs, c2; - /* get the range */ startvichange(1); + /* force line range */ + if (region_active == 1) + region_active = 2; + /* get the range */ if ((c2 = getvirange(0)) == -1) { vichgflag = 0; return 1; @@ -739,6 +830,7 @@ vibackwarddeletechar(char **args) if (invicmdmode()) startvichange(-1); + /* handle negative argument */ if (n < 0) { int ret; @@ -776,95 +868,44 @@ vikillline(UNUSED(char **args)) /**/ int -viputbefore(UNUSED(char **args)) +vijoin(UNUSED(char **args)) { - Cutbuffer buf = &cutbuf; + int x, pos; int n = zmult; + int visual = region_active; startvichange(-1); - if (n < 0) - return 1; - if (zmod.flags & MOD_VIBUF) - buf = &vibuf[zmod.vibuf]; - if (!buf->buf) + if (n < 1) return 1; - if(buf->flags & CUTBUFFER_LINE) { - zlecs = findbol(); - spaceinline(buf->len + 1); - ZS_memcpy(zleline + zlecs, buf->buf, buf->len); - zleline[zlecs + buf->len] = ZWC('\n'); - vifirstnonblank(zlenoargs); - } else { - while (n--) { - spaceinline(buf->len); - ZS_memcpy(zleline + zlecs, buf->buf, buf->len); - zlecs += buf->len; + if (visual && zlecs > mark) { + exchangepointandmark(zlenoargs); + x = findeol(); + if (x >= mark) { + exchangepointandmark(zlenoargs); + return 1; } - if (zlecs) - DECCS(); - } - return 0; -} - -/**/ -int -viputafter(UNUSED(char **args)) -{ - Cutbuffer buf = &cutbuf; - int n = zmult; - - startvichange(-1); - if (n < 0) - return 1; - if (zmod.flags & MOD_VIBUF) - buf = &vibuf[zmod.vibuf]; - if (!buf->buf) + } else if ((x = findeol()) == zlell || (visual && x >= mark)) return 1; - if(buf->flags & CUTBUFFER_LINE) { - zlecs = findeol(); - spaceinline(buf->len + 1); - zleline[zlecs++] = ZWC('\n'); - ZS_memcpy(zleline + zlecs, buf->buf, buf->len); - vifirstnonblank(zlenoargs); - } else { - if (zlecs != findeol()) - INCCS(); - while (n--) { - spaceinline(buf->len); - ZS_memcpy(zleline + zlecs, buf->buf, buf->len); - zlecs += buf->len; - } - if (zlecs) - DECCS(); - } - return 0; -} - -/**/ -int -vijoin(UNUSED(char **args)) -{ - int x, pos; - startvichange(-1); - if ((x = findeol()) == zlell) - return 1; - zlecs = x + 1; - pos = zlecs; - for (; zlecs != zlell && ZC_iblank(zleline[zlecs]); INCPOS(zlecs)) - ; - x = 1 + (zlecs - pos); - backdel(x, CUT_RAW); - if (zlecs) { - int pos = zlecs; - DECPOS(pos); - if (ZC_iblank(zleline[pos])) { - zlecs = pos; - return 0; + do { + zlecs = x + 1; + pos = zlecs; + for (; zlecs != zlell && ZC_iblank(zleline[zlecs]); INCPOS(zlecs)) + ; + x = 1 + (zlecs - pos); + backdel(x, CUT_RAW); + if (zlecs) { + int pos = zlecs; + DECPOS(pos); + if (ZC_iblank(zleline[pos])) { + zlecs = pos; + continue; + } } - } - spaceinline(1); - zleline[zlecs] = ZWC(' '); + spaceinline(1); + zleline[zlecs] = ZWC(' '); + } while (!((!visual && --n < 2) || (x = findeol()) == zlell || (visual && x >= mark))); + return 0; } @@ -878,6 +919,8 @@ viswapcase(UNUSED(char **args)) if (n < 1) return 1; eol = findeol(); + if (zlecs == eol) + return 1; while (zlecs < eol && n--) { if (ZC_ilower(zleline[zlecs])) zleline[zlecs] = ZC_toupper(zleline[zlecs]); @@ -905,14 +948,26 @@ vicapslockpanic(UNUSED(char **args)) /**/ int -visetbuffer(UNUSED(char **args)) +visetbuffer(char **args) { ZLE_INT_T ch; - if ((zmod.flags & MOD_VIBUF) || - (((ch = getfullchar(0)) < ZWC('1') || ch > ZWC('9')) && + if (*args) { + ch = **args; + if (args[1] || (ch && (*args)[1])) + return 1; + } else { + ch = getfullchar(0); + } + if (ch == ZWC('_')) { + zmod.flags |= MOD_NULL; + prefixflag = 1; + return 0; + } else + zmod.flags &= ~MOD_NULL; + if ((ch < ZWC('0') || ch > ZWC('9')) && (ch < ZWC('a') || ch > ZWC('z')) && - (ch < ZWC('A') || ch > ZWC('Z')))) + (ch < ZWC('A') || ch > ZWC('Z'))) return 1; if (ch >= ZWC('A') && ch <= ZWC('Z')) /* needed in cut() */ zmod.flags |= MOD_VIAPP; @@ -920,8 +975,8 @@ visetbuffer(UNUSED(char **args)) zmod.flags &= ~MOD_VIAPP; /* FIXME how portable is it for multibyte encoding? */ zmod.vibuf = ZC_tolower(ch); - if (ch >= ZWC('1') && ch <= ZWC('9')) - zmod.vibuf += - (int)ZWC('1') + 26; + if (ch >= ZWC('0') && ch <= ZWC('9')) + zmod.vibuf += - (int)ZWC('0') + 26; else zmod.vibuf += - (int)ZWC('a'); zmod.flags |= MOD_VIBUF; diff --git a/Src/Zle/zle_word.c b/Src/Zle/zle_word.c index e59304ecd..2e6d75e86 100644 --- a/Src/Zle/zle_word.c +++ b/Src/Zle/zle_word.c @@ -80,16 +80,21 @@ viforwardword(char **args) return ret; } while (n--) { + int nl; if (Z_vialnum(zleline[zlecs])) while (zlecs != zlell && Z_vialnum(zleline[zlecs])) INCCS(); else - while (zlecs != zlell && !Z_vialnum(zleline[zlecs]) && !ZC_iblank(zleline[zlecs])) + while (zlecs != zlell && !Z_vialnum(zleline[zlecs]) && + !ZC_inblank(zleline[zlecs])) INCCS(); if (wordflag && !n) return 0; - while (zlecs != zlell && ZC_inblank(zleline[zlecs])) + nl = (zleline[zlecs] == ZWC('\n')); + while (zlecs != zlell && nl < 2 && ZC_inblank(zleline[zlecs])) { INCCS(); + nl += (zleline[zlecs] == ZWC('\n')); + } } return 0; } @@ -108,12 +113,16 @@ viforwardblankword(char **args) return ret; } while (n--) { - while (zlecs != zlell && !ZC_iblank(zleline[zlecs])) + int nl; + while (zlecs != zlell && !ZC_inblank(zleline[zlecs])) INCCS(); if (wordflag && !n) return 0; - while (zlecs != zlell && ZC_iblank(zleline[zlecs])) + nl = (zleline[zlecs] == ZWC('\n')); + while (zlecs != zlell && nl < 2 && ZC_inblank(zleline[zlecs])) { INCCS(); + nl += (zleline[zlecs] == ZWC('\n')); + } } return 0; } @@ -148,20 +157,25 @@ viforwardblankwordend(UNUSED(char **args)) { int n = zmult; - if (n < 0) - return 1; + if (n < 0) { + int ret; + zmult = -n; + ret = viforwardblankwordend(args); + zmult = n; + return ret; + } while (n--) { while (zlecs != zlell) { int pos = zlecs; INCPOS(pos); - if (!ZC_iblank(zleline[pos])) + if (!ZC_inblank(zleline[pos])) break; zlecs = pos; } while (zlecs != zlell) { int pos = zlecs; INCPOS(pos); - if (ZC_iblank(zleline[pos])) + if (ZC_inblank(zleline[pos])) break; zlecs = pos; } @@ -180,7 +194,7 @@ viforwardwordend(char **args) if (n < 0) { int ret; zmult = -n; - ret = backwardword(args); + ret = vibackwardwordend(args); zmult = n; return ret; } @@ -211,7 +225,7 @@ viforwardwordend(char **args) if (zlecs == zlell) break; INCPOS(pos); - if (Z_vialnum(zleline[pos]) || ZC_iblank(zleline[pos])) + if (Z_vialnum(zleline[pos]) || ZC_inblank(zleline[pos])) break; } } @@ -268,16 +282,19 @@ vibackwardword(char **args) return ret; } while (n--) { + int nl = 0; while (zlecs) { - int pos = zlecs; - DECPOS(pos); - if (!ZC_iblank(zleline[pos])) + DECCS(); + if (!ZC_inblank(zleline[zlecs])) break; - zlecs = pos; + nl += (zleline[zlecs] == ZWC('\n')); + if (nl == 2) { + INCCS(); + break; + } } if (zlecs) { int pos = zlecs; - DECPOS(pos); if (Z_vialnum(zleline[pos])) { for (;;) { zlecs = pos; @@ -293,7 +310,7 @@ vibackwardword(char **args) if (zlecs == 0) break; DECPOS(pos); - if (Z_vialnum(zleline[pos]) || ZC_iblank(zleline[pos])) + if (Z_vialnum(zleline[pos]) || ZC_inblank(zleline[pos])) break; } } @@ -316,17 +333,20 @@ vibackwardblankword(char **args) return ret; } while (n--) { + int nl = 0; while (zlecs) { int pos = zlecs; DECPOS(pos); - if (!ZC_iblank(zleline[pos])) + if (!ZC_inblank(zleline[pos])) break; + nl += (zleline[pos] == ZWC('\n')); + if (nl == 2) break; zlecs = pos; } while (zlecs) { int pos = zlecs; DECPOS(pos); - if (ZC_iblank(zleline[pos])) + if (ZC_inblank(zleline[pos])) break; zlecs = pos; } @@ -336,6 +356,62 @@ vibackwardblankword(char **args) /**/ int +vibackwardwordend(char **args) +{ + int n = zmult; + + if (n < 0) { + int ret; + zmult = -n; + ret = viforwardwordend(args); + zmult = n; + return ret; + } + while (n-- && zlecs > 1) { + int start = 0; + if (Z_vialnum(zleline[zlecs])) + start = 1; + else if (!ZC_inblank(zleline[zlecs])) + start = 2; + DECCS(); + while (zlecs) { + int same = (start != 1) && ZC_iblank(zleline[zlecs]); + if (start) + same |= Z_vialnum(zleline[zlecs]); + if (same == (start == 2)) + break; + DECCS(); + } + while (zlecs && ZC_iblank(zleline[zlecs])) + DECCS(); + } + return 0; +} + +/**/ +int +vibackwardblankwordend(char **args) +{ + int n = zmult; + + if (n < 0) { + int ret; + zmult = -n; + ret = viforwardblankwordend(args); + zmult = n; + return ret; + } + while (n--) { + while (zlecs && !ZC_inblank(zleline[zlecs])) + DECCS(); + while (zlecs && ZC_inblank(zleline[zlecs])) + DECCS(); + } + return 0; +} + +/**/ +int emacsbackwardword(char **args) { int n = zmult; @@ -629,7 +705,6 @@ transposewords(UNUSED(char **args)) DECPOS(pos); if (zleline[pos] == ZWC('\n')) return 1; - x = pos; } for (p4 = x; p4 != zlell && ZC_iword(zleline[p4]); INCPOS(p4)) ; diff --git a/Src/builtin.c b/Src/builtin.c index 4a10c7dd1..ffde5c916 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -119,7 +119,7 @@ static struct builtin builtins[] = BUILTIN("times", BINF_PSPECIAL, bin_times, 0, 0, 0, NULL, NULL), BUILTIN("trap", BINF_PSPECIAL | BINF_HANDLES_OPTS, bin_trap, 0, -1, 0, NULL, NULL), BUILTIN("true", 0, bin_true, 0, -1, 0, NULL, NULL), - BUILTIN("type", 0, bin_whence, 0, -1, 0, "ampfsw", "v"), + BUILTIN("type", 0, bin_whence, 0, -1, 0, "ampfsSw", "v"), BUILTIN("typeset", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%afghi:%klprtuxmz", NULL), BUILTIN("umask", 0, bin_umask, 0, 1, 0, "S", NULL), BUILTIN("unalias", 0, bin_unhash, 1, -1, 0, "ms", "a"), @@ -128,9 +128,9 @@ static struct builtin builtins[] = BUILTIN("unset", BINF_PSPECIAL, bin_unset, 1, -1, 0, "fmv", NULL), BUILTIN("unsetopt", 0, bin_setopt, 0, -1, BIN_UNSETOPT, NULL, NULL), BUILTIN("wait", 0, bin_fg, 0, -1, BIN_WAIT, NULL, NULL), - BUILTIN("whence", 0, bin_whence, 0, -1, 0, "acmpvfsw", NULL), - BUILTIN("where", 0, bin_whence, 0, -1, 0, "pmsw", "ca"), - BUILTIN("which", 0, bin_whence, 0, -1, 0, "ampsw", "c"), + BUILTIN("whence", 0, bin_whence, 0, -1, 0, "acmpvfsSw", NULL), + BUILTIN("where", 0, bin_whence, 0, -1, 0, "pmsSw", "ca"), + BUILTIN("which", 0, bin_whence, 0, -1, 0, "ampsSw", "c"), BUILTIN("zmodload", 0, bin_zmodload, 0, -1, 0, "AFRILP:abcfdilmpue", NULL), BUILTIN("zcompile", 0, bin_zcompile, 0, -1, 0, "tUMRcmzka", NULL), }; @@ -422,7 +422,7 @@ execbuiltin(LinkList args, Builtin bn) argc -= argv - argarr; if (errflag) { - errflag = 0; + errflag &= ~ERRFLAG_ERROR; return 1; } @@ -719,7 +719,7 @@ bin_dirs(UNUSED(char *name), char **argv, Options ops, UNUSED(int func)) for (node = firstnode(dirstack); node; incnode(node)) { printf(fmt, pos++); if (OPT_ISSET(ops,'l')) - fputs(getdata(node), stdout); + zputs(getdata(node), stdout); else fprintdir(getdata(node), stdout); @@ -1093,9 +1093,11 @@ cd_try_chdir(char *pfix, char *dest, int hard) } else { int pfl = strlen(pfix); dlen = strlen(pwd); - + if (dlen == 1 && *pwd == '/') + dlen = 0; buf = zalloc(dlen + pfl + strlen(dest) + 3); - strcpy(buf, pwd); + if (dlen) + strcpy(buf, pwd); buf[dlen] = '/'; strcpy(buf + dlen + 1, pfix); buf[dlen + 1 + pfl] = '/'; @@ -1156,9 +1158,11 @@ cd_new_pwd(int func, LinkNode dir, int quiet) zsfree(getlinknode(dirstack)); if (chasinglinks) { - s = new_pwd; - new_pwd = findpwd(s); - zsfree(s); + s = findpwd(new_pwd); + if (s) { + zsfree(new_pwd); + new_pwd = s; + } } if (isset(PUSHDIGNOREDUPS)) { LinkNode n; @@ -1363,10 +1367,19 @@ bin_fc(char *nam, char **argv, Options ops, int func) if (*argv) { hf = *argv++; if (*argv) { - hs = zstrtol(*argv++, NULL, 10); - if (*argv) - shs = zstrtol(*argv++, NULL, 10); - else + char *check; + hs = zstrtol(*argv++, &check, 10); + if (*check) { + zwarnnam("fc", "HISTSIZE must be an integer"); + return 1; + } + if (*argv) { + shs = zstrtol(*argv++, &check, 10); + if (*check) { + zwarnnam("fc", "SAVEHIST must be an integer"); + return 1; + } + } else shs = hs; if (*argv) { zwarnnam("fc", "too many arguments"); @@ -1855,7 +1868,7 @@ typeset_setbase(const char *name, Param pm, Options ops, int on, int always) if (arg) { char *eptr; - pm->base = (int)zstrtol(arg, &eptr, 10); + int base = (int)zstrtol(arg, &eptr, 10); if (*eptr) { if (on & PM_INTEGER) zwarnnam(name, "bad base value: %s", arg); @@ -1863,11 +1876,12 @@ typeset_setbase(const char *name, Param pm, Options ops, int on, int always) zwarnnam(name, "bad precision value: %s", arg); return 1; } - if (pm->base < 2 || pm->base > 36) { + if ((on & PM_INTEGER) && (base < 2 || base > 36)) { zwarnnam(name, "invalid base (must be 2 to 36 inclusive): %d", - pm->base); + base); return 1; } + pm->base = base; } else if (always) pm->base = 0; @@ -1918,15 +1932,19 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func), * locallevel as an unset one we use the pm struct anyway: that's * handled in createparam(). Here we just avoid using it for the * present tests if it's unset. + * + * POSIXBUILTINS horror: we need to retain the 'readonly' flag + * of an unset parameter. */ - usepm = pm && !(pm->node.flags & PM_UNSET); + usepm = pm && (!(pm->node.flags & PM_UNSET) || + (isset(POSIXBUILTINS) && (pm->node.flags & PM_READONLY))); /* * We need to compare types with an existing pm if special, * even if that's unset */ - if (pm && (pm->node.flags & PM_SPECIAL)) - usepm = 1; + if (!usepm && pm && (pm->node.flags & PM_SPECIAL)) + usepm = 2; /* indicate that we preserve the PM_UNSET flag */ /* * Don't use an existing param if @@ -2019,6 +2037,20 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func), else if (newspecial != NS_NONE && strcmp(pname, "SECONDS") == 0) newspecial = NS_SECONDS; + if (isset(POSIXBUILTINS)) { + /* + * Stricter rules about retaining readonly attribute in this case. + */ + if ((on & PM_READONLY) && (!usepm || (pm->node.flags & PM_UNSET)) && + !value) + on |= PM_UNSET; + else if (usepm && (pm->node.flags & PM_READONLY) && + !(on & PM_READONLY)) { + zerr("read-only variable: %s", pm->node.nam); + return NULL; + } + } + /* * A parameter will be local if * 1. we are re-using an existing local parameter @@ -2063,7 +2095,17 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func), arrfixenv(pm->node.nam, x); } } - pm->node.flags = (pm->node.flags | (on & ~PM_READONLY)) & ~(off | PM_UNSET); + if (usepm == 2) /* do not change the PM_UNSET flag */ + pm->node.flags = (pm->node.flags | (on & ~PM_READONLY)) & ~off; + else { + /* + * Keep unset if using readonly in POSIX mode. + */ + if (!(on & PM_READONLY) || !isset(POSIXBUILTINS)) + off |= PM_UNSET; + pm->node.flags = (pm->node.flags | + (on & ~PM_READONLY)) & ~off; + } if (on & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) { if (typeset_setwidth(cname, pm, ops, on, 0)) return NULL; @@ -2239,7 +2281,12 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func), * readonly flag */ pm = createparam(pname, on & ~PM_READONLY); - DPUTS(!pm, "BUG: parameter not created"); + if (!pm) { + if (on & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z | + PM_INTEGER | PM_EFLOAT | PM_FFLOAT)) + zerrnam(cname, "can't change variable attribute: %s", pname); + return NULL; + } if (on & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) { if (typeset_setwidth(cname, pm, ops, on, 0)) return NULL; @@ -2501,6 +2548,8 @@ bin_typeset(char *name, char **argv, Options ops, int func) asg->name), func, (on | PM_ARRAY) & ~PM_EXPORTED, off, roff, asg->value, NULL, ops, 0))) { + if (oldval) + zsfree(oldval); unqueue_signals(); return 1; } @@ -3127,7 +3176,7 @@ bin_unset(char *name, char **argv, Options ops, int func) } } returnval = errflag; - errflag = 0; + errflag &= ~ERRFLAG_ERROR; } else { zerrnam(name, "%s: invalid element for unset", s); returnval = 1; @@ -3145,6 +3194,15 @@ bin_unset(char *name, char **argv, Options ops, int func) /* type, whence, which, command */ +static LinkList matchednodes; + +static void +fetchcmdnamnode(HashNode hn, UNUSED(int printflags)) +{ + Cmdnam cn = (Cmdnam) hn; + addlinknode(matchednodes, cn->node.nam); +} + /**/ int bin_whence(char *nam, char **argv, Options ops, int func) @@ -3155,8 +3213,8 @@ bin_whence(char *nam, char **argv, Options ops, int func) int printflags = 0; int aliasflags; int csh, all, v, wd; - int informed; - char *cnam; + int informed = 0; + char *cnam, **allmatched = 0; /* Check some option information */ csh = OPT_ISSET(ops,'c'); @@ -3189,6 +3247,11 @@ bin_whence(char *nam, char **argv, Options ops, int func) /* With -m option -- treat arguments as a glob patterns */ if (OPT_ISSET(ops,'m')) { + cmdnamtab->filltable(cmdnamtab); + if (all) { + pushheap(); + matchednodes = newlinklist(); + } for (; *argv; argv++) { /* parse the pattern */ tokenize(*argv); @@ -3204,97 +3267,105 @@ bin_whence(char *nam, char **argv, Options ops, int func) * We're not using it, so search for ... */ /* aliases ... */ + informed += scanmatchtable(aliastab, pprog, 1, 0, DISABLED, aliastab->printnode, printflags); /* and reserved words ... */ + informed += scanmatchtable(reswdtab, pprog, 1, 0, DISABLED, reswdtab->printnode, printflags); /* and shell functions... */ + informed += scanmatchtable(shfunctab, pprog, 1, 0, DISABLED, shfunctab->printnode, printflags); /* and builtins. */ + informed += scanmatchtable(builtintab, pprog, 1, 0, DISABLED, builtintab->printnode, printflags); } /* Done search for `internal' commands, if the -p option * * was not used. Now search the path. */ - cmdnamtab->filltable(cmdnamtab); + informed += scanmatchtable(cmdnamtab, pprog, 1, 0, 0, - cmdnamtab->printnode, printflags); + (all ? fetchcmdnamnode : cmdnamtab->printnode), + printflags); unqueue_signals(); } - return returnval; + if (all) { + allmatched = argv = zlinklist2array(matchednodes); + matchednodes = NULL; + popheap(); + } else + return returnval || !informed; } /* Take arguments literally -- do not glob */ queue_signals(); for (; *argv; argv++) { - informed = 0; - - if (!OPT_ISSET(ops,'p')) { + if (!OPT_ISSET(ops,'p') && !allmatched) { char *suf; /* Look for alias */ if ((hn = aliastab->getnode(aliastab, *argv))) { aliastab->printnode(hn, aliasflags); + informed = 1; if (!all) continue; - informed = 1; } /* Look for suffix alias */ if ((suf = strrchr(*argv, '.')) && suf[1] && suf > *argv && suf[-1] != Meta && (hn = sufaliastab->getnode(sufaliastab, suf+1))) { sufaliastab->printnode(hn, printflags); + informed = 1; if (!all) continue; - informed = 1; } /* Look for reserved word */ if ((hn = reswdtab->getnode(reswdtab, *argv))) { reswdtab->printnode(hn, printflags); + informed = 1; if (!all) continue; - informed = 1; } /* Look for shell function */ if ((hn = shfunctab->getnode(shfunctab, *argv))) { shfunctab->printnode(hn, printflags); + informed = 1; if (!all) continue; - informed = 1; } /* Look for builtin command */ if ((hn = builtintab->getnode(builtintab, *argv))) { builtintab->printnode(hn, printflags); + informed = 1; if (!all) continue; - informed = 1; } /* Look for commands that have been added to the * * cmdnamtab with the builtin `hash foo=bar'. */ if ((hn = cmdnamtab->getnode(cmdnamtab, *argv)) && (hn->flags & HASHED)) { cmdnamtab->printnode(hn, printflags); + informed = 1; if (!all) continue; - informed = 1; } } /* Option -a is to search the entire path, * * rather than just looking for one match. */ - if (all) { + if (all && **argv != '/') { char **pp, *buf; pushheap(); for (pp = path; *pp; pp++) { if (**pp) { buf = zhtricat(*pp, "/", *argv); - } else buf = ztrdup(*argv); + } else buf = dupstring(*argv); if (iscom(buf)) { if (wd) { @@ -3303,8 +3374,8 @@ bin_whence(char *nam, char **argv, Options ops, int func) if (v && !csh) zputs(*argv, stdout), fputs(" is ", stdout); zputs(buf, stdout); - if (OPT_ISSET(ops,'s')) - print_if_link(buf); + if (OPT_ISSET(ops,'s') || OPT_ISSET(ops, 'S')) + print_if_link(buf, OPT_ISSET(ops, 'S')); fputc('\n', stdout); } informed = 1; @@ -3324,10 +3395,11 @@ bin_whence(char *nam, char **argv, Options ops, int func) if (v && !csh) zputs(*argv, stdout), fputs(" is ", stdout); zputs(cnam, stdout); - if (OPT_ISSET(ops,'s')) - print_if_link(cnam); + if (OPT_ISSET(ops,'s') || OPT_ISSET(ops,'S')) + print_if_link(cnam, OPT_ISSET(ops,'S')); fputc('\n', stdout); } + informed = 1; } else { /* Not found at all. */ if (v || csh || wd) @@ -3335,8 +3407,11 @@ bin_whence(char *nam, char **argv, Options ops, int func) returnval = 1; } } + if (allmatched) + freearray(allmatched); + unqueue_signals(); - return returnval; + return returnval || !informed; } /**** command & named directory hash table builtins ****/ @@ -3689,9 +3764,9 @@ int bin_print(char *name, char **args, Options ops, int func) { int flen, width, prec, type, argc, n, narg, curlen = 0; - int nnl = 0, fmttrunc = 0, ret = 0, maxarg = 0; + int nnl = 0, fmttrunc = 0, ret = 0, maxarg = 0, nc = 0; int flags[5], *len; - char *start, *endptr, *c, *d, *flag, *buf, spec[13], *fmt = NULL; + char *start, *endptr, *c, *d, *flag, *buf = NULL, spec[13], *fmt = NULL; char **first, **argp, *curarg, *flagch = "0+- #", save = '\0', nullstr = '\0'; size_t rcount, count = 0; #ifdef HAVE_OPEN_MEMSTREAM @@ -3749,7 +3824,7 @@ bin_print(char *name, char **args, Options ops, int func) /* compute lengths, and interpret according to -P, -D, -e, etc. */ argc = arrlen(args); len = (int *) hcalloc(argc * sizeof(int)); - for(n = 0; n < argc; n++) { + for (n = 0; n < argc; n++) { /* first \ sequences */ if (fmt || (!OPT_ISSET(ops,'e') && @@ -3802,8 +3877,38 @@ bin_print(char *name, char **args, Options ops, int func) } } + /* -o and -O -- sort the arguments */ + if (OPT_ISSET(ops,'o') || OPT_ISSET(ops,'O')) { + int flags; + + if (fmt && !*args) + return 0; + flags = OPT_ISSET(ops,'i') ? SORTIT_IGNORING_CASE : 0; + if (OPT_ISSET(ops,'O')) + flags |= SORTIT_BACKWARDS; + strmetasort(args, flags, len); + } + + /* -C -- number of columns */ + if (!fmt && OPT_ISSET(ops,'C')) { + char *eptr, *argptr = OPT_ARG(ops,'C'); + nc = (int)zstrtol(argptr, &eptr, 10); + if (*eptr) { + zwarnnam(name, "number expected after -%c: %s", 'C', argptr); + return 1; + } + if (nc <= 0) { + zwarnnam(name, "invalid number of columns: %s", argptr); + return 1; + } + } + /* -u and -p -- output to other than standard output */ - if (OPT_HASARG(ops,'u') || OPT_ISSET(ops,'p')) { + if ((OPT_HASARG(ops,'u') || OPT_ISSET(ops,'p')) && + /* rule out conflicting options -- historical precedence */ + ((!fmt && (OPT_ISSET(ops,'c') || OPT_ISSET(ops,'C'))) || + !(OPT_ISSET(ops, 'z') || + OPT_ISSET(ops, 's') || OPT_ISSET(ops, 'S')))) { int fdarg, fd; if (OPT_ISSET(ops, 'p')) { @@ -3816,7 +3921,7 @@ bin_print(char *name, char **args, Options ops, int func) char *argptr = OPT_ARG(ops,'u'), *eptr; /* Handle undocumented feature that -up worked */ if (!strcmp(argptr, "p")) { - fdarg= coprocout; + fdarg = coprocout; if (fdarg < 0) { zwarnnam(name, "-p: no coprocess"); return 1; @@ -3842,24 +3947,9 @@ bin_print(char *name, char **args, Options ops, int func) } } - /* -o and -O -- sort the arguments */ - if (OPT_ISSET(ops,'o') || OPT_ISSET(ops,'O')) { - int flags; - - if (fmt && !*args) { - if (fout != stdout) - fclose(fout); - return 0; - } - flags = OPT_ISSET(ops,'i') ? SORTIT_IGNORING_CASE : 0; - if (OPT_ISSET(ops,'O')) - flags |= SORTIT_BACKWARDS; - strmetasort(args, flags, len); - } - /* -c -- output in columns */ if (!fmt && (OPT_ISSET(ops,'c') || OPT_ISSET(ops,'C'))) { - int l, nc, nr, sc, n, t, i; + int l, nr, sc, n, t, i; #ifdef MULTIBYTE_SUPPORT int *widths; @@ -3930,19 +4020,9 @@ bin_print(char *name, char **args, Options ops, int func) #endif if (OPT_ISSET(ops,'C')) { - char *eptr, *argptr = OPT_ARG(ops,'C'); - nc = (int)zstrtol(argptr, &eptr, 10); - if (*eptr) { - zwarnnam(name, "number expcted after -%c: %s", 'C', argptr); - return 1; - } - if (nc <= 0) { - zwarnnam(name, "invalid number of columns: %s", argptr); - return 1; - } /* * n: number of elements - * nc: number of columns + * nc: number of columns (above) * nr: number of rows */ n = arrlen(args); @@ -4168,6 +4248,10 @@ bin_print(char *name, char **args, Options ops, int func) narg); if (fout != stdout) fclose(fout); +#ifdef HAVE_OPEN_MEMSTREAM + if (buf) + free(buf); +#endif return 1; } else { if (narg > maxarg) maxarg = narg; @@ -4201,6 +4285,10 @@ bin_print(char *name, char **args, Options ops, int func) narg); if (fout != stdout) fclose(fout); +#ifdef HAVE_OPEN_MEMSTREAM + if (buf) + free(buf); +#endif return 1; } else { if (narg > maxarg) maxarg = narg; @@ -4211,7 +4299,7 @@ bin_print(char *name, char **args, Options ops, int func) if (*argp) { width = (int)mathevali(*argp++); if (errflag) { - errflag = 0; + errflag &= ~ERRFLAG_ERROR; ret = 1; } } @@ -4230,6 +4318,10 @@ bin_print(char *name, char **args, Options ops, int func) narg); if (fout != stdout) fclose(fout); +#ifdef HAVE_OPEN_MEMSTREAM + if (buf) + free(buf); +#endif return 1; } else { if (narg > maxarg) maxarg = narg; @@ -4241,7 +4333,7 @@ bin_print(char *name, char **args, Options ops, int func) if (*argp) { prec = (int)mathevali(*argp++); if (errflag) { - errflag = 0; + errflag &= ~ERRFLAG_ERROR; ret = 1; } } @@ -4385,6 +4477,10 @@ bin_print(char *name, char **args, Options ops, int func) (fflush(fout) != 0 && errno != EBADF)) { zwarnnam(name, "write error: %e", errno); } +#ifdef HAVE_OPEN_MEMSTREAM + if (buf) + free(buf); +#endif return 1; } @@ -4421,7 +4517,7 @@ bin_print(char *name, char **args, Options ops, int func) zlongval = (curarg) ? mathevali(curarg) : 0; if (errflag) { zlongval = 0; - errflag = 0; + errflag &= ~ERRFLAG_ERROR; ret = 1; } print_val(zlongval) @@ -4450,7 +4546,7 @@ bin_print(char *name, char **args, Options ops, int func) } else doubleval = 0; if (errflag) { doubleval = 0; - errflag = 0; + errflag &= ~ERRFLAG_ERROR; ret = 1; } print_val(doubleval) @@ -4463,7 +4559,7 @@ bin_print(char *name, char **args, Options ops, int func) zulongval = (curarg) ? mathevali(curarg) : 0; if (errflag) { zulongval = 0; - errflag = 0; + errflag &= ~ERRFLAG_ERROR; ret = 1; } print_val(zulongval) @@ -4481,7 +4577,8 @@ bin_print(char *name, char **args, Options ops, int func) if (OPT_ISSET(ops,'z') || OPT_ISSET(ops,'s')) { #ifdef HAVE_OPEN_MEMSTREAM putc(0, fout); - fflush(fout); + fclose(fout); + fout = NULL; #else rewind(fout); buf = (char *)zalloc(count + 1); @@ -4502,11 +4599,16 @@ bin_print(char *name, char **args, Options ops, int func) unqueue_signals(); } - /* Testing EBADF special-cases >&- redirections */ - if ((fout != stdout) ? (fclose(fout) != 0) : - (fflush(fout) != 0 && errno != EBADF)) { - zwarnnam(name, "write error: %e", errno); - ret = 1; +#ifdef HAVE_OPEN_MEMSTREAM + if (fout) +#endif + { + /* Testing EBADF special-cases >&- redirections */ + if ((fout != stdout) ? (fclose(fout) != 0) : + (fflush(fout) != 0 && errno != EBADF)) { + zwarnnam(name, "write error: %e", errno); + ret = 1; + } } return ret; } @@ -4840,7 +4942,7 @@ zexit(int val, int from_where) in_exit = -1; /* * We want to do all remaining processing regardless of preceding - * errors. + * errors, even user interrupts. */ errflag = 0; @@ -5043,7 +5145,7 @@ eval(char **argv) if (fpushed) funcstack = funcstack->prev; - errflag = 0; + errflag &= ~ERRFLAG_ERROR; scriptname = oscriptname; ineval = oineval; @@ -5140,7 +5242,7 @@ bin_emulate(UNUSED(char *nam), char **argv, Options ops, UNUSED(int func)) if (cmd) { if (opt_L) { zwarnnam("emulate", "option -L incompatible with -c"); - goto restore; + goto restore2; } *--argv = cmd; /* on stack, never free()d, see execbuiltin() */ } else @@ -5178,6 +5280,7 @@ bin_emulate(UNUSED(char *nam), char **argv, Options ops, UNUSED(int func)) } ret = eval(argv); sticky = save_sticky; +restore2: emulation = saveemulation; memcpy(opts, saveopts, sizeof(opts)); restorepatterndisables(savepatterns); @@ -6061,7 +6164,7 @@ bin_test(char *name, char **argv, UNUSED(Options ops), int func) } } - lexsave(); + zcontext_save(); testargs = argv; tok = NULLTOK; condlex = testlex; @@ -6070,17 +6173,17 @@ bin_test(char *name, char **argv, UNUSED(Options ops), int func) condlex = zshlex; if (errflag) { - errflag = 0; - lexrestore(); + errflag &= ~ERRFLAG_ERROR; + zcontext_restore(); return 1; } if (!prog || tok == LEXERR) { zwarnnam(name, tokstr ? "parse error" : "argument expected"); - lexrestore(); + zcontext_restore(); return 1; } - lexrestore(); + zcontext_restore(); if (*curtestarg) { zwarnnam(name, "too many arguments"); @@ -6247,7 +6350,7 @@ bin_let(UNUSED(char *name), char **argv, UNUSED(Options ops), UNUSED(int func)) while (*argv) val = matheval(*argv++); /* Errors in math evaluation in let are non-fatal. */ - errflag = 0; + errflag &= ~ERRFLAG_ERROR; /* should test for fabs(val.u.d) < epsilon? */ return (val.type == MN_INTEGER) ? val.u.l == 0 : val.u.d == 0.0; } diff --git a/Src/compat.c b/Src/compat.c index b0bcb6265..b3a8b063c 100644 --- a/Src/compat.c +++ b/Src/compat.c @@ -443,7 +443,7 @@ zgetcwd(void) ret = getcwd(cwdbuf, PATH_MAX); if (ret) ret = dupstring(ret); - free(cwdbuf); + zfree(cwdbuf, PATH_MAX); #endif /* GETCWD_CALLS_MALLOC */ } #endif /* HAVE_GETCWD */ @@ -951,3 +951,18 @@ int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n) /**/ #endif /* BROKEN_WCWIDTH && (__STDC_ISO_10646__ || __APPLE__) */ +/**/ +#if defined(__APPLE__) && defined(BROKEN_ISPRINT) + +/**/ +int +isprint_ascii(int c) +{ + if (!strcmp(nl_langinfo(CODESET), "UTF-8")) + return (c >= 0x20 && c <= 0x7e); + else + return isprint(c); +} + +/**/ +#endif /* __APPLE__ && BROKEN_ISPRINT */ diff --git a/Src/context.c b/Src/context.c new file mode 100644 index 000000000..1b8741f46 --- /dev/null +++ b/Src/context.c @@ -0,0 +1,116 @@ +/* + * context.c - context save and restore + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 2015 Peter Stephenson + * 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 Peter Stephenson 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 Peter Stephenson and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Peter Stephenson 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 Peter Stephenson and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ +/* + * This short file provides a home for the stack of saved contexts. + * The actions for saving and restoring are encapsulated within + * individual modules. + */ + +#include "zsh.mdh" +#include "context.pro" + +struct context_stack { + struct context_stack *next; + + struct hist_stack hist_stack; + struct lex_stack lex_stack; + struct parse_stack parse_stack; +}; + +static struct context_stack *cstack; + +/* save some or all of current context */ + +/**/ +mod_export void +zcontext_save_partial(int parts) +{ + struct context_stack *cs; + + cs = (struct context_stack *)malloc(sizeof(struct context_stack)); + + if (parts & ZCONTEXT_HIST) { + hist_context_save(&cs->hist_stack, !cstack); + } + if (parts & ZCONTEXT_LEX) { + lex_context_save(&cs->lex_stack, !cstack); + } + if (parts & ZCONTEXT_PARSE) { + parse_context_save(&cs->parse_stack, !cstack); + } + + cs->next = cstack; + cstack = cs; +} + +/* save context in full */ + +/**/ +mod_export void +zcontext_save(void) +{ + zcontext_save_partial(ZCONTEXT_HIST|ZCONTEXT_LEX|ZCONTEXT_PARSE); +} + +/* restore context or part thereof */ + +/**/ +mod_export void +zcontext_restore_partial(int parts) +{ + struct context_stack *cs = cstack; + + DPUTS(!cstack, "BUG: zcontext_restore() without zcontext_save()"); + + queue_signals(); + cstack = cstack->next; + + if (parts & ZCONTEXT_HIST) { + hist_context_restore(&cs->hist_stack, !cstack); + } + if (parts & ZCONTEXT_LEX) { + lex_context_restore(&cs->lex_stack, !cstack); + } + if (parts & ZCONTEXT_PARSE) { + parse_context_restore(&cs->parse_stack, !cstack); + } + + free(cs); + + unqueue_signals(); +} + +/* restore full context */ + +/**/ +mod_export void +zcontext_restore(void) +{ + zcontext_restore_partial(ZCONTEXT_HIST|ZCONTEXT_LEX|ZCONTEXT_PARSE); +} diff --git a/Src/exec.c b/Src/exec.c index d0fadd69a..6a8b35a36 100644 --- a/Src/exec.c +++ b/Src/exec.c @@ -59,7 +59,7 @@ mod_export int noerrs; /**/ int nohistsave; -/* error/break flag */ +/* error flag: bits from enum errflag_bits */ /**/ mod_export int errflag; @@ -217,7 +217,7 @@ parse_string(char *s, int reset_lineno) Eprog p; zlong oldlineno; - lexsave(); + zcontext_save(); inpush(s, INP_LINENO, NULL); strinbeg(0); oldlineno = lineno; @@ -229,7 +229,7 @@ parse_string(char *s, int reset_lineno) lastval = 1; strinend(); inpop(); - lexrestore(); + zcontext_restore(); return p; } @@ -250,6 +250,7 @@ zsetlimit(int limnum, char *nam) if (setrlimit(limnum, limits + limnum)) { if (nam) zwarnnam(nam, "setrlimit failed: %e", errno); + limits[limnum] = current_limits[limnum]; return -1; } current_limits[limnum] = limits[limnum]; @@ -1145,7 +1146,8 @@ execlist(Estate state, int dont_change_job, int exiting) Wordcode next; wordcode code; int ret, cj, csp, ltype; - int old_pline_level, old_list_pipe; + int old_pline_level, old_list_pipe, old_list_pipe_job; + char *old_list_pipe_text; zlong oldlineno; /* * ERREXIT only forces the shell to exit if the last command in a && @@ -1158,10 +1160,17 @@ execlist(Estate state, int dont_change_job, int exiting) cj = thisjob; old_pline_level = pline_level; old_list_pipe = list_pipe; + old_list_pipe_job = list_pipe_job; + if (*list_pipe_text) + old_list_pipe_text = ztrdup(list_pipe_text); + else + old_list_pipe_text = NULL; oldlineno = lineno; - if (sourcelevel && unset(SHINSTDIN)) - pline_level = list_pipe = 0; + if (sourcelevel && unset(SHINSTDIN)) { + pline_level = list_pipe = list_pipe_job = 0; + *list_pipe_text = '\0'; + } /* Loop over all sets of comands separated by newline, * * semi-colon or ampersand (`sublists'). */ @@ -1396,6 +1405,13 @@ sublist_done: } pline_level = old_pline_level; list_pipe = old_list_pipe; + list_pipe_job = old_list_pipe_job; + if (old_list_pipe_text) { + strcpy(list_pipe_text, old_list_pipe_text); + zsfree(old_list_pipe_text); + } else { + *list_pipe_text = '\0'; + } lineno = oldlineno; if (dont_change_job) thisjob = cj; @@ -1600,7 +1616,8 @@ execpline(Estate state, wordcode slcode, int how, int last1) (killpg(jobtab[list_pipe_job].gleader, 0) == -1 ? 2 : 1); list_pipe_pid = pid; list_pipe_start = bgtime; - nowait = errflag = 1; + nowait = 1; + errflag |= ERRFLAG_ERROR; breaks = loops; close(synch[1]); read_loop(synch[0], &dummy, 1); @@ -1633,7 +1650,10 @@ execpline(Estate state, wordcode slcode, int how, int last1) list_pipe_child = 1; opts[INTERACTIVE] = 0; if (errbrk_saved) { - errflag = prev_errflag; + /* + * Keep any user interrupt bit in errflag. + */ + errflag = prev_errflag | (errflag & ERRFLAG_INT); breaks = prev_breaks; } break; @@ -1718,12 +1738,14 @@ execpline2(Estate state, wordcode pcode, if (pipe(synch) < 0) { zerr("pipe failed: %e", errno); - lastval = errflag = 1; + lastval = 1; + errflag |= ERRFLAG_ERROR; return; } else if ((pid = zfork(&bgtime)) == -1) { close(synch[0]); close(synch[1]); - lastval = errflag = 1; + lastval = 1; + errflag |= ERRFLAG_ERROR; return; } else if (pid) { char dummy, *text; @@ -2242,8 +2264,16 @@ addvars(Estate state, Wordcode pc, int addflags) state->pc = opc; return; } - if (isset(GLOBASSIGN) || !isstr) + if (!isstr || (isset(GLOBASSIGN) && + haswilds((char *)getdata(firstnode(vl))))) { globlist(vl, 0); + /* Unset the parameter to force it to be recreated + * as either scalar or array depending on how many + * matches were found for the glob. + */ + if (isset(GLOBASSIGN)) + unsetparam(name); + } if (errflag) { state->pc = opc; return; @@ -2279,6 +2309,8 @@ addvars(Estate state, Wordcode pc, int addflags) } allexp = opts[ALLEXPORT]; opts[ALLEXPORT] = 1; + if (isset(KSHARRAYS)) + unsetparam(name); pm = assignsparam(name, val, myflags); opts[ALLEXPORT] = allexp; } else @@ -2290,13 +2322,13 @@ addvars(Estate state, Wordcode pc, int addflags) continue; } if (vl) { - ptr = arr = (char **) zalloc(sizeof(char **) * + ptr = arr = (char **) zalloc(sizeof(char *) * (countlinknodes(vl) + 1)); while (nonempty(vl)) *ptr++ = ztrdup((char *) ugetnode(vl)); } else - ptr = arr = (char **) zalloc(sizeof(char **)); + ptr = arr = (char **) zalloc(sizeof(char *)); *ptr = NULL; if (xtr) { @@ -2320,6 +2352,7 @@ addvars(Estate state, Wordcode pc, int addflags) void setunderscore(char *str) { + queue_signals(); if (str && *str) { int l = strlen(str) + 1, nl = (l + 31) & ~31; @@ -2337,6 +2370,7 @@ setunderscore(char *str) *zunderscore = '\0'; underscoreused = 1; } + unqueue_signals(); } /* These describe the type of expansions that need to be done on the words @@ -2551,7 +2585,8 @@ execcmd(Estate state, int input, int output, int how, int last1) while (next && *next == '-' && strlen(next) >= 2) { if (!firstnode(args)) { zerr("exec requires a command to execute"); - errflag = lastval = 1; + lastval = 1; + errflag |= ERRFLAG_ERROR; goto done; } uremnode(args, firstnode(args)); @@ -2568,12 +2603,14 @@ execcmd(Estate state, int input, int output, int how, int last1) } else { if (!firstnode(args)) { zerr("exec requires a command to execute"); - errflag = lastval = 1; + lastval = 1; + errflag |= ERRFLAG_ERROR; goto done; } if (!nextnode(firstnode(args))) { zerr("exec flag -a requires a parameter"); - errflag = lastval = 1; + lastval = 1; + errflag |= ERRFLAG_ERROR; goto done; } exec_argv0 = (char *) @@ -2589,7 +2626,8 @@ execcmd(Estate state, int input, int output, int how, int last1) break; default: zerr("unknown exec flag -%c", *cmdopt); - errflag = lastval = 1; + lastval = 1; + errflag |= ERRFLAG_ERROR; return; } } @@ -2613,6 +2651,10 @@ execcmd(Estate state, int input, int output, int how, int last1) } } + /* if we get this far, it is OK to pay attention to lastval again */ + if (noerrexit == 2 && !is_shfunc) + noerrexit = 0; + /* Do prefork substitutions */ esprefork = (assign || isset(MAGICEQUALSUBST)) ? PREFORK_TYPESET : 0; if (args && htok) @@ -2652,7 +2694,8 @@ execcmd(Estate state, int input, int output, int how, int last1) } else if (!nullcmd || !*nullcmd || opts[CSHNULLCMD] || (cflags & BINF_PREFIX)) { zerr("redirection with no command"); - errflag = lastval = 1; + lastval = 1; + errflag |= ERRFLAG_ERROR; return; } else if (!nullcmd || !*nullcmd || opts[SHNULLCMD]) { if (!args) @@ -2682,7 +2725,7 @@ execcmd(Estate state, int input, int output, int how, int last1) if (varspc) addvars(state, varspc, 0); if (errflag) - lastval = errflag; + lastval = 1; else lastval = cmdoutval; if (isset(XTRACE)) { @@ -2758,9 +2801,14 @@ execcmd(Estate state, int input, int output, int how, int last1) else text = NULL; - /* Set up special parameter $_ */ - - setunderscore((args && nonempty(args)) ? ((char *) getdata(lastnode(args))) : ""); + /* + * Set up special parameter $_ + * For execfuncdef we may need to take account of an + * anonymous function with arguments. + */ + if (type != WC_FUNCDEF) + setunderscore((args && nonempty(args)) ? + ((char *) getdata(lastnode(args))) : ""); /* Warn about "rm *" */ if (type == WC_SIMPLE && interact && unset(RMSTARSILENT) && @@ -2786,7 +2834,7 @@ execcmd(Estate state, int input, int output, int how, int last1) } } if (!nextnode(firstnode(args))) - errflag = 1; + errflag |= ERRFLAG_ERROR; } if (type == WC_FUNCDEF) { @@ -2882,6 +2930,7 @@ execcmd(Estate state, int input, int output, int how, int last1) * is a directory we should AUTOCD to. */ if (!hn && trycd && (s = cancd(peekfirst(args)))) { peekfirst(args) = (void *) s; + pushnode(args, dupstring("--")); pushnode(args, dupstring("cd")); if ((hn = builtintab->getnode(builtintab, "cd"))) is_builtin = 1; @@ -2931,7 +2980,8 @@ execcmd(Estate state, int input, int output, int how, int last1) } else if ((pid = zfork(&bgtime)) == -1) { close(synch[0]); close(synch[1]); - lastval = errflag = 1; + lastval = 1; + errflag |= ERRFLAG_ERROR; goto fatal; } if (pid) { @@ -2941,8 +2991,6 @@ execcmd(Estate state, int input, int output, int how, int last1) close(synch[0]); if (how & Z_ASYNC) { lastpid = (zlong) pid; - /* indicate it's possible to set status for lastpid */ - lastpid_status = -2L; } else if (!jobtab[thisjob].stty_in_env && varspc) { /* search for STTY=... */ Wordcode p = varspc; @@ -2996,6 +3044,15 @@ execcmd(Estate state, int input, int output, int how, int last1) * Note that any form of exec means that the subshell is fake * * (but we may be in a subshell already). */ is_exec = 1; + /* + * If we are in a subshell environment anyway, say we're forked, + * even if we're actually not forked because we know the + * subshell is exiting. This ensures SHLVL reflects the current + * shell, and also optimises out any save/restore we'd need to + * do if we were returning to the main shell. + */ + if (type == WC_SUBSH) + forked = 1; } if ((esglob = !(cflags & BINF_NOGLOB)) && args && htok) { @@ -3169,7 +3226,12 @@ execcmd(Estate state, int input, int output, int how, int last1) } if (fn->fd1 < 10) closemn(mfds, fn->fd1, REDIR_CLOSE); - if (!closed && zclose(fn->fd1) < 0) { + /* + * Only report failures to close file descriptors + * if they're under user control as we don't know + * what the previous status of others was. + */ + if (!closed && zclose(fn->fd1) < 0 && fn->varid) { zwarn("failed to close file descriptor %d: %e", fn->fd1, errno); } @@ -3259,6 +3321,20 @@ execcmd(Estate state, int input, int output, int how, int last1) closemn(mfds, i, REDIR_CLOSE); if (nullexec) { + /* + * If nullexec is 2, we have variables to add with the redirections + * in place. If nullexec is 1, we may have variables but they + * need the standard restore logic. + */ + if (varspc) { + LinkList restorelist = 0, removelist = 0; + if (!isset(POSIXBUILTINS) && nullexec != 2) + save_params(state, varspc, &restorelist, &removelist); + addvars(state, varspc, 0); + if (restorelist) + restore_params(restorelist, removelist); + } + lastval = errflag ? errflag : cmdoutval; if (nullexec == 1) { /* * If nullexec is 1 we specifically *don't* restore the original @@ -3269,13 +3345,6 @@ execcmd(Estate state, int input, int output, int how, int last1) zclose(save[i]); goto done; } - /* - * If nullexec is 2, we have variables to add with the redirections - * in place. - */ - if (varspc) - addvars(state, varspc, 0); - lastval = errflag ? errflag : cmdoutval; if (isset(XTRACE)) { fputc('\n', xtrerr); fflush(xtrerr); @@ -3315,14 +3384,14 @@ execcmd(Estate state, int input, int output, int how, int last1) * The copy uses the wordcode parsing area, so save and * restore state. */ - lexsave(); + zcontext_save(); redir_prog = eccopyredirs(&s); - lexrestore(); + zcontext_restore(); } else redir_prog = NULL; - + lastval = execfuncdef(state, redir_prog); - } + } else if (type >= WC_CURSH) { if (last1 == 1) do_exec = 1; @@ -3338,14 +3407,28 @@ execcmd(Estate state, int input, int output, int how, int last1) LinkList restorelist = 0, removelist = 0; /* builtin or shell function */ - if (!forked && ((cflags & BINF_COMMAND) || - (unset(POSIXBUILTINS) && !assign) || - (isset(POSIXBUILTINS) && !is_shfunc && - !(hn->flags & BINF_PSPECIAL)))) { - if (varspc) + if (!forked && varspc) { + int do_save = 0; + if (isset(POSIXBUILTINS)) { + /* + * If it's a function or special builtin --- save + * if it's got "command" in front. + * If it's a normal command --- save. + */ + if (is_shfunc || (hn->flags & BINF_PSPECIAL)) + do_save = (orig_cflags & BINF_COMMAND); + else + do_save = 1; + } else { + /* + * Save if it's got "command" in front or it's + * not a magic-equals assignment. + */ + if ((cflags & BINF_COMMAND) || !assign) + do_save = 1; + } + if (do_save) save_params(state, varspc, &restorelist, &removelist); - else - restorelist = removelist = NULL; } if (varspc) { /* Export this if the command is a shell function, @@ -3513,7 +3596,7 @@ execcmd(Estate state, int input, int output, int how, int last1) else exit(1); } - errflag = 1; + errflag |= ERRFLAG_ERROR; } } if (newxtrerr) { @@ -3738,17 +3821,20 @@ gethere(char **strp, int typ) *bptr++ = '\n'; } *t = '\0'; + s = buf; + buf = dupstring(buf); + zfree(s, bsiz); if (!qt) { int ef = errflag; - parsestr(buf); + parsestr(&buf); - if (!errflag) - errflag = ef; + if (!errflag) { + /* Retain any user interrupt error */ + errflag = ef | (errflag & ERRFLAG_INT); + } } - s = dupstring(buf); - zfree(buf, bsiz); - return s; + return buf; } /* open here string fd */ @@ -3832,13 +3918,13 @@ getoutput(char *cmd, int qt) return NULL; untokenize(s); if ((stream = open(unmeta(s), O_RDONLY | O_NOCTTY)) == -1) { - zerr("%e: %s", errno, s); - return NULL; + zwarn("%e: %s", errno, s); + return newlinklist(); } return readoutput(stream, qt); } if (mpipe(pipes) < 0) { - errflag = 1; + errflag |= ERRFLAG_ERROR; cmdoutpid = 0; return NULL; } @@ -3848,7 +3934,7 @@ getoutput(char *cmd, int qt) /* fork error */ zclose(pipes[0]); zclose(pipes[1]); - errflag = 1; + errflag |= ERRFLAG_ERROR; cmdoutpid = 0; child_unblock(); return NULL; @@ -4258,7 +4344,7 @@ execcond(Estate state, UNUSED(int do_exec)) * into a shell error. */ if (stat == 2) - errflag = 1; + errflag |= ERRFLAG_ERROR; cmdpop(); if (isset(XTRACE)) { fprintf(xtrerr, " ]]\n"); @@ -4298,7 +4384,7 @@ execarith(Estate state, UNUSED(int do_exec)) fflush(xtrerr); } if (errflag) { - errflag = 0; + errflag &= ~ERRFLAG_ERROR; return 2; } /* should test for fabs(val.u.d) < epsilon? */ @@ -4332,7 +4418,7 @@ execfuncdef(Estate state, Eprog redir_prog) Shfunc shf; char *s = NULL; int signum, nprg, sbeg, nstrs, npats, len, plen, i, htok = 0, ret = 0; - int nfunc = 0; + int nfunc = 0, anon_func = 0; Wordcode beg = state->pc, end; Eprog prog; Patprog *pp; @@ -4414,10 +4500,12 @@ execfuncdef(Estate state, Eprog redir_prog) if (!names) { /* * Anonymous function, execute immediately. - * Function name is "(anon)", parameter list is empty. + * Function name is "(anon)". */ LinkList args; + anon_func = 1; + state->pc = end; end += *state->pc++; args = ecgetlist(state, *state->pc++, EC_DUPTOK, &htok); @@ -4425,11 +4513,19 @@ execfuncdef(Estate state, Eprog redir_prog) if (htok && args) { execsubst(args); if (errflag) { + freeeprog(shf->funcdef); + if (shf->redir) /* shouldn't be */ + freeeprog(shf->redir); + zsfree(shf->filename); + zfree(shf, sizeof(*shf)); state->pc = end; return 1; } } + setunderscore((args && nonempty(args)) ? + ((char *) getdata(lastnode(args))) : ""); + if (!args) args = newlinklist(); shf->node.nam = "(anon)"; @@ -4438,6 +4534,16 @@ execfuncdef(Estate state, Eprog redir_prog) execshfunc(shf, args); ret = lastval; + if (isset(PRINTEXITVALUE) && isset(SHINSTDIN) && + lastval) { +#if defined(ZLONG_IS_LONG_LONG) && defined(PRINTF_HAS_LLD) + fprintf(stderr, "zsh: exit %lld\n", lastval); +#else + fprintf(stderr, "zsh: exit %ld\n", (long)lastval); +#endif + fflush(stderr); + } + freeeprog(shf->funcdef); if (shf->redir) /* shouldn't be */ freeeprog(shf->redir); @@ -4450,6 +4556,7 @@ execfuncdef(Estate state, Eprog redir_prog) (signum = getsignum(s + 4)) != -1) { if (settrap(signum, NULL, ZSIG_FUNC)) { freeeprog(shf->funcdef); + zsfree(shf->filename); zfree(shf, sizeof(*shf)); state->pc = end; return 1; @@ -4464,6 +4571,8 @@ execfuncdef(Estate state, Eprog redir_prog) shfunctab->addnode(shfunctab, ztrdup(s), shf); } } + if (!anon_func) + setunderscore(""); if (!nfunc && redir_prog) { /* For completeness, shouldn't happen */ freeeprog(redir_prog); @@ -4547,6 +4656,7 @@ execshfunc(Shfunc shf, LinkList args) fputc('\n', xtrerr); fflush(xtrerr); } + queue_signals(); ocs = cmdstack; ocsp = cmdsp; cmdstack = (unsigned char *) zalloc(CMDSTACKSZ); @@ -4554,7 +4664,11 @@ execshfunc(Shfunc shf, LinkList args) if ((osfc = sfcontext) == SFC_NONE) sfcontext = SFC_DIRECT; xtrerr = stderr; + unqueue_signals(); + doshfunc(shf, args, 0); + + queue_signals(); sfcontext = osfc; free(cmdstack); cmdstack = ocs; @@ -4562,6 +4676,7 @@ execshfunc(Shfunc shf, LinkList args) if (!list_pipe) deletefilelist(last_file_list, 0); + unqueue_signals(); } /* @@ -4910,7 +5025,7 @@ doshfunc(Shfunc shfunc, LinkList doshargs, int noreturnval) (name = fname)))) { zwarn("%s: function not defined by file", name); if (noreturnval) - errflag = 1; + errflag |= ERRFLAG_ERROR; else lastval = 1; goto doneshfunc; @@ -5243,7 +5358,7 @@ execsave(void) { struct execstack *es; - es = (struct execstack *) malloc(sizeof(struct execstack)); + es = (struct execstack *) zalloc(sizeof(struct execstack)); es->list_pipe_pid = list_pipe_pid; es->nowait = nowait; es->pline_level = pline_level; diff --git a/Src/glob.c b/Src/glob.c index ca7bc4410..057d44a17 100644 --- a/Src/glob.c +++ b/Src/glob.c @@ -386,8 +386,8 @@ insert(char *s, int checked) while (!inserts || (news = dupstring(*inserts++))) { if (colonmod) { /* Handle the remainder of the qualifier: e.g. (:r:s/foo/bar/). */ - s = colonmod; - modify(&news, &s); + char *mod = colonmod; + modify(&news, &mod); } if (!statted && (gf_sorts & GS_NORMAL)) { statfullpath(s, &buf, 1); @@ -437,7 +437,7 @@ insert(char *s, int checked) matchptr++; if (++matchct == matchsz) { - matchbuf = (Gmatch )realloc((char *)matchbuf, + matchbuf = (Gmatch)zrealloc((char *)matchbuf, sizeof(struct gmatch) * (matchsz *= 2)); matchptr = matchbuf + matchct; @@ -463,7 +463,7 @@ scanner(Complist q, int shortcircuit) int errssofar = errsfound; struct dirsav ds; - if (!q) + if (!q || errflag) return; init_dirsav(&ds); @@ -682,7 +682,7 @@ parsecomplist(char *instr) /* Now get the next path component if there is one. */ l1 = (Complist) zhalloc(sizeof *l1); if ((l1->next = parsecomplist(instr)) == NULL) { - errflag = 1; + errflag |= ERRFLAG_ERROR; return NULL; } l1->pat = patcompile(NULL, compflags | PAT_ANY, NULL); @@ -708,7 +708,8 @@ parsecomplist(char *instr) } l1 = (Complist) zhalloc(sizeof *l1); l1->pat = p1; - l1->closure = 1 + pdflag; + /* special case (/)# to avoid infinite recursion */ + l1->closure = (*((char *)p1 + p1->startoff)) ? 1 + pdflag : 0; l1->follow = 0; l1->next = parsecomplist(instr); return (l1->pat) ? l1 : NULL; @@ -728,7 +729,7 @@ parsecomplist(char *instr) return (ef && !l1->next) ? NULL : l1; } } - errflag = 1; + errflag |= ERRFLAG_ERROR; return NULL; } @@ -1790,7 +1791,7 @@ zglob(LinkList list, LinkNode np, int nountok) insertlinknode(list, node, ostr); return; } - errflag = 0; + errflag &= ~ERRFLAG_ERROR; zerr("bad pattern: %s", ostr); return; } @@ -1817,7 +1818,7 @@ zglob(LinkList list, LinkNode np, int nountok) badcshglob |= 1; /* at least one cmd. line expansion failed */ } else if (isset(NOMATCH)) { zerr("no matches found: %s", ostr); - free(matchbuf); + zfree(matchbuf, 0); restore_globstate(saved); return; } else { @@ -1873,7 +1874,8 @@ zglob(LinkList list, LinkNode np, int nountok) tmpptr->sortstrs[iexec] = tmpptr->name; } - errflag = ef; + /* Retain any user interrupt error status */ + errflag = ef | (errflag & ERRFLAG_INT); lastval = lv; } else { /* Failed, let's be safe */ @@ -1921,7 +1923,7 @@ zglob(LinkList list, LinkNode np, int nountok) } else if (!badcshglob && !isset(NOMATCH) && matchct == 1) { insert_glob_match(list, node, (--matchptr)->name); } - free(matchbuf); + zfree(matchbuf, 0); restore_globstate(saved); } @@ -3733,7 +3735,8 @@ qualsheval(char *name, UNUSED(struct stat *buf), UNUSED(off_t days), char *str) execode(prog, 1, 0, "globqual"); ret = lastval; - errflag = ef; + /* Retain any user interrupt error status */ + errflag = ef | (errflag & ERRFLAG_INT); lastval = lv; if (!(inserts = getaparam("reply")) && diff --git a/Src/hashtable.c b/Src/hashtable.c index 7a430629d..ab381cc6a 100644 --- a/Src/hashtable.c +++ b/Src/hashtable.c @@ -910,7 +910,7 @@ printshfuncnode(HashNode hn, int printflags) { Shfunc f = (Shfunc) hn; char *t = 0; - + if ((printflags & PRINT_NAMEONLY) || ((printflags & PRINT_WHENCE_SIMPLE) && !(printflags & PRINT_WHENCE_FUNCDEF))) { @@ -922,8 +922,16 @@ printshfuncnode(HashNode hn, int printflags) if ((printflags & (PRINT_WHENCE_VERBOSE|PRINT_WHENCE_WORD)) && !(printflags & PRINT_WHENCE_FUNCDEF)) { nicezputs(f->node.nam, stdout); - printf((printflags & PRINT_WHENCE_WORD) ? ": function\n" : - " is a shell function\n"); + printf((printflags & PRINT_WHENCE_WORD) ? ": function" : + (f->node.flags & PM_UNDEFINED) ? + " is an autoload shell function" : + " is a shell function"); + if (f->filename && (printflags & PRINT_WHENCE_VERBOSE) && + strcmp(f->filename, f->node.nam) != 0) { + printf(" from "); + quotedzputs(f->filename, stdout); + } + putchar('\n'); return; } diff --git a/Src/hist.c b/Src/hist.c index 4660fd073..bd03c4f11 100644 --- a/Src/hist.c +++ b/Src/hist.c @@ -134,6 +134,8 @@ mod_export int hist_skip_flags; /* Bits of histactive variable */ #define HA_ACTIVE (1<<0) /* History mechanism is active */ #define HA_NOINC (1<<1) /* Don't store, curhist not incremented */ +#define HA_INWORD (1<<2) /* We're inside a word, don't add + start and end markers */ /* Array of word beginnings and endings in current history line. */ @@ -222,13 +224,119 @@ static int histsave_stack_pos = 0; static zlong histfile_linect; +/* save history context */ + +/**/ +void +hist_context_save(struct hist_stack *hs, int toplevel) +{ + if (toplevel) { + /* top level, make this version visible to ZLE */ + zle_chline = chline; + /* ensure line stored is NULL-terminated */ + if (hptr) + *hptr = '\0'; + } + hs->histactive = histactive; + hs->histdone = histdone; + hs->stophist = stophist; + hs->hline = chline; + hs->hptr = hptr; + hs->chwords = chwords; + hs->chwordlen = chwordlen; + hs->chwordpos = chwordpos; + hs->hgetc = hgetc; + hs->hungetc = hungetc; + hs->hwaddc = hwaddc; + hs->hwbegin = hwbegin; + hs->hwend = hwend; + hs->addtoline = addtoline; + hs->hlinesz = hlinesz; + /* + * We save and restore the command stack with history + * as it's visible to the user interactively, so if + * we're preserving history state we'll continue to + * show the current set of commands from input. + */ + hs->cstack = cmdstack; + hs->csp = cmdsp; + + stophist = 0; + chline = NULL; + hptr = NULL; + histactive = 0; + cmdstack = (unsigned char *)zalloc(CMDSTACKSZ); + cmdsp = 0; +} + +/* restore history context */ + +/**/ +void +hist_context_restore(const struct hist_stack *hs, int toplevel) +{ + if (toplevel) { + /* Back to top level: don't need special ZLE value */ + DPUTS(hs->hline != zle_chline, "BUG: Ouch, wrong chline for ZLE"); + zle_chline = NULL; + } + histactive = hs->histactive; + histdone = hs->histdone; + stophist = hs->stophist; + chline = hs->hline; + hptr = hs->hptr; + chwords = hs->chwords; + chwordlen = hs->chwordlen; + chwordpos = hs->chwordpos; + hgetc = hs->hgetc; + hungetc = hs->hungetc; + hwaddc = hs->hwaddc; + hwbegin = hs->hwbegin; + hwend = hs->hwend; + addtoline = hs->addtoline; + hlinesz = hs->hlinesz; + if (cmdstack) + zfree(cmdstack, CMDSTACKSZ); + cmdstack = hs->cstack; + cmdsp = hs->csp; +} + +/* + * Mark that the current level of history is within a word whatever + * characters turn up, or turn that mode off. This is used for nested + * parsing of substitutions. + * + * The caller takes care only to turn this on or off at the start + * or end of recursive use of the same mode, so a single flag is + * good enough here. + */ + +/**/ +void +hist_in_word(int yesno) +{ + if (yesno) + histactive |= HA_INWORD; + else + histactive &= ~HA_INWORD; +} + /* add a character to the current history word */ static void ihwaddc(int c) { /* Only if history line exists and lexing has not finished. */ - if (chline && !(errflag || lexstop)) { + if (chline && !(errflag || lexstop) && + /* + * If we're reading inside a word for command substitution + * we allow the lexer to expand aliases but don't deal + * with them here. Note matching code in ihungetc(). + * TBD: it might be neater to deal with all aliases in this + * fashion as we never need the expansion in the history + * line, only in the lexer and above. + */ + (inbufflags & (INP_ALIAS|INP_HIST)) != INP_ALIAS) { /* Quote un-expanded bangs in the history line. */ if (c == bangchar && stophist < 2 && qbang) /* If qbang is not set, we do not escape this bangchar as it's * @@ -287,7 +395,8 @@ ihgetc(void) c = histsubchar(c); if (c < 0) { /* bad expansion */ - errflag = lexstop = 1; + lexstop = 1; + errflag |= ERRFLAG_ERROR; return ' '; } } @@ -366,6 +475,7 @@ getsubsargs(char *subline, int *gbalp, int *cflagp) zsfree(hsubl); hsubl = ptr1; } else if (!hsubl) { /* fail silently on this */ + zsfree(ptr1); zsfree(ptr2); return 0; } @@ -415,9 +525,20 @@ histsubchar(int c) static zlong mev = -1; char *buf, *ptr; char *sline; + int lexraw_mark; Histent ehist; size_t buflen; + /* + * If accumulating raw input for use in command substitution, + * we don't want the history text, so mark it for later removal. + * It would be better to do this at a level above the history + * and below the lexer --- but there isn't one. + * + * Include the character we are attempting to substitute. + */ + lexraw_mark = zshlex_raw_mark(-1); + /* look, no goto's */ if (isfirstch && c == hatchar) { int gbal = 0; @@ -721,7 +842,7 @@ histsubchar(int c) noerrs = 1; parse_subst_string(sline); noerrs = one; - errflag = oef; + errflag = oef | (errflag & ERRFLAG_INT); remnulargs(sline); untokenize(sline); } @@ -751,6 +872,8 @@ histsubchar(int c) } } + zshlex_raw_back_to_mark(lexraw_mark); + /* * Push the expanded value onto the input stack, * marking this as a history word for purposes of the alias stack. @@ -789,11 +912,16 @@ ihungetc(int c) zlemetall--; exlast++; } - DPUTS(hptr <= chline, "BUG: hungetc attempted at buffer start"); - hptr--; - DPUTS(*hptr != (char) c, "BUG: wrong character in hungetc() "); - qbang = (c == bangchar && stophist < 2 && - hptr > chline && hptr[-1] == '\\'); + if ((inbufflags & (INP_ALIAS|INP_HIST)) != INP_ALIAS) { + DPUTS(hptr <= chline, "BUG: hungetc attempted at buffer start"); + hptr--; + DPUTS(*hptr != (char) c, "BUG: wrong character in hungetc() "); + qbang = (c == bangchar && stophist < 2 && + hptr > chline && hptr[-1] == '\\'); + } else { + /* No active bangs in aliases */ + qbang = 0; + } if (doit) inungetc(c); if (!qbang) @@ -813,6 +941,11 @@ strinbeg(int dohist) strin++; hbegin(dohist); lexinit(); + /* + * Also initialise some variables owned by the parser but + * used for communication between the parser and lexer. + */ + init_parse_status(); } /* done reading a string */ @@ -880,7 +1013,8 @@ hbegin(int dohist) char *hf; isfirstln = isfirstch = 1; - errflag = histdone = 0; + errflag &= ~ERRFLAG_ERROR; + histdone = 0; if (!dohist) stophist = 2; else if (dohist != 2) @@ -1110,8 +1244,11 @@ static void putoldhistentryontop(short keep_going) { static Histent next = NULL; - Histent he = keep_going? next : hist_ring->down; - next = he->down; + Histent he = (keep_going || !hist_ring) ? next : hist_ring->down; + if (he) + next = he->down; + else + return; if (isset(HISTEXPIREDUPSFIRST) && !(he->node.flags & HIST_DUP)) { static zlong max_unique_ct = 0; if (!keep_going) @@ -1151,7 +1288,7 @@ prepnexthistent(void) freehistnode(&hist_ring->node); } - if (histlinect < histsiz) { + if (histlinect < histsiz || !hist_ring) { he = (Histent)zshcalloc(sizeof *he); if (!hist_ring) hist_ring = he->up = he->down = he; @@ -1395,28 +1532,17 @@ hend(Eprog prog) return !(flag & HISTFLAG_NOEXEC || errflag); } -/* Gives current expansion word if not last word before chwordpos. */ - -/**/ -int hwgetword = -1; - /* begin a word */ /**/ void ihwbegin(int offset) { - if (stophist == 2) + if (stophist == 2 || (histactive & HA_INWORD) || + (inbufflags & (INP_ALIAS|INP_HIST)) == INP_ALIAS) return; if (chwordpos%2) chwordpos--; /* make sure we're on a word start, not end */ - /* If we're expanding an alias, we should overwrite the expansion - * in the history. - */ - if ((inbufflags & INP_ALIAS) && !(inbufflags & INP_HIST)) - hwgetword = chwordpos; - else - hwgetword = -1; chwords[chwordpos++] = hptr - chline + offset; } @@ -1426,7 +1552,8 @@ ihwbegin(int offset) void ihwend(void) { - if (stophist == 2) + if (stophist == 2 || (histactive & HA_INWORD) || + (inbufflags & (INP_ALIAS|INP_HIST)) == INP_ALIAS) return; if (chwordpos%2 && chline) { /* end of word reached and we've already begun a word */ @@ -1437,13 +1564,6 @@ ihwend(void) (chwordlen += 32) * sizeof(short)); } - if (hwgetword > -1 && - (inbufflags & INP_ALIAS) && !(inbufflags & INP_HIST)) { - /* We want to reuse the current word position */ - chwordpos = hwgetword; - /* Start from where previous word ended, if possible */ - hptr = chline + chwords[chwordpos ? chwordpos - 1 : 0]; - } } else { /* scrub that last word, it doesn't exist */ chwordpos--; @@ -1467,17 +1587,17 @@ histbackword(void) static void hwget(char **startptr) { - int pos = hwgetword > -1 ? hwgetword : chwordpos - 2; + int pos = chwordpos - 2; #ifdef DEBUG /* debugging only */ - if (hwgetword == -1 && !chwordpos) { + if (!chwordpos) { /* no words available */ DPUTS(1, "BUG: hwget() called with no words"); *startptr = ""; return; - } - else if (hwgetword == -1 && chwordpos%2) { + } + else if (chwordpos%2) { DPUTS(1, "BUG: hwget() called in middle of word"); *startptr = ""; return; @@ -1499,9 +1619,9 @@ hwrep(char *rep) if (!strcmp(rep, start)) return; - + hptr = start; - chwordpos = (hwgetword > -1) ? hwgetword : chwordpos - 2; + chwordpos = chwordpos - 2; hwbegin(0); qbang = 1; while (*rep) @@ -1529,7 +1649,6 @@ hgetline(void) /* reset line */ hptr = chline; chwordpos = 0; - hwgetword = -1; return ret; } @@ -1699,11 +1818,12 @@ int chrealpath(char **junkptr) { char *str; -#ifdef HAVE_CANONICALIZE_FILE_NAME +#ifdef HAVE_REALPATH +# ifdef REALPATH_ACCEPTS_NULL char *lastpos, *nonreal, *real; -#else -# ifdef HAVE_REALPATH - char *lastpos, *nonreal, real[PATH_MAX]; +# else + char *lastpos, *nonreal, pathbuf[PATH_MAX]; + char *real = pathbuf; # endif #endif @@ -1714,7 +1834,7 @@ chrealpath(char **junkptr) if (!chabspath(junkptr)) return 0; -#if !defined(HAVE_REALPATH) && !defined(HAVE_CANONICALIZE_FILE_NAME) +#ifndef HAVE_REALPATH return 1; #else /* @@ -1730,29 +1850,21 @@ chrealpath(char **junkptr) nonreal = lastpos + 1; while (! -#ifdef HAVE_CANONICALIZE_FILE_NAME - /* - * This is a GNU extension to realpath(); it's the - * same as calling realpath() with a NULL second argument - * which uses malloc() to get memory. The alternative - * interface is easier to test for, however. - */ - (real = canonicalize_file_name(*junkptr)) +#ifdef REALPATH_ACCEPTS_NULL + /* realpath() with a NULL second argument uses malloc() to get + * memory so we don't need to worry about overflowing PATH_MAX */ + (real = realpath(*junkptr, NULL)) #else realpath(*junkptr, real) #endif ) { - if (errno == EINVAL || errno == ELOOP || - errno == ENAMETOOLONG || errno == ENOMEM) + if (errno == EINVAL || errno == ENOMEM) return 0; -#ifdef HAVE_CANONICALIZE_FILE_NAME - if (!real) - return 0; -#endif - if (nonreal == *junkptr) { - *real = '\0'; +#ifndef REALPATH_ACCEPTS_NULL + real = NULL; +#endif break; } @@ -1768,11 +1880,15 @@ chrealpath(char **junkptr) str++; } - *junkptr = metafy(str = bicat(real, nonreal), -1, META_HEAPDUP); - zsfree(str); -#ifdef HAVE_CANONICALIZE_FILE_NAME - free(real); + if (real) { + *junkptr = metafy(str = bicat(real, nonreal), -1, META_HEAPDUP); + zsfree(str); +#ifdef REALPATH_ACCEPTS_NULL + free(real); #endif + } else { + *junkptr = metafy(nonreal, lastpos - nonreal + 1, META_HEAPDUP); + } #endif return 1; @@ -2139,10 +2255,10 @@ getargs(Histent elist, int arg1, int arg2) } /**/ -int +static int quote(char **tr) { - char *ptr, *rptr, **str = (char **)tr; + char *ptr, *rptr, **str = tr; int len = 3; int inquotes = 0; @@ -2176,7 +2292,6 @@ quote(char **tr) *rptr++ = *ptr; *rptr++ = '\''; *rptr++ = 0; - str[1] = NULL; return 0; } @@ -2184,7 +2299,7 @@ quote(char **tr) static int quotebreak(char **tr) { - char *ptr, *rptr, **str = (char **)tr; + char *ptr, *rptr, **str = tr; int len = 3; for (ptr = *str; *ptr; ptr++, len++) @@ -2378,12 +2493,40 @@ readhistfile(char *fn, int err, int readflags) || (hist_ignore_all_dups && newflags & hist_skip_flags)) newflags |= HIST_MAKEUNIQUE; while (fpos = ftell(in), (l = readhistline(0, &buf, &bufsiz, in))) { - char *pt = buf; + char *pt; + int remeta = 0; if (l < 0) { zerr("corrupt history file %s", fn); break; } + + /* + * Handle the special case that we're reading from an + * old shell with fewer meta characters, so we need to + * metafy some more. (It's not clear why the history + * file is metafied at all; some would say this is plain + * stupid. But we're stuck with it now without some + * hairy workarounds for compatibility). + * + * This is rare so doesn't need to be that efficient; just + * allocate space off the heap. + */ + for (pt = buf; *pt; pt++) { + if (*pt == Meta && pt[1]) + pt++; + else if (imeta(*pt)) { + remeta = 1; + break; + } + } + if (remeta) { + unmetafy(buf, &remeta); + pt = metafy(buf, remeta, META_USEHEAP); + } else { + pt = buf; + } + if (*pt == ':') { pt++; stim = zstrtol(pt, NULL, 0); @@ -2443,8 +2586,6 @@ readhistfile(char *fn, int err, int readflags) start = pt; uselex = isset(HISTLEXWORDS) && !(readflags & HFILE_FAST); histsplitwords(pt, &words, &nwords, &nwordpos, uselex); - if (uselex) - freeheap(); he->nwords = nwordpos/2; if (he->nwords) { @@ -2457,6 +2598,14 @@ readhistfile(char *fn, int err, int readflags) freehistnode(&he->node); curhist--; } + /* + * Do this last out of paranoia in case use of + * heap is disguised... + */ + if (uselex || remeta) + freeheap(); + if (errflag & ERRFLAG_INT) + break; } if (start && readflags & HFILE_USE_OPTIONS) { zsfree(lasthist.text); @@ -2488,7 +2637,8 @@ static int flockhistfile(char *fn, int keep_trying) { struct flock lck; - int ctr = keep_trying ? 9 : 0; + long sleep_us = 0x10000; /* about 67 ms */ + time_t end_time; if (flock_fd >= 0) return 0; /* already locked */ @@ -2501,13 +2651,22 @@ flockhistfile(char *fn, int keep_trying) lck.l_start = 0; lck.l_len = 0; /* lock the whole file */ + /* + * Timeout is ten seconds. + */ + end_time = time(NULL) + (time_t)10; while (fcntl(flock_fd, F_SETLKW, &lck) == -1) { - if (--ctr < 0) { + if (!keep_trying || time(NULL) >= end_time || + /* + * Randomise wait to minimise clashes with shells exiting at + * the same time. + */ + !zsleep_random(sleep_us, end_time)) { close(flock_fd); flock_fd = -1; return 1; } - sleep(1); + sleep_us <<= 1; } return 0; @@ -2743,7 +2902,7 @@ savehistfile(char *fn, int err, int writeflags) static int lockhistct; static int -checklocktime(char *lockfile, time_t then) +checklocktime(char *lockfile, long *sleep_usp, time_t then) { time_t now = time(NULL); @@ -2753,9 +2912,19 @@ checklocktime(char *lockfile, time_t then) return -1; } - if (now - then < 10) - sleep(1); - else + if (now - then < 10) { + /* + * To give the effect of a gradually increasing backoff, + * we'll sleep a period based on the time we've spent so far. + */ + DPUTS(now < then, "time flowing backwards through history"); + /* + * Randomise to minimise clashes with shells exiting at the same + * time. + */ + (void)zsleep_random(*sleep_usp, then + 10); + *sleep_usp <<= 1; + } else unlink(lockfile); return 0; @@ -2772,6 +2941,7 @@ lockhistfile(char *fn, int keep_trying) { int ct = lockhistct; int ret = 0; + long sleep_us = 0x10000; /* about 67 ms */ if (!fn && !(fn = getsparam("HISTFILE"))) return 1; @@ -2799,7 +2969,8 @@ lockhistfile(char *fn, int keep_trying) #ifdef HAVE_LINK # ifdef HAVE_SYMLINK sprintf(pidbuf, "/pid-%ld/host-", (long)mypid); - lnk = bicat(pidbuf, getsparam("HOST")); + lnk = getsparam("HOST"); + lnk = bicat(pidbuf, lnk ? lnk : ""); /* We'll abuse fd as our success flag. */ while ((fd = symlink(lnk, lockfile)) < 0) { if (errno != EEXIST) { @@ -2814,7 +2985,7 @@ lockhistfile(char *fn, int keep_trying) continue; break; } - if (checklocktime(lockfile, sb.st_mtime) < 0) { + if (checklocktime(lockfile, &sleep_us, sb.st_mtime) < 0) { ret = 1; break; } @@ -2842,7 +3013,7 @@ lockhistfile(char *fn, int keep_trying) continue; ret = 2; } else { - if (checklocktime(lockfile, sb.st_mtime) < 0) { + if (checklocktime(lockfile, &sleep_us, sb.st_mtime) < 0) { ret = 1; break; } @@ -2870,7 +3041,7 @@ lockhistfile(char *fn, int keep_trying) ret = 2; break; } - if (checklocktime(lockfile, sb.st_mtime) < 0) { + if (checklocktime(lockfile, &sleep_us, sb.st_mtime) < 0) { ret = 1; break; } @@ -2988,7 +3159,7 @@ bufferwords(LinkList list, char *buf, int *index, int flags) opts[RCQUOTES] = 0; addedx = 0; noerrs = 1; - lexsave(); + zcontext_save(); lexflags = flags | LEXFLAGS_ACTIVE; /* * Are we handling comments? @@ -3108,7 +3279,7 @@ bufferwords(LinkList list, char *buf, int *index, int flags) * double quotes. Whitespace in the middle is * similarly retained, so just add the parentheses back. */ - p = tricat("((", tokstr, "))"); + p = zhtricat("((", tokstr, "))"); } break; @@ -3162,7 +3333,7 @@ bufferwords(LinkList list, char *buf, int *index, int flags) got = 1; cur = num - 1; } - } while (tok != ENDINPUT && tok != LEXERR); + } while (tok != ENDINPUT && tok != LEXERR && !(errflag & ERRFLAG_INT)); if (buf && tok == LEXERR && tokstr && *tokstr) { int plen; untokenize((p = dupstring(tokstr))); @@ -3182,10 +3353,10 @@ bufferwords(LinkList list, char *buf, int *index, int flags) noaliases = ona; strinend(); inpop(); - errflag = 0; + errflag &= ~ERRFLAG_ERROR; nocomments = onc; noerrs = ne; - lexrestore(); + zcontext_restore(); zlemetacs = ocs; zlemetall = oll; wb = owb; @@ -3233,11 +3404,14 @@ histsplitwords(char *lineptr, short **wordsp, int *nwordsp, int *nwordposp, char *start = lineptr; if (uselex) { - LinkList wordlist = bufferwords(NULL, lineptr, NULL, - LEXFLAGS_COMMENTS_KEEP); + LinkList wordlist; LinkNode wordnode; int nwords_max; + wordlist = bufferwords(NULL, lineptr, NULL, + LEXFLAGS_COMMENTS_KEEP); + if (errflag) + return; nwords_max = 2 * countlinknodes(wordlist); if (nwords_max > nwords) { *nwordsp = nwords = nwords_max; @@ -3329,7 +3503,8 @@ histsplitwords(char *lineptr, short **wordsp, int *nwordsp, int *nwordposp, if (*lptr == *wptr || (*lptr == '!' && *wptr == '|')) { lptr++; - wptr++; + if (!*++wptr) + break; } else if (lptr[0] == '\\' && lptr[1] == '\n') { /* diff --git a/Src/init.c b/Src/init.c index 6d005dce7..102276a64 100644 --- a/Src/init.c +++ b/Src/init.c @@ -107,7 +107,7 @@ loop(int toplevel, int justonce) pushheap(); if (!toplevel) - lexsave(); + zcontext_save(); for (;;) { freeheap(); if (stophist == 3) /* re-entry via preprompt() */ @@ -118,18 +118,32 @@ loop(int toplevel, int justonce) if (interact && toplevel) { int hstop = stophist; stophist = 3; + /* + * Reset all errors including the interrupt error status + * immediately, so preprompt runs regardless of what + * just happened. We'll reset again below as a + * precaution to ensure we get back to the command line + * no matter what. + */ + errflag = 0; preprompt(); if (stophist != 3) hbegin(1); else stophist = hstop; + /* + * Reset all errors, including user interupts. + * This is what allows ^C in an interactive shell + * to return us to the command line. + */ errflag = 0; } } use_exit_printed = 0; intr(); /* interrupts on */ lexinit(); /* initialize lexical state */ - if (!(prog = parse_event())) { /* if we couldn't parse a list */ + if (!(prog = parse_event(ENDINPUT))) { + /* if we couldn't parse a list */ hend(NULL); if ((tok == ENDINPUT && !errflag) || (tok == LEXERR && (!isset(SHINSTDIN) || !toplevel)) || @@ -178,7 +192,15 @@ loop(int toplevel, int justonce) /* The only permanent storage is from getpermtext() */ zsfree(cmdstr); - errflag = 0; + /* + * Note this does *not* remove a user interrupt error + * condition, even though we're at the top level loop: + * that would be inconsistent with the case where + * we didn't execute a preexec function. This is + * an implementation detail that an interrupting user + * does't care about. + */ + errflag &= ~ERRFLAG_ERROR; } if (stopmsg) /* unset 'you have stopped jobs' flag */ stopmsg--; @@ -205,7 +227,7 @@ loop(int toplevel, int justonce) } err = errflag; if (!toplevel) - lexrestore(); + zcontext_restore(); popheap(); if (err) @@ -243,12 +265,24 @@ parseargs(char **argv, char **runscript) */ opts[MONITOR] = 2; /* may be unset in init_io() */ opts[HASHDIRS] = 2; /* same relationship to INTERACTIVE */ + opts[USEZLE] = 1; /* see below, related to SHINSTDIN */ opts[SHINSTDIN] = 0; opts[SINGLECOMMAND] = 0; if (parseopts(NULL, &argv, opts, &cmd, NULL)) exit(1); + /* + * USEZLE remains set if the shell has access to a terminal and + * is not reading from some other source as indicated by SHINSTDIN. + * SHINSTDIN becomes set below if there is no command argument, + * but it is the explicit setting (or not) that matters to USEZLE. + * USEZLE may also become unset in init_io() if the shell is not + * interactive or the terminal cannot be re-opened read/write. + */ + if (opts[SHINSTDIN]) + opts[USEZLE] = (opts[USEZLE] && isatty(0)); + paramlist = znewlinklist(); if (*argv) { if (unset(SHINSTDIN)) { @@ -603,7 +637,7 @@ init_shout(void) if (SHTTY == -1) { - /* Since we're interative, it's nice to have somewhere to write. */ + /* Since we're interactive, it's nice to have somewhere to write. */ shout = stderr; return; } @@ -616,7 +650,8 @@ init_shout(void) /* Associate terminal file descriptor with a FILE pointer */ shout = fdopen(SHTTY, "w"); #ifdef _IOFBF - setvbuf(shout, shoutbuf, _IOFBF, BUFSIZ); + if (shout) + setvbuf(shout, shoutbuf, _IOFBF, BUFSIZ); #endif gettyinfo(&shttyinfo); /* get tty state */ @@ -676,7 +711,7 @@ init_term(void) { if (isset(INTERACTIVE)) zerr("can't find terminal definition for %s", term); - errflag = 0; + errflag &= ~ERRFLAG_ERROR; termflags |= TERM_BAD; return 0; } else { @@ -1036,7 +1071,6 @@ setupvals(void) bufstack = znewlinklist(); hsubl = hsubr = NULL; lastpid = 0; - lastpid_status = -1L; get_usage(); @@ -1163,10 +1197,13 @@ run_init_scripts(void) if (islogin) sourcehome(".profile"); noerrs = 2; - if (s && !parsestr(s)) { - singsub(&s); - noerrs = 0; - source(s); + if (s) { + s = dupstring(s); + if (!parsestr(&s)) { + singsub(&s); + noerrs = 0; + source(s); + } } noerrs = 0; } else @@ -1324,7 +1361,7 @@ source(char *s) if (prog) { pushheap(); - errflag = 0; + errflag &= ~ERRFLAG_ERROR; execode(prog, 1, 0, "filecode"); popheap(); if (errflag) @@ -1367,12 +1404,12 @@ source(char *s) lineno = oldlineno; /* our current lineno */ loops = oloops; /* the # of nested loops we are in */ dosetopt(SHINSTDIN, oldshst, 1, opts); /* SHINSTDIN option */ - errflag = 0; + errflag &= ~ERRFLAG_ERROR; if (!exit_pending) retflag = 0; scriptname = old_scriptname; scriptfilename = old_scriptfilename; - free(cmdstack); + zfree(cmdstack, CMDSTACKSZ); cmdstack = ocs; cmdsp = ocsp; @@ -1608,8 +1645,7 @@ zsh_main(UNUSED(int argc), char **argv) emulate(zsh_name, 1, &emulation, opts); /* initialises most options */ opts[LOGINSHELL] = (**argv == '-'); opts[PRIVILEGED] = (getuid() != geteuid() || getgid() != getegid()); - opts[USEZLE] = 1; /* may be unset in init_io() */ - /* sets INTERACTIVE, SHINSTDIN and SINGLECOMMAND */ + /* sets ZLE, INTERACTIVE, SHINSTDIN and SINGLECOMMAND */ parseargs(argv, &runscript); SHTTY = -1; diff --git a/Src/input.c b/Src/input.c index 4ac7e6ec8..4a5bf89c6 100644 --- a/Src/input.c +++ b/Src/input.c @@ -56,8 +56,9 @@ * inpop(), which effectively flushes any unread input as well as restoring * the previous input state. * - * The internal flag INP_ALCONT shows that the stack element was pushed - * by an alias expansion; it should not be needed elsewhere. + * The internal flags INP_ALCONT and INP_HISTCONT show that the stack + * element was pushed by an alias or history expansion; they should not + * be needed elsewhere. * * The global variable inalmore is set to indicate aliases should * continue to be expanded because the last alias expansion ended @@ -179,12 +180,12 @@ shingetline(void) /* Get the next character from the input. * Will call inputline() to get a new line where necessary. */ - + /**/ int ingetc(void) { - int lastc; + int lastc = ' '; if (lexstop) return ' '; @@ -196,7 +197,7 @@ ingetc(void) continue; if (((inbufflags & INP_LINENO) || !strin) && lastc == '\n') lineno++; - return lastc; + break; } /* @@ -208,7 +209,7 @@ ingetc(void) */ if (!inbufct && (strin || errflag)) { lexstop = 1; - return ' '; + break; } /* If the next element down the input stack is a continuation of * this, use it. @@ -219,8 +220,10 @@ ingetc(void) } /* As a last resort, get some more input */ if (inputline()) - return ' '; + break; } + zshlex_raw_add(lastc); + return lastc; } /* Read a line from the current command stream and store it as input */ @@ -291,7 +294,8 @@ inputline(void) } if (errflag) { free(ingetcline); - return lexstop = errflag = 1; + errflag |= ERRFLAG_ERROR; + return lexstop = 1; } if (isset(VERBOSE)) { /* Output the whole line read so far. */ @@ -327,8 +331,38 @@ inputline(void) } } isfirstch = 1; - /* Put this into the input channel. */ - inputsetline(ingetcline, INP_FREE); + if ((inbufflags & INP_APPEND) && inbuf) { + /* + * We need new input but need to be able to back up + * over the old input, so append this line. + * Pushing the line onto the stack doesn't have the right + * effect. + * + * This is quite a simple and inefficient fix, but currently + * we only need it when backing up over a multi-line $((... + * that turned out to be a command substitution rather than + * a math substitution, which is a very special case. + * So it's not worth rewriting. + */ + char *oinbuf = inbuf; + int newlen = strlen(ingetcline); + int oldlen = (int)(inbufptr - inbuf) + inbufleft; + if (inbufflags & INP_FREE) { + inbuf = realloc(inbuf, oldlen + newlen + 1); + } else { + inbuf = zalloc(oldlen + newlen + 1); + memcpy(inbuf, oinbuf, oldlen); + } + inbufptr += inbuf - oinbuf; + strcpy(inbuf + oldlen, ingetcline); + free(ingetcline); + inbufleft += newlen; + inbufct += newlen; + inbufflags |= INP_FREE; + } else { + /* Put this into the input channel. */ + inputsetline(ingetcline, INP_FREE); + } return 0; } @@ -390,12 +424,14 @@ inungetc(int c) if (((inbufflags & INP_LINENO) || !strin) && c == '\n') lineno--; } -#ifdef DEBUG else if (!(inbufflags & INP_CONT)) { +#ifdef DEBUG /* Just for debugging */ fprintf(stderr, "Attempt to inungetc() at start of input.\n"); - } #endif + zerr("Garbled input at %c (binary file as commands?)", c); + return; + } else { /* * The character is being backed up from a previous input stack @@ -411,7 +447,8 @@ inungetc(int c) * we may need to restore an alias popped from the stack. * Note this may be a dummy (history expansion) entry. */ - if (inbufptr == inbufpush && inbufflags & INP_ALCONT) { + if (inbufptr == inbufpush && + (inbufflags & (INP_ALCONT|INP_HISTCONT))) { /* * Go back up the stack over all entries which were alias * expansions and were pushed with nothing remaining to read. @@ -420,11 +457,16 @@ inungetc(int c) if (instacktop->alias) instacktop->alias->inuse = 1; instacktop++; - } while ((instacktop->flags & INP_ALCONT) && !instacktop->bufleft); - inbufflags = INP_CONT|INP_ALIAS; + } while ((instacktop->flags & (INP_ALCONT|INP_HISTCONT)) + && !instacktop->bufleft); + if (inbufflags & INP_HISTCONT) + inbufflags = INP_CONT|INP_ALIAS|INP_HIST; + else + inbufflags = INP_CONT|INP_ALIAS; inbufleft = 0; inbuf = inbufptr = ""; } + zshlex_raw_back(); } } @@ -486,7 +528,7 @@ inpush(char *str, int flags, Alias inalias) instacktop->bufptr = inbufptr; instacktop->bufleft = inbufleft; instacktop->bufct = inbufct; - inbufflags &= ~INP_ALCONT; + inbufflags &= ~(INP_ALCONT|INP_HISTCONT); if (flags & (INP_ALIAS|INP_HIST)) { /* * Text is expansion for history or alias, so continue @@ -495,7 +537,10 @@ inpush(char *str, int flags, Alias inalias) * and mark alias as in use. */ flags |= INP_CONT|INP_ALIAS; - instacktop->flags = inbufflags | INP_ALCONT; + if (flags & INP_HIST) + instacktop->flags = inbufflags | INP_HISTCONT; + else + instacktop->flags = inbufflags | INP_ALCONT; if ((instacktop->alias = inalias)) inalias->inuse = 1; } else { @@ -533,6 +578,24 @@ inpush(char *str, int flags, Alias inalias) static void inpoptop(void) { + if (!lexstop) { + inbufflags &= ~(INP_ALCONT|INP_HISTCONT); + while (inbufptr > inbuf) { + inbufptr--; + inbufct++; + inbufleft++; + /* + * As elsewhere in input and history mechanisms: + * unwinding aliases and unwinding history have different + * implications as aliases are after the lexer while + * history is before, but they're both pushed onto + * the input stack. + */ + if ((inbufflags & (INP_ALIAS|INP_HIST)) == INP_ALIAS) + zshlex_raw_back(); + } + } + if (inbuf && (inbufflags & INP_FREE)) free(inbuf); @@ -544,7 +607,7 @@ inpoptop(void) inbufct = instacktop->bufct; inbufflags = instacktop->flags; - if (!(inbufflags & INP_ALCONT)) + if (!(inbufflags & (INP_ALCONT|INP_HISTCONT))) return; if (instacktop->alias) { diff --git a/Src/jobs.c b/Src/jobs.c index bd95afb7a..948f61b01 100644 --- a/Src/jobs.c +++ b/Src/jobs.c @@ -104,15 +104,6 @@ int prev_errflag, prev_breaks, errbrk_saved; /**/ int numpipestats, pipestats[MAX_PIPESTATS]; -/* - * The status associated with the process lastpid. - * -1 if not set and no associated lastpid - * -2 if lastpid is set and status isn't yet - * else the value returned by wait(). - */ -/**/ -long lastpid_status; - /* Diff two timevals for elapsed-time computations */ /**/ @@ -430,8 +421,10 @@ update_job(Job jn) for (pn = jn->procs; pn; pn = pn->next) { #ifdef WIFCONTINUED - if (WIFCONTINUED(pn->status)) + if (WIFCONTINUED(pn->status)) { + jn->stat &= ~STAT_STOPPED; pn->status = SP_RUNNING; + } #endif if (pn->status == SP_RUNNING) /* some processes in this job are running */ return; /* so no need to update job table entry */ @@ -518,7 +511,7 @@ update_job(Job jn) prev_errflag = errflag; } breaks = loops; - errflag = 1; + errflag |= ERRFLAG_INT; inerrflush(); } } else { @@ -535,7 +528,7 @@ update_job(Job jn) prev_errflag = errflag; } breaks = loops; - errflag = 1; + errflag |= ERRFLAG_INT; inerrflush(); } if (somestopped && jn->stat & STAT_SUPERJOB) @@ -590,7 +583,7 @@ update_job(Job jn) breaks = loops; } else { breaks = loops; - errflag = 1; + errflag |= ERRFLAG_INT; } check_cursh_sig(sig); } @@ -713,6 +706,8 @@ printtime(struct timeval *real, child_times_t *ti, char *desc) queue_signals(); if (!(s = getsparam("TIMEFMT"))) s = DEFAULT_TIMEFMT; + else + s = unmetafy(s, NULL); for (; *s; s++) if (*s == '%') @@ -1309,14 +1304,6 @@ addproc(pid_t pid, char *text, int aux, struct timeval *bgtime) { Process pn, *pnlist; - if (pid == lastpid && lastpid_status != -2L) { - /* - * The status for the previous lastpid is invalid. - * Presumably process numbers have wrapped. - */ - lastpid_status = -1L; - } - DPUTS(thisjob == -1, "No valid job in addproc."); pn = (Process) zshcalloc(sizeof *pn); pn->pid = pid; @@ -1459,12 +1446,19 @@ zwaitjob(int job, int wait_cmd) restore_queue_signals(q); return 128 + last_signal; } - /* Commenting this out makes ^C-ing a job started by a function - stop the whole function again. But I guess it will stop - something else from working properly, we have to find out - what this might be. --oberon + /* Commenting this out makes ^C-ing a job started by a function + stop the whole function again. But I guess it will stop + something else from working properly, we have to find out + what this might be. --oberon + + When attempting to separate errors and interrupts, we + assumed because of the previous comment it would be OK + to remove ERRFLAG_ERROR and leave ERRFLAG_INT set, since + that's the one related to ^C. But that doesn't work. + There's something more here we don't understand. --pws + + errflag = 0; */ - errflag = 0; */ if (subsh) { killjb(jn, SIGCONT); jn->stat &= ~STAT_STOPPED; @@ -1828,7 +1822,9 @@ static int hackspace; void init_jobs(char **argv, char **envp) { +#ifndef HAVE_SETPROCTITLE char *p, *q; +#endif size_t init_bytes = MAXJOBS_ALLOC*sizeof(struct job); /* @@ -1940,6 +1936,122 @@ maybeshrinkjobtab(void) unqueue_signals(); } +/* + * Definitions for the background process stuff recorded below. + * This would be more efficient as a hash, but + * - that's quite heavyweight for something not needed very often + * - we need some kind of ordering as POSIX allows us to limit + * the size of the list to the value of _SC_CHILD_MAX and clearly + * we want to clear the oldest first + * - cases with a long list of background jobs where the user doesn't + * wait for a large number, and then does wait for one (the only + * inefficient case) are rare + * - in the context of waiting for an external process, looping + * over a list isn't so very inefficient. + * Enough excuses already. + */ + +/* Data in the link list, a key (process ID) / value (exit status) pair. */ +struct bgstatus { + pid_t pid; + int status; +}; +typedef struct bgstatus *Bgstatus; +/* The list of those entries */ +LinkList bgstatus_list; +/* Count of entries. Reaches value of _SC_CHILD_MAX and stops. */ +long bgstatus_count; + +/* + * Remove and free a bgstatus entry. + */ +static void rembgstatus(LinkNode node) +{ + zfree(remnode(bgstatus_list, node), sizeof(struct bgstatus)); + bgstatus_count--; +} + +/* + * Record the status of a background process that exited so we + * can execute the builtin wait for it. + * + * We can't execute the wait builtin for something that exited in the + * foreground as it's not visible to the user, so don't bother recording. + */ + +/**/ +void +addbgstatus(pid_t pid, int status) +{ + static long child_max; + Bgstatus bgstatus_entry; + + if (!child_max) { +#ifdef _SC_CHILD_MAX + child_max = sysconf(_SC_CHILD_MAX); + if (!child_max) /* paranoia */ +#endif + { + /* Be inventive */ + child_max = 1024L; + } + } + + if (!bgstatus_list) { + bgstatus_list = znewlinklist(); + /* + * We're not always robust about memory failures, but + * this is pretty deep in the shell basics to be failing owing + * to memory, and a failure to wait is reported loudly, so test + * and fail silently here. + */ + if (!bgstatus_list) + return; + } + if (bgstatus_count == child_max) { + /* Overflow. List is in order, remove first */ + rembgstatus(firstnode(bgstatus_list)); + } + bgstatus_entry = (Bgstatus)zalloc(sizeof(*bgstatus_entry)); + if (!bgstatus_entry) { + /* See note above */ + return; + } + bgstatus_entry->pid = pid; + bgstatus_entry->status = status; + if (!zaddlinknode(bgstatus_list, bgstatus_entry)) { + zfree(bgstatus_entry, sizeof(*bgstatus_entry)); + return; + } + bgstatus_count++; +} + +/* + * See if pid has a recorded exit status. + * Note we make no guarantee that the PIDs haven't wrapped, so this + * may not be the right process. + * + * This is only used by wait, which must only work on each + * pid once, so we need to remove the entry if we find it. + */ + +static int getbgstatus(pid_t pid) +{ + LinkNode node; + Bgstatus bgstatus_entry; + + if (!bgstatus_list) + return -1; + for (node = firstnode(bgstatus_list); node; incnode(node)) { + bgstatus_entry = (Bgstatus)getdata(node); + if (bgstatus_entry->pid == pid) { + int status = bgstatus_entry->status; + rembgstatus(node); + return status; + } + } + return -1; +} /* bg, disown, fg, jobs, wait: most of the job control commands are * * here. They all take the same type of argument. Exception: wait can * @@ -2085,10 +2197,7 @@ bin_fg(char *name, char **argv, Options ops, int func) } if (retval == 0) retval = lastval2; - } else if (isset(POSIXJOBS) && - pid == lastpid && lastpid_status >= 0L) { - retval = (int)lastpid_status; - } else { + } else if ((retval = getbgstatus(pid)) < 0) { zwarnnam(name, "pid %d is not a child of this shell", pid); /* presumably lastval2 doesn't tell us a heck of a lot? */ retval = 1; @@ -2611,7 +2720,7 @@ findjobnam(const char *s) for (jobnum = maxjob; jobnum >= 0; jobnum--) if (!(jobtab[jobnum].stat & (STAT_SUBJOB | STAT_NOPRINT)) && jobtab[jobnum].stat && jobtab[jobnum].procs && jobnum != thisjob && - jobtab[jobnum].procs->text && strpfx(s, jobtab[jobnum].procs->text)) + jobtab[jobnum].procs->text[0] && strpfx(s, jobtab[jobnum].procs->text)) return jobnum; return -1; } @@ -2627,7 +2736,7 @@ acquire_pgrp(void) long ttpgrp; sigset_t blockset, oldset; - if ((mypgrp = GETPGRP()) > 0) { + if ((mypgrp = GETPGRP()) >= 0) { long lastpgrp = mypgrp; sigemptyset(&blockset); sigaddset(&blockset, SIGTTIN); @@ -2672,8 +2781,11 @@ void release_pgrp(void) { if (origpgrp != mypgrp) { - attachtty(origpgrp); - setpgrp(0, origpgrp); + /* in linux pid namespaces, origpgrp may never have been set */ + if (origpgrp) { + attachtty(origpgrp); + setpgrp(0, origpgrp); + } mypgrp = origpgrp; } } @@ -35,7 +35,7 @@ /* tokens */ /**/ -mod_export char ztokens[] = "#$^*()$=|{}[]`<>>?~`,'\"\\\\"; +mod_export char ztokens[] = "#$^*(())$=|{}[]`<>>?~`,'\"\\\\"; /* parts of the current token */ @@ -90,6 +90,12 @@ int inalmore; int nocorrect; /* + * TBD: the following exported variables are part of the non-interface + * with ZLE for completion. They are poorly named and the whole + * scheme is incredibly brittle. One piece of robustness is applied: + * the variables are only set if LEXFLAGS_ZLE is set. Improvements + * should therefore concentrate on areas with this flag set. + * * Cursor position and line length in zle when the line is * metafied for access from the main shell. */ @@ -113,6 +119,16 @@ mod_export int addedx; /**/ mod_export int wb, we; +/**/ +mod_export int wordbeg; + +/**/ +mod_export int parbegin; + +/**/ +mod_export int parend; + + /* 1 if aliases should not be expanded */ /**/ @@ -134,19 +150,20 @@ mod_export int noaliases; /**/ mod_export int lexflags; -/**/ -mod_export int wordbeg; +/* don't recognize comments */ /**/ -mod_export int parbegin; +mod_export int nocomments; + +/* add raw input characters while parsing command substitution */ /**/ -mod_export int parend; +static int lex_add_raw; -/* don't recognize comments */ +/* variables associated with the above */ -/**/ -mod_export int nocomments; +static char *tokstr_raw; +static struct lexbufstate lexbuf_raw; /* text of punctuation tokens */ @@ -190,205 +207,58 @@ mod_export char *tokstrings[WHILE + 1] = { /* lexical state */ static int dbparens; -static int len = 0, bsiz = 256; -static char *bptr; - -struct lexstack { - struct lexstack *next; - - int incmdpos; - int incond; - int incasepat; - int dbparens; - int isfirstln; - int isfirstch; - int histactive; - int histdone; - int lexflags; - int stophist; - int hlinesz; - char *hline; - char *hptr; - enum lextok tok; - int isnewlin; - char *tokstr; - char *zshlextext; - char *bptr; - int bsiz; - int len; - short *chwords; - int chwordlen; - int chwordpos; - int hwgetword; - int lexstop; - struct heredocs *hdocs; - int (*hgetc) _((void)); - void (*hungetc) _((int)); - void (*hwaddc) _((int)); - void (*hwbegin) _((int)); - void (*hwend) _((void)); - void (*addtoline) _((int)); - - int eclen, ecused, ecnpats; - Wordcode ecbuf; - Eccstr ecstrs; - int ecsoffs, ecssub, ecnfunc; - - unsigned char *cstack; - int csp; - zlong toklineno; -}; - -static struct lexstack *lstack = NULL; +static struct lexbufstate lexbuf = { NULL, 256, 0 }; -/* save the lexical state */ - -/* is this a hack or what? */ +/* save lexical context */ /**/ -mod_export void -lexsave(void) +void +lex_context_save(struct lex_stack *ls, int toplevel) { - struct lexstack *ls; + (void)toplevel; - ls = (struct lexstack *)malloc(sizeof(struct lexstack)); - - ls->incmdpos = incmdpos; - ls->incond = incond; - ls->incasepat = incasepat; ls->dbparens = dbparens; ls->isfirstln = isfirstln; ls->isfirstch = isfirstch; - ls->histactive = histactive; - ls->histdone = histdone; ls->lexflags = lexflags; - ls->stophist = stophist; - stophist = 0; - if (!lstack) { - /* top level, make this version visible to ZLE */ - zle_chline = chline; - /* ensure line stored is NULL-terminated */ - if (hptr) - *hptr = '\0'; - } - ls->hline = chline; - chline = NULL; - ls->hptr = hptr; - hptr = NULL; - ls->hlinesz = hlinesz; - ls->cstack = cmdstack; - ls->csp = cmdsp; - cmdstack = (unsigned char *)zalloc(CMDSTACKSZ); + ls->tok = tok; - ls->isnewlin = isnewlin; ls->tokstr = tokstr; ls->zshlextext = zshlextext; - ls->bptr = bptr; - tokstr = zshlextext = bptr = NULL; - ls->bsiz = bsiz; - bsiz = 256; - ls->len = len; - ls->chwords = chwords; - ls->chwordlen = chwordlen; - ls->chwordpos = chwordpos; - ls->hwgetword = hwgetword; + ls->lexbuf = lexbuf; + ls->lex_add_raw = lex_add_raw; + ls->tokstr_raw = tokstr_raw; + ls->lexbuf_raw = lexbuf_raw; ls->lexstop = lexstop; - ls->hdocs = hdocs; - ls->hgetc = hgetc; - ls->hungetc = hungetc; - ls->hwaddc = hwaddc; - ls->hwbegin = hwbegin; - ls->hwend = hwend; - ls->addtoline = addtoline; - ls->eclen = eclen; - ls->ecused = ecused; - ls->ecnpats = ecnpats; - ls->ecbuf = ecbuf; - ls->ecstrs = ecstrs; - ls->ecsoffs = ecsoffs; - ls->ecssub = ecssub; - ls->ecnfunc = ecnfunc; ls->toklineno = toklineno; - cmdsp = 0; - inredir = 0; - hdocs = NULL; - histactive = 0; - ecbuf = NULL; - - ls->next = lstack; - lstack = ls; + + tokstr = zshlextext = lexbuf.ptr = NULL; + lexbuf.siz = 256; + tokstr_raw = lexbuf_raw.ptr = NULL; + lexbuf_raw.siz = lexbuf_raw.len = lex_add_raw = 0; } -/* restore lexical state */ +/* restore lexical context */ /**/ mod_export void -lexrestore(void) +lex_context_restore(const struct lex_stack *ls, int toplevel) { - struct lexstack *ln = lstack; - - DPUTS(!lstack, "BUG: lexrestore() without lexsave()"); - - queue_signals(); - lstack = lstack->next; - - if (!lstack) { - /* Back to top level: don't need special ZLE value */ - DPUTS(ln->hline != zle_chline, "BUG: Ouch, wrong chline for ZLE"); - zle_chline = NULL; - } - - incmdpos = ln->incmdpos; - incond = ln->incond; - incasepat = ln->incasepat; - dbparens = ln->dbparens; - isfirstln = ln->isfirstln; - isfirstch = ln->isfirstch; - histactive = ln->histactive; - histdone = ln->histdone; - lexflags = ln->lexflags; - stophist = ln->stophist; - chline = ln->hline; - hptr = ln->hptr; - if (cmdstack) - zfree(cmdstack, CMDSTACKSZ); - cmdstack = ln->cstack; - cmdsp = ln->csp; - tok = ln->tok; - isnewlin = ln->isnewlin; - tokstr = ln->tokstr; - zshlextext = ln->zshlextext; - bptr = ln->bptr; - bsiz = ln->bsiz; - len = ln->len; - chwords = ln->chwords; - chwordlen = ln->chwordlen; - chwordpos = ln->chwordpos; - hwgetword = ln->hwgetword; - lexstop = ln->lexstop; - hdocs = ln->hdocs; - hgetc = ln->hgetc; - hungetc = ln->hungetc; - hwaddc = ln->hwaddc; - hwbegin = ln->hwbegin; - hwend = ln->hwend; - addtoline = ln->addtoline; - if (ecbuf) - zfree(ecbuf, eclen); - eclen = ln->eclen; - ecused = ln->ecused; - ecnpats = ln->ecnpats; - ecbuf = ln->ecbuf; - ecstrs = ln->ecstrs; - ecsoffs = ln->ecsoffs; - ecssub = ln->ecssub; - ecnfunc = ln->ecnfunc; - hlinesz = ln->hlinesz; - toklineno = ln->toklineno; - errflag = 0; - free(ln); - - unqueue_signals(); + (void)toplevel; + + dbparens = ls->dbparens; + isfirstln = ls->isfirstln; + isfirstch = ls->isfirstch; + lexflags = ls->lexflags; + tok = ls->tok; + tokstr = ls->tokstr; + zshlextext = ls->zshlextext; + lexbuf = ls->lexbuf; + lex_add_raw = ls->lex_add_raw; + tokstr_raw = ls->tokstr_raw; + lexbuf_raw = ls->lexbuf_raw; + lexstop = ls->lexstop; + toklineno = ls->toklineno; } /**/ @@ -563,9 +433,7 @@ initlextabs(void) void lexinit(void) { - incond = incasepat = nocorrect = - infor = dbparens = lexstop = 0; - incmdpos = 1; + nocorrect = dbparens = lexstop = 0; tok = ENDINPUT; } @@ -575,17 +443,18 @@ lexinit(void) void add(int c) { - *bptr++ = c; - if (bsiz == ++len) { - int newbsiz = bsiz * 2; + *lexbuf.ptr++ = c; + if (lexbuf.siz == ++lexbuf.len) { + int newbsiz = lexbuf.siz * 2; - if (newbsiz > inbufct && inbufct > bsiz) + if (newbsiz > inbufct && inbufct > lexbuf.siz) newbsiz = inbufct; - bptr = len + (tokstr = (char *)hrealloc(tokstr, bsiz, newbsiz)); + tokstr = (char *)hrealloc(tokstr, lexbuf.siz, newbsiz); + lexbuf.ptr = tokstr + lexbuf.len; /* len == bsiz, so bptr is at the start of newly allocated memory */ - memset(bptr, 0, newbsiz - bsiz); - bsiz = newbsiz; + memset(lexbuf.ptr, 0, newbsiz - lexbuf.siz); + lexbuf.siz = newbsiz; } } @@ -604,48 +473,61 @@ add(int c) } \ } +enum { + CMD_OR_MATH_CMD, + CMD_OR_MATH_MATH, + CMD_OR_MATH_ERR +}; + /* - * Return 1 for math, 0 for a command, 2 for an error. If it couldn't be + * Return one of the above. If it couldn't be * parsed as math, but there was no gross error, it's a command. */ static int cmd_or_math(int cs_type) { - int oldlen = len; + int oldlen = lexbuf.len; int c; + int oinflags = inbufflags; cmdpush(cs_type); + inbufflags |= INP_APPEND; c = dquote_parse(')', 0); + if (!(oinflags & INP_APPEND)) + inbufflags &= ~INP_APPEND; cmdpop(); - *bptr = '\0'; + *lexbuf.ptr = '\0'; if (!c) { /* Successfully parsed, see if it was math */ c = hgetc(); if (c == ')') - return 1; /* yes */ + return CMD_OR_MATH_MATH; /* yes */ hungetc(c); lexstop = 0; c = ')'; } else if (lexstop) { /* we haven't got anything to unget */ - return 2; + return CMD_OR_MATH_ERR; } /* else unsuccessful: unget the whole thing */ hungetc(c); lexstop = 0; - while (len > oldlen) { - len--; - hungetc(itok(*--bptr) ? ztokens[*bptr - Pound] : *bptr); + while (lexbuf.len > oldlen && !(errflag & ERRFLAG_ERROR)) { + lexbuf.len--; + hungetc(itok(*--lexbuf.ptr) ? + ztokens[*lexbuf.ptr - Pound] : *lexbuf.ptr); } + if (errflag) + return CMD_OR_MATH_ERR; hungetc('('); - return 0; + return errflag ? CMD_OR_MATH_ERR : CMD_OR_MATH_CMD; } /* * Parse either a $(( ... )) or a $(...) - * Return 0 on success, 1 on failure. + * Return the same as cmd_or_math(). */ static int cmd_or_math_sub(void) @@ -653,21 +535,23 @@ cmd_or_math_sub(void) int c = hgetc(), ret; if (c == '(') { + int lexpos = (int)(lexbuf.ptr - tokstr); add(Inpar); add('('); - if ((ret = cmd_or_math(CS_MATHSUBST)) == 1) { + if ((ret = cmd_or_math(CS_MATHSUBST)) == CMD_OR_MATH_MATH) { + tokstr[lexpos] = Inparmath; add(')'); - return 0; + return CMD_OR_MATH_MATH; } - if (ret == 2) - return 1; - bptr -= 2; - len -= 2; + if (ret == CMD_OR_MATH_ERR) + return CMD_OR_MATH_ERR; + lexbuf.ptr -= 2; + lexbuf.len -= 2; } else { hungetc(c); lexstop = 0; } - return skipcomm(); + return skipcomm() ? CMD_OR_MATH_ERR : CMD_OR_MATH_CMD; } /* Check whether we're looking at valid numeric globbing syntax * @@ -722,17 +606,18 @@ gettok(void) if (lexstop) return (errflag) ? LEXERR : ENDINPUT; isfirstln = 0; - wordbeg = inbufct - (qbang && c == bangchar); + if ((lexflags & LEXFLAGS_ZLE)) + wordbeg = inbufct - (qbang && c == bangchar); hwbegin(-1-(qbang && c == bangchar)); /* word includes the last character read and possibly \ before ! */ if (dbparens) { - len = 0; - bptr = tokstr = (char *) hcalloc(bsiz = LEX_HEAP_SIZE); + lexbuf.len = 0; + lexbuf.ptr = tokstr = (char *) hcalloc(lexbuf.siz = LEX_HEAP_SIZE); hungetc(c); cmdpush(CS_MATH); c = dquote_parse(infor ? ';' : ')', 0); cmdpop(); - *bptr = '\0'; + *lexbuf.ptr = '\0'; if (!c && infor) { infor--; return DINPAR; @@ -780,8 +665,9 @@ gettok(void) * newlines being inserted into the history. */ if (lexflags & LEXFLAGS_COMMENTS_KEEP) { - len = 0; - bptr = tokstr = (char *)hcalloc(bsiz = LEX_HEAP_SIZE); + lexbuf.len = 0; + lexbuf.ptr = tokstr = + (char *)hcalloc(lexbuf.siz = LEX_HEAP_SIZE); add(c); } hwend(); @@ -796,7 +682,7 @@ gettok(void) peek = LEXERR; else { if (lexflags & LEXFLAGS_COMMENTS_KEEP) { - *bptr = '\0'; + *lexbuf.ptr = '\0'; if (!lexstop) hungetc(c); peek = STRING; @@ -882,13 +768,14 @@ gettok(void) return DINPAR; } if (incmdpos || (isset(SHGLOB) && !isset(KSHGLOB))) { - len = 0; - bptr = tokstr = (char *) hcalloc(bsiz = LEX_HEAP_SIZE); + lexbuf.len = 0; + lexbuf.ptr = tokstr = (char *) + hcalloc(lexbuf.siz = LEX_HEAP_SIZE); switch (cmd_or_math(CS_MATH)) { - case 1: + case CMD_OR_MATH_MATH: return DINPAR; - case 0: + case CMD_OR_MATH_CMD: /* * Not math, so we don't return the contents * as a string in this case. @@ -1032,8 +919,8 @@ gettokstr(int c, int sub) peek = STRING; if (!sub) { - len = 0; - bptr = tokstr = (char *) hcalloc(bsiz = LEX_HEAP_SIZE); + lexbuf.len = 0; + lexbuf.ptr = tokstr = (char *) hcalloc(lexbuf.siz = LEX_HEAP_SIZE); } for (;;) { int act; @@ -1069,7 +956,7 @@ gettokstr(int c, int sub) if (fdpar) { /* this is a single word `( )', treat as INOUTPAR */ add(c); - *bptr = '\0'; + *lexbuf.ptr = '\0'; return INOUTPAR; } if ((sub || in_brace_param) && isset(SHGLOB)) @@ -1108,12 +995,19 @@ gettokstr(int c, int sub) c = Outbrack; } else if (e == '(') { add(String); - c = cmd_or_math_sub(); - if (c) { + switch (cmd_or_math_sub()) { + case CMD_OR_MATH_CMD: + c = Outpar; + break; + + case CMD_OR_MATH_MATH: + c = Outparmath; + break; + + default: peek = LEXERR; goto brk; } - c = Outpar; } else { if (e == '{') { add(c); @@ -1144,9 +1038,9 @@ gettokstr(int c, int sub) if (isset(SHGLOB)) { if (sub || in_brace_param) break; - if (incasepat && !len) + if (incasepat && !lexbuf.len) return INPAR; - if (!isset(KSHGLOB) && len) + if (!isset(KSHGLOB) && lexbuf.len) goto brk; } if (!in_brace_param) { @@ -1203,9 +1097,9 @@ gettokstr(int c, int sub) if (isset(IGNOREBRACES) || sub) c = '{'; else { - if (!len && incmdpos) { + if (!lexbuf.len && incmdpos) { add('{'); - *bptr = '\0'; + *lexbuf.ptr = '\0'; return STRING; } if (in_brace_param) { @@ -1291,23 +1185,23 @@ gettokstr(int c, int sub) incmdpos && !bct && !brct) { char *t = tokstr; if (idigit(*t)) - while (++t < bptr && idigit(*t)); + while (++t < lexbuf.ptr && idigit(*t)); else { - int sav = *bptr; - *bptr = '\0'; + int sav = *lexbuf.ptr; + *lexbuf.ptr = '\0'; t = itype_end(t, IIDENT, 0); - if (t < bptr) { + if (t < lexbuf.ptr) { skipparens(Inbrack, Outbrack, &t); } else { - *bptr = sav; + *lexbuf.ptr = sav; } } if (*t == '+') t++; - if (t == bptr) { + if (t == lexbuf.ptr) { e = hgetc(); if (e == '(' && incmdpos) { - *bptr = '\0'; + *lexbuf.ptr = '\0'; return ENVARRAY; } hungetc(e); @@ -1326,13 +1220,25 @@ gettokstr(int c, int sub) c = hgetc(); if (!lexstop) continue; - } else + } else { add(Bnull); + if (c == STOUC(Meta)) { + c = hgetc(); +#ifdef DEBUG + if (lexstop) { + fputs("BUG: input terminated by Meta\n", stderr); + fflush(stderr); + goto brk; + } +#endif + add(Meta); + } + } if (lexstop) goto brk; break; case LX2_QUOTE: { - int strquote = (len && bptr[-1] == String); + int strquote = (lexbuf.len && lexbuf.ptr[-1] == String); add(Snull); cmdpush(CS_QUOTE); @@ -1355,8 +1261,8 @@ gettokstr(int c, int sub) else add('\\'); } else if (!sub && isset(CSHJUNKIEQUOTES) && c == '\n') { - if (bptr[-1] == '\\') - bptr--, len--; + if (lexbuf.ptr[-1] == '\\') + lexbuf.ptr--, lexbuf.len--; else break; } @@ -1439,6 +1345,8 @@ gettokstr(int c, int sub) break; } brk: + if (errflag) + return LEXERR; hungetc(c); if (unmatched) zerr("unmatched %c", unmatched); @@ -1446,15 +1354,16 @@ gettokstr(int c, int sub) while(bct-- >= in_brace_param) cmdpop(); zerr("closing brace expected"); - } else if (unset(IGNOREBRACES) && !sub && len > 1 && - peek == STRING && bptr[-1] == '}' && bptr[-2] != Bnull) { + } else if (unset(IGNOREBRACES) && !sub && lexbuf.len > 1 && + peek == STRING && lexbuf.ptr[-1] == '}' && + lexbuf.ptr[-2] != Bnull) { /* hack to get {foo} command syntax work */ - bptr--; - len--; + lexbuf.ptr--; + lexbuf.len--; lexstop = 0; hungetc('}'); } - *bptr = '\0'; + *lexbuf.ptr = '\0'; DPUTS(cmdsp != ocmdsp, "BUG: gettok: cmdstack changed."); return peek; } @@ -1508,8 +1417,19 @@ dquote_parse(char endchar, int sub) c = hgetc(); if (c == '(') { add(Qstring); - err = cmd_or_math_sub(); - c = Outpar; + switch (cmd_or_math_sub()) { + case CMD_OR_MATH_CMD: + c = Outpar; + break; + + case CMD_OR_MATH_MATH: + c = Outparmath; + break; + + default: + err = 1; + break; + } } else if (c == '[') { add(String); add(Inbrack); @@ -1617,44 +1537,58 @@ dquote_parse(char endchar, int sub) return err; } -/* Tokenize a string given in s. Parsing is done as in double * - * quotes. This is usually called before singsub(). */ +/* + * Tokenize a string given in s. Parsing is done as in double + * quotes. This is usually called before singsub(). + * + * parsestr() is noisier, reporting an error if the parse failed. + * + * On entry, *s must point to a string allocated from the stack of + * exactly the right length, i.e. strlen(*s) + 1, as the string + * is used as the lexical token string whose memory management + * demands this. Usually the input string will therefore be + * the result of an immediately preceding dupstring(). + */ /**/ mod_export int -parsestr(char *s) +parsestr(char **s) { int err; if ((err = parsestrnoerr(s))) { - untokenize(s); - if (err > 32 && err < 127) - zerr("parse error near `%c'", err); - else - zerr("parse error"); + untokenize(*s); + if (!(errflag & ERRFLAG_INT)) { + if (err > 32 && err < 127) + zerr("parse error near `%c'", err); + else + zerr("parse error"); + } } return err; } /**/ mod_export int -parsestrnoerr(char *s) +parsestrnoerr(char **s) { - int l = strlen(s), err; + int l = strlen(*s), err; - lexsave(); - untokenize(s); - inpush(dupstring(s), 0, NULL); + zcontext_save(); + untokenize(*s); + inpush(dupstring(*s), 0, NULL); strinbeg(0); - len = 0; - bptr = tokstr = s; - bsiz = l + 1; + lexbuf.len = 0; + lexbuf.ptr = tokstr = *s; + lexbuf.siz = l + 1; err = dquote_parse('\0', 1); - *bptr = '\0'; + if (tokstr) + *s = tokstr; + *lexbuf.ptr = '\0'; strinend(); inpop(); DPUTS(cmdsp, "BUG: parsestr: cmdstack not empty."); - lexrestore(); + zcontext_restore(); return err; } @@ -1673,27 +1607,27 @@ parse_subscript(char *s, int sub, int endchar) if (!*s || *s == endchar) return 0; - lexsave(); + zcontext_save(); untokenize(t = dupstring(s)); inpush(t, 0, NULL); strinbeg(0); - len = 0; - bptr = tokstr = s; - bsiz = l + 1; + lexbuf.len = 0; + lexbuf.ptr = tokstr = s; + lexbuf.siz = l + 1; err = dquote_parse(endchar, sub); if (err) { - err = *bptr; - *bptr = '\0'; + err = *lexbuf.ptr; + *lexbuf.ptr = '\0'; untokenize(s); - *bptr = err; + *lexbuf.ptr = err; s = NULL; } else { - s = bptr; + s = lexbuf.ptr; } strinend(); inpop(); DPUTS(cmdsp, "BUG: parse_subscript: cmdstack not empty."); - lexrestore(); + zcontext_restore(); return s; } @@ -1711,29 +1645,30 @@ parse_subst_string(char *s) if (!*s || !strcmp(s, nulstring)) return 0; - lexsave(); + zcontext_save(); untokenize(s); inpush(dupstring(s), 0, NULL); strinbeg(0); - len = 0; - bptr = tokstr = s; - bsiz = l + 1; + lexbuf.len = 0; + lexbuf.ptr = tokstr = s; + lexbuf.siz = l + 1; c = hgetc(); ctok = gettokstr(c, 1); err = errflag; strinend(); inpop(); DPUTS(cmdsp, "BUG: parse_subst_string: cmdstack not empty."); - lexrestore(); - errflag = err; + zcontext_restore(); + /* Keep any interrupt error status */ + errflag = err | (errflag & ERRFLAG_INT); if (ctok == LEXERR) { untokenize(s); return 1; } #ifdef DEBUG /* - * Historical note: we used to check here for olen (the value of len - * before lexrestore()) == l, but that's not necessarily the case if + * Historical note: we used to check here for olen (the value of lexbuf.len + * before zcontext_restore()) == l, but that's not necessarily the case if * we stripped an RCQUOTE. */ if (ctok != STRING || (errflag && !noerrs)) { @@ -1787,7 +1722,7 @@ parse_subst_string(char *s) /* Called below to report word positions. */ /**/ -mod_export void +static void gotword(void) { we = zlemetall + 1 - inbufct + (addedx == 2 ? 1 : 0); @@ -1797,13 +1732,62 @@ gotword(void) } } +/* Check if current lex text matches an alias: 1 if so, else 0 */ + +static int +checkalias(void) +{ + Alias an; + + if (!zshlextext) + return 0; + + if (!noaliases && isset(ALIASESOPT) && + (!isset(POSIXALIASES) || + (tok == STRING && !reswdtab->getnode(reswdtab, zshlextext)))) { + char *suf; + + an = (Alias) aliastab->getnode(aliastab, zshlextext); + if (an && !an->inuse && + ((an->node.flags & ALIAS_GLOBAL) || + (incmdpos && tok == STRING) || inalmore)) { + if (!lexstop) { + /* + * Tokens that don't require a space after, get one, + * because they are treated as if preceded by one. + */ + int c = hgetc(); + hungetc(c); + if (!iblank(c)) + inpush(" ", INP_ALIAS, 0); + } + inpush(an->text, INP_ALIAS, an); + if (an->text[0] == ' ' && !(an->node.flags & ALIAS_GLOBAL)) + aliasspaceflag = 1; + lexstop = 0; + return 1; + } + if ((suf = strrchr(zshlextext, '.')) && suf[1] && + suf > zshlextext && suf[-1] != Meta && + (an = (Alias)sufaliastab->getnode(sufaliastab, suf+1)) && + !an->inuse && incmdpos) { + inpush(dupstring(zshlextext), INP_ALIAS, NULL); + inpush(" ", INP_ALIAS, NULL); + inpush(an->text, INP_ALIAS, an); + lexstop = 0; + return 1; + } + } + + return 0; +} + /* expand aliases and reserved words */ /**/ int exalias(void) { - Alias an; Reswd rw; hwend(); @@ -1815,7 +1799,9 @@ exalias(void) if (!tokstr) { zshlextext = tokstrings[tok]; - return 0; + if (tok == NEWLIN) + return 0; + return checkalias(); } else { VARARR(char, copy, (strlen(tokstr) + 1)); @@ -1841,34 +1827,10 @@ exalias(void) if (tok == STRING) { /* Check for an alias */ - if (!noaliases && isset(ALIASESOPT) && - (!isset(POSIXALIASES) || - !reswdtab->getnode(reswdtab, zshlextext))) { - char *suf; - - an = (Alias) aliastab->getnode(aliastab, zshlextext); - if (an && !an->inuse && - ((an->node.flags & ALIAS_GLOBAL) || incmdpos || inalmore)) { - inpush(an->text, INP_ALIAS, an); - if (an->text[0] == ' ' && !(an->node.flags & ALIAS_GLOBAL)) - aliasspaceflag = 1; - lexstop = 0; - if (zshlextext == copy) - zshlextext = tokstr; - return 1; - } - if ((suf = strrchr(zshlextext, '.')) && suf[1] && - suf > zshlextext && suf[-1] != Meta && - (an = (Alias)sufaliastab->getnode(sufaliastab, suf+1)) && - !an->inuse && incmdpos) { - inpush(dupstring(zshlextext), INP_ALIAS, NULL); - inpush(" ", INP_ALIAS, NULL); - inpush(an->text, INP_ALIAS, an); - lexstop = 0; - if (zshlextext == copy) - zshlextext = tokstr; - return 1; - } + if ((zshlextext != copy || !isset(POSIXALIASES)) && checkalias()) { + if (zshlextext == copy) + zshlextext = tokstr; + return 1; } /* Then check for a reserved word */ @@ -1892,12 +1854,68 @@ exalias(void) return 0; } -/* skip (...) */ +/**/ +void +zshlex_raw_add(int c) +{ + if (!lex_add_raw) + return; + + *lexbuf_raw.ptr++ = c; + if (lexbuf_raw.siz == ++lexbuf_raw.len) { + int newbsiz = lexbuf_raw.siz * 2; + + tokstr_raw = (char *)hrealloc(tokstr_raw, lexbuf_raw.siz, newbsiz); + lexbuf_raw.ptr = tokstr_raw + lexbuf_raw.len; + memset(lexbuf_raw.ptr, 0, newbsiz - lexbuf_raw.siz); + lexbuf_raw.siz = newbsiz; + } +} + +/**/ +void +zshlex_raw_back(void) +{ + if (!lex_add_raw) + return; + lexbuf_raw.ptr--; + lexbuf_raw.len--; +} + +/**/ +int +zshlex_raw_mark(int offset) +{ + if (!lex_add_raw) + return 0; + return lexbuf_raw.len + offset; +} + +/**/ +void +zshlex_raw_back_to_mark(int mark) +{ + if (!lex_add_raw) + return; + lexbuf_raw.ptr = tokstr_raw + mark; + lexbuf_raw.len = mark; +} + +/* + * Skip (...) for command-style substitutions: $(...), <(...), >(...) + * + * In order to ensure we don't stop at closing parentheses with + * some other syntactic significance, we'll parse the input until + * we find an unmatched closing parenthesis. However, we'll throw + * away the result of the parsing and just keep the string we've built + * up on the way. + */ /**/ static int skipcomm(void) { +#ifdef ZSH_OLD_SKIPCOMM int pct = 1, c, start = 1; cmdpush(CS_CMDSUBST); @@ -1922,7 +1940,7 @@ skipcomm(void) c = hgetc(); break; case '\'': { - int strquote = bptr[-1] == '$'; + int strquote = lexbuf.ptr[-1] == '$'; add(c); STOPHIST while ((c = hgetc()) != '\'' && !lexstop) { @@ -1968,4 +1986,114 @@ skipcomm(void) SETPAREND cmdpop(); return lexstop; +#else + char *new_tokstr; + int new_lexstop, new_lex_add_raw; + struct lexbufstate new_lexbuf; + + cmdpush(CS_CMDSUBST); + SETPARBEGIN + add(Inpar); + + new_lex_add_raw = lex_add_raw + 1; + if (!lex_add_raw) { + /* + * We'll combine the string so far with the input + * read in for the command substitution. To do this + * we'll just propagate the current tokstr etc. as the + * variables used for adding raw input, and + * ensure we swap those for the real tokstr etc. at the end. + * + * However, we need to save and restore the rest of the + * lexical and parse state as we're effectively parsing + * an internal string. Because we're still parsing it from + * the original input source (we have to --- we don't know + * when to stop inputting it otherwise and can't rely on + * the input being recoverable until we've read it) we need + * to keep the same history context. + */ + new_tokstr = tokstr; + new_lexbuf = lexbuf; + + zcontext_save_partial(ZCONTEXT_LEX|ZCONTEXT_PARSE); + hist_in_word(1); + } else { + /* + * Set up for nested command subsitution, however + * we don't actually need the string until we get + * back to the top level and recover the lot. + * The $() body just appears empty. + * + * We do need to propagate the raw variables which would + * otherwise by cleared, though. + */ + new_tokstr = tokstr_raw; + new_lexbuf = lexbuf_raw; + + zcontext_save_partial(ZCONTEXT_LEX|ZCONTEXT_PARSE); + } + tokstr_raw = new_tokstr; + lexbuf_raw = new_lexbuf; + lex_add_raw = new_lex_add_raw; + /* + * Don't do any ZLE specials down here: they're only needed + * when we return the string from the recursive parse. + * (TBD: this probably means we should be initialising lexflags + * more consistently.) + * + * Note that in that case we're still using the ZLE line reading + * function at the history layer --- this is consistent with the + * intention of maintaining the history and input layers across + * the recursive parsing. + */ + lexflags &= ~LEXFLAGS_ZLE; + + if (!parse_event(OUTPAR) || tok != OUTPAR) + lexstop = 1; + /* Outpar lexical token gets added in caller if present */ + + /* + * We're going to keep the full raw input string + * as the current token string after popping the stack. + */ + new_tokstr = tokstr_raw; + new_lexbuf = lexbuf_raw; + /* + * We're also going to propagate the lexical state: + * if we couldn't parse the command substitution we + * can't continue. + */ + new_lexstop = lexstop; + + zcontext_restore_partial(ZCONTEXT_LEX|ZCONTEXT_PARSE); + + if (lex_add_raw) { + /* + * Keep going, so retain the raw variables. + */ + tokstr_raw = new_tokstr; + lexbuf_raw = new_lexbuf; + } else { + if (!new_lexstop) { + /* Ignore the ')' added on input */ + new_lexbuf.len--; + *--new_lexbuf.ptr = '\0'; + } + + /* + * Convince the rest of lex.c we were examining a string + * all along. + */ + tokstr = new_tokstr; + lexbuf = new_lexbuf; + lexstop = new_lexstop; + hist_in_word(0); + } + + if (!lexstop) + SETPAREND + cmdpop(); + + return lexstop; +#endif } diff --git a/Src/linklist.c b/Src/linklist.c index 1e364fb4e..3aa8125d9 100644 --- a/Src/linklist.c +++ b/Src/linklist.c @@ -118,6 +118,8 @@ znewlinklist(void) LinkList list; list = (LinkList) zalloc(sizeof *list); + if (!list) + return NULL; list->list.first = NULL; list->list.last = &list->node; list->list.flags = 0; @@ -152,6 +154,8 @@ zinsertlinknode(LinkList list, LinkNode node, void *dat) tmp = node->next; node->next = new = (LinkNode) zalloc(sizeof *tmp); + if (!new) + return NULL; new->prev = node; new->dat = dat; new->next = tmp; diff --git a/Src/loop.c b/Src/loop.c index 2f639fd5a..d025fbb9f 100644 --- a/Src/loop.c +++ b/Src/loop.c @@ -102,7 +102,10 @@ execfor(Estate state, int do_exec) addlinknode(args, dupstring(*x)); } } - /* lastval = 0; */ + + if (!args || empty(args)) + lastval = 0; + loops++; pushheap(); cmdpush(CS_FOR); @@ -238,10 +241,10 @@ execselect(Estate state, UNUSED(int do_exec)) } if (!args || empty(args)) { state->pc = end; - return 1; + return 0; } loops++; - /* lastval = 0; */ + pushheap(); cmdpush(CS_SELECT); usezle = interact && SHTTY != -1 && isset(USEZLE); @@ -259,13 +262,14 @@ execselect(Estate state, UNUSED(int do_exec)) 0, ZLCON_SELECT); if (errflag) str = NULL; - errflag = oef; + /* Keep any user interrupt error status */ + errflag = oef | (errflag & ERRFLAG_INT); } else { str = promptexpand(prompt3, 0, NULL, NULL, NULL); zputs(str, stderr); free(str); fflush(stderr); - str = fgets(zalloc(256), 256, inp); + str = fgets(zhalloc(256), 256, inp); } } else str = (char *)getlinknode(bufstack); @@ -518,14 +522,17 @@ execif(Estate state, int do_exec) s = 1; state->pc = next; } - noerrexit = olderrexit; if (run) { + /* we need to ignore lastval until we reach execcmd() */ + noerrexit = olderrexit ? olderrexit : lastval ? 2 : 0; cmdpush(run == 2 ? CS_ELSE : (s ? CS_ELIFTHEN : CS_IFTHEN)); execlist(state, 1, do_exec); cmdpop(); - } else + } else { + noerrexit = olderrexit; lastval = 0; + } state->pc = end; return lastval; @@ -632,6 +639,14 @@ execcase(Estate state, int do_exec) zlong try_errflag = -1; +/** + * Corresponding interrupt error status form `try' block. + */ + +/**/ +zlong +try_interrupt = -1; + /**/ zlong try_tryflag = 0; @@ -643,7 +658,7 @@ exectry(Estate state, int do_exec) Wordcode end, always; int endval; int save_retflag, save_breaks, save_contflag; - zlong save_try_errflag, save_try_tryflag; + zlong save_try_errflag, save_try_tryflag, save_try_interrupt; end = state->pc + WC_TRY_SKIP(state->pc[-1]); always = state->pc + 1 + WC_TRY_SKIP(*state->pc); @@ -659,8 +674,9 @@ exectry(Estate state, int do_exec) try_tryflag = save_try_tryflag; - /* Don't record errflag here, may be reset. */ - endval = lastval; + /* Don't record errflag here, may be reset. However, */ + /* endval should show failure when there is an error. */ + endval = lastval ? lastval : errflag; freeheap(); @@ -669,7 +685,10 @@ exectry(Estate state, int do_exec) /* The always clause. */ save_try_errflag = try_errflag; - try_errflag = (zlong)errflag; + save_try_interrupt = try_interrupt; + try_errflag = (zlong)(errflag & ERRFLAG_ERROR); + try_interrupt = (zlong)((errflag & ERRFLAG_INT) ? 1 : 0); + /* We need to reset all errors to allow the block to execute */ errflag = 0; save_retflag = retflag; retflag = 0; @@ -681,8 +700,16 @@ exectry(Estate state, int do_exec) state->pc = always; execlist(state, 1, do_exec); - errflag = try_errflag ? 1 : 0; + if (try_errflag) + errflag |= ERRFLAG_ERROR; + else + errflag &= ~ERRFLAG_ERROR; + if (try_interrupt) + errflag |= ERRFLAG_INT; + else + errflag &= ~ERRFLAG_INT; try_errflag = save_try_errflag; + try_interrupt = save_try_interrupt; if (!retflag) retflag = save_retflag; if (!breaks) diff --git a/Src/math.c b/Src/math.c index 266569827..97a97b32b 100644 --- a/Src/math.c +++ b/Src/math.c @@ -288,11 +288,11 @@ static int type[TOKCOUNT] = { /* 0 */ LR, LR|OP_OP|OP_OPF, RL, RL, RL|OP_OP|OP_OPF, /* 5 */ RL|OP_OP|OP_OPF, RL, RL, LR|OP_A2IO, LR|OP_A2IO, -/* 10 */ LR|OP_A2IO, LR|OP_A2, LR|OP_A2, LR|OP_A2IO, LR|OP_A2, +/* 10 */ LR|OP_A2IO, LR|OP_A2, LR|OP_A2, LR|OP_A2, LR|OP_A2, /* 15 */ LR|OP_A2, LR|OP_A2IO, LR|OP_A2IO, LR|OP_A2IR, LR|OP_A2IR, /* 20 */ LR|OP_A2IR, LR|OP_A2IR, LR|OP_A2IR, LR|OP_A2IR, BOOL|OP_A2IO, /* 25 */ BOOL|OP_A2IO, LR|OP_A2IO, RL|OP_OP, RL|OP_OP, RL|OP_E2, -/* 30 */ RL|OP_E2, RL|OP_E2, RL|OP_E2, RL|OP_E2, RL|OP_E2IO, +/* 30 */ RL|OP_E2, RL|OP_E2, RL|OP_E2, RL|OP_E2, RL|OP_E2, /* 35 */ RL|OP_E2IO, RL|OP_E2IO, RL|OP_E2IO, RL|OP_E2IO, RL|OP_E2IO, /* 40 */ BOOL|OP_E2IO, BOOL|OP_E2IO, RL|OP_A2IO, RL|OP_A2, RL|OP_OP, /* 45 */ RL, RL, LR|OP_OPF, LR|OP_OPF, RL|OP_A2, @@ -336,16 +336,27 @@ enum prec_type { static mnumber getmathparam(struct mathvalue *mptr) { + mnumber result; if (!mptr->pval) { char *s = mptr->lval; mptr->pval = (Value)zhalloc(sizeof(struct value)); if (!getvalue(mptr->pval, &s, 1)) { mptr->pval = NULL; + if (isset(FORCEFLOAT)) { + result.type = MN_FLOAT; + result.u.d = 0.0; + return result; + } return zero_mnumber; } } - return getnumvalue(mptr->pval); + result = getnumvalue(mptr->pval); + if (isset(FORCEFLOAT) && result.type == MN_INTEGER) { + result.type = MN_FLOAT; + result.u.d = (double)result.u.l; + } + return result; } static mnumber @@ -449,12 +460,14 @@ lexconstant(void) nptr++; if (*nptr == '0') { + int lowchar; nptr++; - if (*nptr == 'x' || *nptr == 'X') { + lowchar = tolower(*nptr); + if (lowchar == 'x' || lowchar == 'b') { /* Let zstrtol parse number with base */ yyval.u.l = zstrtol_underscore(ptr, &ptr, 0, 1); /* Should we set lastbase here? */ - lastbase = 16; + lastbase = (lowchar == 'b') ? 2 : 16; if (isset(FORCEFLOAT)) { yyval.type = MN_FLOAT; @@ -878,6 +891,8 @@ getcvar(char *s) static mnumber setmathvar(struct mathvalue *mvp, mnumber v) { + Param pm; + if (mvp->pval) { /* * This value may have been hanging around for a while. @@ -907,7 +922,32 @@ setmathvar(struct mathvalue *mvp, mnumber v) if (noeval) return v; untokenize(mvp->lval); - setnparam(mvp->lval, v); + pm = setnparam(mvp->lval, v); + if (pm) { + /* + * If we are performing an assignment, we return the + * number with the same type as the parameter we are + * assigning to, in the spirit of the way assignments + * in C work. Note this was a change to long-standing + * zsh behaviour. + */ + switch (PM_TYPE(pm->node.flags)) { + case PM_INTEGER: + if (v.type != MN_INTEGER) { + v.u.l = (zlong)v.u.d; + v.type = MN_INTEGER; + } + break; + + case PM_EFLOAT: + case PM_FFLOAT: + if (v.type != MN_FLOAT) { + v.u.d = (double)v.u.l; + v.type = MN_FLOAT; + } + break; + } + } return v; } @@ -1131,7 +1171,9 @@ op(int what) * Any integer mod -1 is the same as any integer mod 1 * i.e. zero. */ - if (b.u.l == -1) + if (c.type == MN_FLOAT) + c.u.d = fmod(a.u.d, b.u.d); + else if (b.u.l == -1) c.u.l = 0; else c.u.l = a.u.l % b.u.l; @@ -1356,6 +1398,8 @@ matheval(char *s) if (!mlevel) outputradix = outputunderscore = 0; + if (*s == Nularg) + s++; if (!*s) { x.type = MN_INTEGER; x.u.l = 0; @@ -1393,6 +1437,8 @@ mathevalarg(char *s, char **ss) * * To avoid a more opaque error further in, bail out here. */ + if (*s == Nularg) + s++; if (!*s) { zerr("bad math expression: empty string"); return (zlong)0; @@ -79,6 +79,10 @@ #include <sys/mman.h> +#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS) +#define MAP_ANONYMOUS MAP_ANON +#endif + #if defined(MAP_ANONYMOUS) && defined(MAP_PRIVATE) #define USE_MMAP 1 diff --git a/Src/module.c b/Src/module.c index 9e8b3cc2a..368254c29 100644 --- a/Src/module.c +++ b/Src/module.c @@ -1577,7 +1577,9 @@ try_load_module(char const *name) if (l + (**pp ? strlen(*pp) : 1) > PATH_MAX) continue; sprintf(buf, "%s/%s.%s", **pp ? *pp : ".", name, DL_EXT); - ret = dlopen(unmeta(buf), RTLD_LAZY | RTLD_GLOBAL); + unmetafy(buf, NULL); + if (*buf) /* dlopen(NULL) returns a handle to the main binary */ + ret = dlopen(buf, RTLD_LAZY | RTLD_GLOBAL); } return ret; @@ -1597,8 +1599,9 @@ do_load_module(char const *name, int silent) ret = try_load_module(name); if (!ret && !silent) { #ifdef HAVE_DLERROR + char *errstr = dlerror(); zwarn("failed to load module `%s': %s", name, - metafy(dlerror(), -1, META_USEHEAP)); + errstr ? metafy(errstr, -1, META_HEAPDUP) : "empty module path"); #else zwarn("failed to load module: %s", name); #endif @@ -3367,7 +3370,7 @@ mod_export int handlefeatures(Module m, Features f, int **enables) { if (!enables || *enables) - return setfeatureenables(m, f, *enables); + return setfeatureenables(m, f, enables ? *enables : NULL); *enables = getfeatureenables(m, f); return 0; } diff --git a/Src/options.c b/Src/options.c index 6e4e7b911..3e3e07474 100644 --- a/Src/options.c +++ b/Src/options.c @@ -766,7 +766,17 @@ dosetopt(int optno, int value, int force, char *new_opts) #ifdef HAVE_SETUID setuid(getuid()); setgid(getgid()); -#endif /* HAVE_SETUID */ + if (setuid(getuid())) { + zwarn("failed to change user ID: %e", errno); + return -1; + } else if (setgid(getgid())) { + zwarn("failed to change group ID: %e", errno); + return -1; + } +#else + zwarn("setuid not available"); + return -1; +#endif /* not HAVE_SETUID */ #ifdef JOB_CONTROL } else if (!force && optno == MONITOR && value) { if (new_opts[optno] == value) diff --git a/Src/params.c b/Src/params.c index 61edc5d08..d53b6ca7e 100644 --- a/Src/params.c +++ b/Src/params.c @@ -331,6 +331,7 @@ IPDEF5("SHLVL", &shlvl, varinteger_gsu), #define IPDEF6(A,B,F) {{NULL,A,PM_INTEGER|PM_SPECIAL|PM_DONTIMPORT},BR((void *)B),GSU(F),10,0,NULL,NULL,NULL,0} IPDEF6("OPTIND", &zoptind, varinteger_gsu), IPDEF6("TRY_BLOCK_ERROR", &try_errflag, varinteger_gsu), +IPDEF6("TRY_BLOCK_INTERRUPT", &try_interrupt, varinteger_gsu), #define IPDEF7(A,B) {{NULL,A,PM_SCALAR|PM_SPECIAL},BR((void *)B),GSU(varscalar_gsu),0,0,NULL,NULL,NULL,0} #define IPDEF7U(A,B) {{NULL,A,PM_SCALAR|PM_SPECIAL|PM_UNSET},BR((void *)B),GSU(varscalar_gsu),0,0,NULL,NULL,NULL,0} @@ -641,8 +642,16 @@ split_env_string(char *env, char **name, char **value) return 0; tenv = strcpy(zhalloc(strlen(env) + 1), env); - for (str = tenv; *str && *str != '='; str++) - ; + for (str = tenv; *str && *str != '='; str++) { + if (STOUC(*str) >= 128) { + /* + * We'll ignore environment variables with names not + * from the portable character set since we don't + * know of a good reason to accept them. + */ + return 0; + } + } if (str != tenv && *str == '=') { *str = '\0'; *name = tenv; @@ -865,10 +874,14 @@ createparam(char *name, int flags) DPUTS(oldpm && oldpm->level > locallevel, "BUG: old local parameter not deleted"); if (oldpm && (oldpm->level == locallevel || !(flags & PM_LOCAL))) { + if (isset(POSIXBUILTINS) && (oldpm->node.flags & PM_READONLY)) { + zerr("read-only variable: %s", name); + return NULL; + } if (!(oldpm->node.flags & PM_UNSET) || (oldpm->node.flags & PM_SPECIAL)) { oldpm->node.flags &= ~PM_UNSET; if ((oldpm->node.flags & PM_SPECIAL) && oldpm->ename) { - Param altpm = + Param altpm = (Param) paramtab->getnode(paramtab, oldpm->ename); if (altpm) altpm->node.flags &= ~PM_UNSET; @@ -918,7 +931,15 @@ shempty(void) { } -/* Create a simple special hash parameter. */ +/* + * Create a simple special hash parameter. + * + * This is for hashes added internally --- it's not possible to add + * special hashes from shell commands. It's currently used + * - by addparamdef() for special parameters in the zsh/parameter + * module + * - by ztie for special parameters tied to databases. + */ /**/ mod_export Param @@ -930,7 +951,22 @@ createspecialhash(char *name, GetNodeFunc get, ScanTabFunc scan, int flags) if (!(pm = createparam(name, PM_SPECIAL|PM_HASHED|flags))) return NULL; - pm->level = pm->old ? locallevel : 0; + /* + * If there's an old parameter, we'll put the new one at + * the current locallevel, so that the old parameter is + * exposed again after leaving the function. Otherwise, + * we'll leave it alone. Usually this means the parameter + * will stay in place until explicitly unloaded, however + * if the parameter was previously unset within a function + * we'll inherit the level of that function and follow the + * standard convention that the parameter remains local + * even if unset. + * + * These semantics are similar to those of a normal parameter set + * within a function without a local definition. + */ + if (pm->old) + pm->level = locallevel; pm->gsu.h = (flags & PM_READONLY) ? &stdhash_gsu : &nullsethash_gsu; pm->u.hash = ht = newhashtable(0, name, NULL); @@ -1251,7 +1287,8 @@ getarg(char **str, int *inv, Value v, int a2, zlong *w, if (ishash && (keymatch || !rev)) remnulargs(s); if (needtok) { - if (parsestr(s)) + s = dupstring(s); + if (parsestr(&s)) return 0; singsub(&s); } else if (rev) @@ -2654,7 +2691,7 @@ assignsparam(char *s, char *val, int flags) if (!isident(s)) { zerr("not an identifier: %s", s); zsfree(val); - errflag = 1; + errflag |= ERRFLAG_ERROR; return NULL; } queue_signals(); @@ -2783,7 +2820,7 @@ assignaparam(char *s, char **val, int flags) if (!isident(s)) { zerr("not an identifier: %s", s); freearray(val); - errflag = 1; + errflag |= ERRFLAG_ERROR; return NULL; } queue_signals(); @@ -2799,7 +2836,7 @@ assignaparam(char *s, char **val, int flags) zerr("%s: attempt to set slice of associative array", v->pm->node.nam); freearray(val); - errflag = 1; + errflag |= ERRFLAG_ERROR; return NULL; } v = NULL; @@ -2870,13 +2907,13 @@ sethparam(char *s, char **val) if (!isident(s)) { zerr("not an identifier: %s", s); freearray(val); - errflag = 1; + errflag |= ERRFLAG_ERROR; return NULL; } if (strchr(s, '[')) { freearray(val); zerr("nested associative arrays not yet supported"); - errflag = 1; + errflag |= ERRFLAG_ERROR; return NULL; } if (unset(EXECOPT)) @@ -2916,7 +2953,7 @@ setnparam(char *s, mnumber val) if (!isident(s)) { zerr("not an identifier: %s", s); - errflag = 1; + errflag |= ERRFLAG_ERROR; return NULL; } if (unset(EXECOPT)) @@ -4368,9 +4405,18 @@ zputenv(char *str) char *ptr; int ret; - for (ptr = str; *ptr && *ptr != '='; ptr++) + for (ptr = str; *ptr && STOUC(*ptr) < 128 && *ptr != '='; ptr++) ; - if (*ptr) { + if (STOUC(*ptr) >= 128) { + /* + * Environment variables not in the portable character + * set are non-standard and we don't really know of + * a use for them. + * + * We'll disable until someone complains. + */ + return 1; + } else if (*ptr) { *ptr = '\0'; ret = setenv(str, ptr+1, 1); *ptr = '='; diff --git a/Src/parse.c b/Src/parse.c index 433efb94e..985eb8e71 100644 --- a/Src/parse.c +++ b/Src/parse.c @@ -31,7 +31,7 @@ #include "parse.pro" /* != 0 if we are about to read a command word */ - + /**/ mod_export int incmdpos; @@ -71,13 +71,14 @@ struct heredocs *hdocs; #define YYERROR(O) { tok = LEXERR; ecused = (O); return 0; } #define YYERRORV(O) { tok = LEXERR; ecused = (O); return; } -#define COND_ERROR(X,Y) do { \ - zwarn(X,Y); \ - herrflush(); \ - if (noerrs != 2) \ - errflag = 1; \ - YYERROR(ecused) \ -} while(0) +#define COND_ERROR(X,Y) \ + do { \ + zwarn(X,Y); \ + herrflush(); \ + if (noerrs != 2) \ + errflag |= ERRFLAG_ERROR; \ + YYERROR(ecused) \ + } while(0) /* @@ -241,6 +242,67 @@ int ecsoffs, ecssub, ecnfunc; #define EC_DOUBLE_THRESHOLD 32768 #define EC_INCREMENT 1024 +/* save parse context */ + +/**/ +void +parse_context_save(struct parse_stack *ps, int toplevel) +{ + (void)toplevel; + + ps->incmdpos = incmdpos; + ps->aliasspaceflag = aliasspaceflag; + ps->incond = incond; + ps->inredir = inredir; + ps->incasepat = incasepat; + ps->isnewlin = isnewlin; + ps->infor = infor; + + ps->hdocs = hdocs; + ps->eclen = eclen; + ps->ecused = ecused; + ps->ecnpats = ecnpats; + ps->ecbuf = ecbuf; + ps->ecstrs = ecstrs; + ps->ecsoffs = ecsoffs; + ps->ecssub = ecssub; + ps->ecnfunc = ecnfunc; + ecbuf = NULL; + hdocs = NULL; +} + +/* restore parse context */ + +/**/ +void +parse_context_restore(const struct parse_stack *ps, int toplevel) +{ + (void)toplevel; + + if (ecbuf) + zfree(ecbuf, eclen); + + incmdpos = ps->incmdpos; + aliasspaceflag = ps->aliasspaceflag; + incond = ps->incond; + inredir = ps->inredir; + incasepat = ps->incasepat; + incasepat = ps->incasepat; + isnewlin = ps->isnewlin; + infor = ps->infor; + + hdocs = ps->hdocs; + eclen = ps->eclen; + ecused = ps->ecused; + ecnpats = ps->ecnpats; + ecbuf = ps->ecbuf; + ecstrs = ps->ecstrs; + ecsoffs = ps->ecsoffs; + ecssub = ps->ecssub; + ecnfunc = ps->ecnfunc; + + errflag &= ~ERRFLAG_ERROR; +} /* Adjust pointers in here-doc structs. */ @@ -358,9 +420,25 @@ ecstrcode(char *s) } while (0) +/**/ +mod_export void +init_parse_status(void) +{ + /* + * These variables are currently declared by the parser, so we + * initialise them here. Possibly they are more naturally declared + * by the lexical anaylser; however, as they are used for signalling + * between the two it's a bit ambiguous. We clear them when + * using the lexical analyser for strings as well as here. + */ + incasepat = incond = inredir = infor = 0; + incmdpos = 1; +} + /* Initialise wordcode buffer. */ -static void +/**/ +void init_parse(void) { if (ecbuf) zfree(ecbuf, eclen); @@ -371,6 +449,8 @@ init_parse(void) ecsoffs = ecnpats = 0; ecssub = 0; ecnfunc = 0; + + init_parse_status(); } /* Build eprog. */ @@ -442,11 +522,15 @@ clear_hdocs() * event : ENDINPUT * | SEPER * | sublist [ SEPER | AMPER | AMPERBANG ] + * + * cmdsubst indicates our event is part of a command-style + * substitution terminated by the token indicationg, usual closing + * parenthesis. In other cases endtok is ENDINPUT. */ /**/ Eprog -parse_event(void) +parse_event(int endtok) { tok = ENDINPUT; incmdpos = 1; @@ -454,36 +538,42 @@ parse_event(void) zshlex(); init_parse(); - if (!par_event()) { + if (!par_event(endtok)) { clear_hdocs(); return NULL; } + if (endtok != ENDINPUT) { + /* don't need to build an eprog for this */ + return &dummy_eprog; + } return bld_eprog(1); } /**/ -static int -par_event(void) +int +par_event(int endtok) { int r = 0, p, c = 0; while (tok == SEPER) { - if (isnewlin > 0) + if (isnewlin > 0 && endtok == ENDINPUT) return 0; zshlex(); } if (tok == ENDINPUT) return 0; + if (tok == endtok) + return 0; p = ecadd(0); if (par_sublist(&c)) { - if (tok == ENDINPUT) { + if (tok == ENDINPUT || tok == endtok) { set_list_code(p, Z_SYNC, c); r = 1; } else if (tok == SEPER) { set_list_code(p, Z_SYNC, c); - if (isnewlin <= 0) + if (isnewlin <= 0 || endtok != ENDINPUT) zshlex(); r = 1; } else if (tok == AMPER) { @@ -506,15 +596,16 @@ par_event(void) yyerror(1); herrflush(); if (noerrs != 2) - errflag = 1; + errflag |= ERRFLAG_ERROR; ecused--; return 0; } else { int oec = ecused; - if (!par_event()) { + if (!par_event(endtok)) { ecused = oec; ecbuf[p] |= wc_bdata(Z_END); + return errflag ? 0 : 1; } } return 1; @@ -527,9 +618,8 @@ parse_list(void) int c = 0; tok = ENDINPUT; - incmdpos = 1; - zshlex(); init_parse(); + zshlex(); par_list(&c); if (tok != ENDINPUT) { clear_hdocs(); @@ -2330,7 +2420,7 @@ yyerror(int noerr) for (t0 = 0; t0 != 20; t0++) if (!t || !t[t0] || t[t0] == '\n') break; - if (!(histdone & HISTFLAG_NOEXEC)) { + if (!(histdone & HISTFLAG_NOEXEC) && !(errflag & ERRFLAG_INT)) { if (t0 == 20) zwarn("parse error near `%l...'", t, 20); else if (t0) @@ -2339,7 +2429,7 @@ yyerror(int noerr) zwarn("parse error"); } if (!noerr && noerrs != 2) - errflag = 1; + errflag |= ERRFLAG_ERROR; } /* @@ -2590,7 +2680,7 @@ eccopyredirs(Estate s) { Wordcode pc = s->pc; wordcode code = *pc; - int ncode, ncodes = 0, r, type; + int ncode, ncodes = 0, r; if (wc_code(code) != WC_REDIR) return NULL; @@ -2598,7 +2688,9 @@ eccopyredirs(Estate s) init_parse(); while (wc_code(code) == WC_REDIR) { - type = WC_REDIR_TYPE(code); +#ifdef DEBUG + int type = WC_REDIR_TYPE(code); +#endif DPUTS(type == REDIR_HEREDOC || type == REDIR_HEREDOCDASH, "unexpanded here document"); @@ -3029,7 +3121,7 @@ build_dump(char *nam, char *dump, char **files, int ali, int map, int flags) file = metafy(file, flen, META_REALLOC); if (!(prog = parse_string(file, 1)) || errflag) { - errflag = 0; + errflag &= ~ERRFLAG_ERROR; close(dfd); zfree(file, flen); zwarnnam(nam, "can't read file: %s", *files); @@ -3139,7 +3231,7 @@ build_cur_dump(char *nam, char *dump, char **names, int match, int map, for (hn = shfunctab->nodes[i]; hn; hn = hn->next) if (cur_add_func(nam, (Shfunc) hn, lnames, progs, &hlen, &tlen, what)) { - errflag = 0; + errflag &= ~ERRFLAG_ERROR; close(dfd); unlink(dump); return 1; @@ -3164,7 +3256,7 @@ build_cur_dump(char *nam, char *dump, char **names, int match, int map, pattry(pprog, hn->nam) && cur_add_func(nam, (Shfunc) hn, lnames, progs, &hlen, &tlen, what)) { - errflag = 0; + errflag &= ~ERRFLAG_ERROR; close(dfd); unlink(dump); return 1; @@ -3175,13 +3267,13 @@ build_cur_dump(char *nam, char *dump, char **names, int match, int map, if (errflag || !(shf = (Shfunc) shfunctab->getnode(shfunctab, *names))) { zwarnnam(nam, "unknown function: %s", *names); - errflag = 0; + errflag &= ~ERRFLAG_ERROR; close(dfd); unlink(dump); return 1; } if (cur_add_func(nam, shf, lnames, progs, &hlen, &tlen, what)) { - errflag = 0; + errflag &= ~ERRFLAG_ERROR; close(dfd); unlink(dump); return 1; @@ -3190,7 +3282,7 @@ build_cur_dump(char *nam, char *dump, char **names, int match, int map, } if (empty(progs)) { zwarnnam(nam, "no functions"); - errflag = 0; + errflag &= ~ERRFLAG_ERROR; close(dfd); unlink(dump); return 1; diff --git a/Src/pattern.c b/Src/pattern.c index df5e602ca..17cd40c23 100644 --- a/Src/pattern.c +++ b/Src/pattern.c @@ -3622,7 +3622,7 @@ patmatchrange(char *range, int ch, int *indptr, int *mtp) return 1; break; case PP_PRINT: - if (isprint(ch)) + if (ISPRINT(ch)) return 1; break; case PP_PUNCT: diff --git a/Src/prompt.c b/Src/prompt.c index 0cc9ef917..ffc1d0df2 100644 --- a/Src/prompt.c +++ b/Src/prompt.c @@ -183,7 +183,7 @@ promptexpand(char *s, int ns, char *rs, char *Rs, unsigned int *txtchangep) int oldval = lastval; s = dupstring(s); - if (!parsestr(s)) + if (!parsestr(&s)) singsub(&s); /* * We don't need the special Nularg hack here and we're @@ -192,8 +192,11 @@ promptexpand(char *s, int ns, char *rs, char *Rs, unsigned int *txtchangep) if (*s == Nularg && s[1] == '\0') *s = '\0'; - /* Ignore errors and status change in prompt substitution */ - errflag = olderr; + /* + * Ignore errors and status change in prompt substitution. + * However, keep any user interrupt error that occurred. + */ + errflag = olderr | (errflag & ERRFLAG_INT); lastval = oldval; } diff --git a/Src/signals.c b/Src/signals.c index 2df69f96e..3950ad1a2 100644 --- a/Src/signals.c +++ b/Src/signals.c @@ -522,14 +522,14 @@ wait_for_processes(void) get_usage(); } /* - * Remember the status associated with $!, so we can - * wait for it even if it's exited. This value is - * only used if we can't find the PID in the job table, - * so it doesn't matter that the value we save here isn't - * useful until the process has exited. + * Accumulate a list of older jobs. We only do this for + * background jobs, which is something in the job table + * that's not marked as in the current shell or as shell builtin + * and is not equal to the current foreground job. */ - if (pn != NULL && pid == lastpid && lastpid_status != -1L) - lastpid_status = lastval2; + if (jn && !(jn->stat & (STAT_CURSH|STAT_BUILTIN)) && + jn - jobtab != thisjob) + addbgstatus(pid, (int)lastval2); } } @@ -619,7 +619,7 @@ zhandler(int sig) zexit(SIGINT, 1); if (list_pipe || chline || simple_pline) { breaks = loops; - errflag = 1; + errflag |= ERRFLAG_INT; inerrflush(); check_cursh_sig(SIGINT); } @@ -640,6 +640,11 @@ zhandler(int sig) if (idle >= 0 && idle < tmout) alarm(tmout - idle); else { + /* + * We want to exit now. + * Cancel all errors, including a user interrupt + * which is now redundant. + */ errflag = noerrs = 0; zwarn("timeout"); stopmsg = 1; @@ -1205,7 +1210,7 @@ dotrapargs(int sig, int *sigtr, void *sigfn) intrap++; *sigtr |= ZSIG_IGNORED; - lexsave(); + zcontext_save(); /* execsave will save the old trap_return and trap_state */ execsave(); breaks = retflag = 0; @@ -1260,14 +1265,25 @@ dotrapargs(int sig, int *sigtr, void *sigfn) new_trap_return = trap_return; execrestore(); - lexrestore(); + zcontext_restore(); if (new_trap_state == TRAP_STATE_FORCE_RETURN && /* zero return from function isn't special */ !(isfunc && new_trap_return == 0)) { if (isfunc) { breaks = loops; - errflag = 1; + /* + * For SIGINT we behave the same as the default behaviour + * i.e. we set the error bit indicating an interrupt. + * We do this with SIGQUIT, too, even though we don't + * handle SIGQUIT by default. That's to try to make + * it behave a bit more like its normal behaviour when + * the trap handler has told us that's what it wants. + */ + if (sig == SIGINT || sig == SIGQUIT) + errflag |= ERRFLAG_INT; + else + errflag |= ERRFLAG_ERROR; } lastval = new_trap_return; /* return triggered */ @@ -1282,8 +1298,12 @@ dotrapargs(int sig, int *sigtr, void *sigfn) */ lastval = olastval; } - if (try_tryflag) - errflag = traperr; + if (try_tryflag) { + if (traperr) + errflag |= ERRFLAG_ERROR; + else + errflag &= ~ERRFLAG_ERROR; + } breaks += obreaks; /* return not triggered: restore old flag */ retflag = oretflag; diff --git a/Src/sort.c b/Src/sort.c index 3d00bb576..92ee1c0d4 100644 --- a/Src/sort.c +++ b/Src/sort.c @@ -368,7 +368,7 @@ strmetasort(char **array, int sortwhat, int *unmetalenp) sortdir = (sortwhat & SORTIT_BACKWARDS) ? -1 : 1; sortnumeric = (sortwhat & SORTIT_NUMERICALLY) ? 1 : 0; - qsort(sortptrarr, nsort, sizeof(SortElt *), eltpcmp); + qsort(sortptrarr, nsort, sizeof(SortElt), eltpcmp); sortnumeric = oldsortnumeric; sortdir = oldsortdir; diff --git a/Src/subst.c b/Src/subst.c index 1aa9b982e..f52bcdfc8 100644 --- a/Src/subst.c +++ b/Src/subst.c @@ -195,7 +195,7 @@ stringsubst(LinkList list, LinkNode node, int pf_flags, int asssub) while (!errflag && (c = *str)) { if ((qt = c == Qstring) || c == String) { - if ((c = str[1]) == Inpar) { + if ((c = str[1]) == Inpar || c == Inparmath) { if (!qt) list->list.flags |= LF_ARRAY; str++; @@ -258,24 +258,34 @@ stringsubst(LinkList list, LinkNode node, int pf_flags, int asssub) skipparens(Inpar, Outpar, &str); #endif str--; - } else { - endchar = c; - *str = '\0'; - - while (*++str != endchar) - DPUTS(!*str, "BUG: parse error in command substitution"); - } - *str++ = '\0'; - if (endchar == Outpar && str2[1] == '(' && str[-2] == ')') { + } else if (c == Inparmath) { /* Math substitution of the form $((...)) */ - str[-2] = '\0'; + str[-1] = '\0'; + while (*str != Outparmath && *str) + str++; + if (*str != Outparmath) { + zerr("failed to find end of math substitution"); + return NULL; + } + str[-1] = '\0'; if (isset(EXECOPT)) - str = arithsubst(str2 + 2, &str3, str); + str = arithsubst(str2 + 2, &str3, str+1); else strncpy(str3, str2, 1); setdata(node, (void *) str3); continue; + } else { + endchar = c; + *str = '\0'; + + while (*++str != endchar) { + if (!*str) { + zerr("failed to find end of command substitution"); + return NULL; + } + } } + *str++ = '\0'; /* It is a command substitution, which will be parsed again * * by the lexer, so we untokenize it first, but we cannot use * @@ -298,7 +308,7 @@ stringsubst(LinkList list, LinkNode node, int pf_flags, int asssub) if (endchar == Outpar) str2--; if (!(s = (char *) ugetnode(pl))) { - str = strcpy(str2, str); + str = (char *)memmove(str2, str, strlen(str)+1); continue; } if (!qt && (pf_flags & PREFORK_SINGLE) && isset(GLOBSUBST)) @@ -1306,7 +1316,7 @@ get_intarg(char **s, int *delmatchp) p = dupstring(*s + arglen); *s = t + arglen; *t = sav; - if (parsestr(p)) + if (parsestr(&p)) return -1; singsub(&p); if (errflag) @@ -1317,7 +1327,7 @@ get_intarg(char **s, int *delmatchp) if (ret < 0) ret = -ret; *delmatchp = arglen; - return ret < 0 ? -ret : ret; + return ret; } /* Parsing for the (e) flag. */ @@ -1329,18 +1339,21 @@ subst_parse_str(char **sp, int single, int err) *sp = s = dupstring(*sp); - if (!(err ? parsestr(s) : parsestrnoerr(s))) { + if (!(err ? parsestr(&s) : parsestrnoerr(&s))) { + *sp = s; if (!single) { int qt = 0; - for (; *s; s++) + for (; *s; s++) { if (!qt) { if (*s == Qstring) *s = String; else if (*s == Qtick) *s = Tick; - } else if (*s == Dnull) + } + if (*s == Dnull) qt = !qt; + } } return 0; } @@ -1385,12 +1398,23 @@ static char * untok_and_escape(char *s, int escapes, int tok_arg) { int klen; - char *dst; + char *dst = NULL; - untokenize(dst = dupstring(s)); - if (escapes) { - dst = getkeystring(dst, &klen, GETKEYS_SEP, NULL); - dst = metafy(dst, klen, META_HREALLOC); + if (escapes && (*s == String || *s == Qstring) && s[1]) { + char *pstart = s+1, *pend; + for (pend = pstart; *pend; pend++) + if (!iident(*pend)) + break; + if (!*pend) { + dst = dupstring(getsparam(pstart)); + } + } + if (dst == NULL) { + untokenize(dst = dupstring(s)); + if (escapes) { + dst = getkeystring(dst, &klen, GETKEYS_SEP, NULL); + dst = metafy(dst, klen, META_HREALLOC); + } } if (tok_arg) shtokenize(dst); @@ -1426,7 +1450,8 @@ check_colon_subscript(char *str, char **endp) } sav = **endp; **endp = '\0'; - if (parsestr(str = dupstring(str))) + str = dupstring(str); + if (parsestr(&str)) return NULL; singsub(&str); remnulargs(str); @@ -2192,12 +2217,28 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags) */ idbeg = s; if ((subexp = (inbrace && s[-1] && isstring(*s) && - (s[1] == Inbrace || s[1] == Inpar)))) { + (s[1] == Inbrace || s[1] == Inpar || s[1] == Inparmath)))) { int sav; int quoted = *s == Qstring; + int outtok; val = s++; - skipparens(*s, *s == Inpar ? Outpar : Outbrace, &s); + switch (*s) { + case Inbrace: + outtok = Outbrace; + break; + case Inpar: + outtok = Outpar; + break; + case Inparmath: + outtok = Outparmath; + break; + default: + /* "Can't Happen" (TM) */ + DPUTS(1, "Nested substitution: This Can't Happen (TM)"); + return NULL; + } + skipparens(*s, outtok, &s); sav = *s; *s = 0; /* @@ -2811,7 +2852,8 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags) haserr = parse_subst_string(s); noerrs = one; if (!quoteerr) { - errflag = oef; + /* Retain user interrupt error status */ + errflag = oef | (errflag & ERRFLAG_INT); if (haserr) shtokenize(s); } else if (haserr || errflag) { @@ -3238,8 +3280,10 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags) haserr = 1; } noerrs = one; - if (!quoteerr) - errflag = oef; + if (!quoteerr) { + /* Retain user interrupt error status */ + errflag = oef | (errflag & ERRFLAG_INT); + } if (haserr || errflag) return NULL; } @@ -3472,8 +3516,10 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags) untokenize(*ap); } noerrs = one; - if (!quoteerr) - errflag = oef; + if (!quoteerr) { + /* Retain any user interrupt error status */ + errflag = oef | (errflag & ERRFLAG_INT); + } else if (haserr || errflag) { zerr("parse error in parameter value"); return NULL; @@ -3505,8 +3551,10 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags) noerrs = 1; haserr = parse_subst_string(val); noerrs = one; - if (!quoteerr) - errflag = oef; + if (!quoteerr) { + /* Retain any user interrupt error status */ + errflag = oef | (errflag & ERRFLAG_INT); + } else if (haserr || errflag) { zerr("parse error in parameter value"); return NULL; @@ -4075,7 +4123,8 @@ modify(char **str, char **ptr) noerrs = 1; parse_subst_string(copy); noerrs = one; - errflag = oef; + /* Retain any user interrupt error status */ + errflag = oef | (errflag & ERRFLAG_INT); remnulargs(copy); untokenize(copy); } @@ -4150,7 +4199,8 @@ modify(char **str, char **ptr) noerrs = 1; parse_subst_string(*str); noerrs = one; - errflag = oef; + /* Retain any user interrupt error status */ + errflag = oef | (errflag & ERRFLAG_INT); remnulargs(*str); untokenize(*str); } diff --git a/Src/text.c b/Src/text.c index f55553ed0..b58c2516d 100644 --- a/Src/text.c +++ b/Src/text.c @@ -173,6 +173,8 @@ getpermtext(Eprog prog, Wordcode c, int start_indent) { struct estate s; + queue_signals(); + if (!c) c = prog->prog; @@ -193,6 +195,9 @@ getpermtext(Eprog prog, Wordcode c, int start_indent) *tptr = '\0'; freeeprog(prog); /* mark as unused */ untokenize(tbuf); + + unqueue_signals(); + return tbuf; } @@ -206,6 +211,8 @@ getjobtext(Eprog prog, Wordcode c) struct estate s; + queue_signals(); + if (!c) c = prog->prog; @@ -224,6 +231,9 @@ getjobtext(Eprog prog, Wordcode c) *tptr = '\0'; freeeprog(prog); /* mark as unused */ untokenize(jbuf); + + unqueue_signals(); + return jbuf; } @@ -834,6 +844,10 @@ gettext2(Estate state) taddstr("))"); stack = 1; break; + case WC_AUTOFN: + taddstr("builtin autoload -X"); + stack = 1; + break; case WC_TRY: if (!s) { taddstr("{"); @@ -879,6 +893,9 @@ getredirs(LinkList redirs) ">", ">|", ">>", ">>|", "&>", "&>|", "&>>", "&>>|", "<>", "<", "<<", "<<-", "<<<", "<&", ">&", NULL /* >&- */, "<", ">" }; + + queue_signals(); + taddchr(' '); for (n = firstnode(redirs); n; incnode(n)) { Redir f = (Redir) getdata(n); @@ -966,4 +983,6 @@ getredirs(LinkList redirs) } } tptr--; + + unqueue_signals(); } diff --git a/Src/utils.c b/Src/utils.c index e6eb8e6a7..3d12807e2 100644 --- a/Src/utils.c +++ b/Src/utils.c @@ -112,7 +112,20 @@ set_widearray(char *mb_array, Widechar_array wca) #endif -/* Print an error */ +/* Print an error + + The following functions use the following printf-like format codes + (implemented by zerrmsg()): + + Code Argument types Prints + %s const char * C string (null terminated) + %l const char *, int C string of given length (null not required) + %L long decimal value + %d int decimal value + %% (none) literal '%' + %c int character at that codepoint + %e int strerror() message (argument is typically 'errno') + */ static void zwarning(const char *cmd, const char *fmt, va_list ap) @@ -153,7 +166,7 @@ VA_DCL if (errflag || noerrs) { if (noerrs < 2) - errflag = 1; + errflag |= ERRFLAG_ERROR; return; } @@ -161,7 +174,7 @@ VA_DCL VA_GET_ARG(ap, fmt, const char *); zwarning(NULL, fmt, ap); va_end(ap); - errflag = 1; + errflag |= ERRFLAG_ERROR; } /**/ @@ -181,7 +194,7 @@ VA_DCL VA_GET_ARG(ap, fmt, const char *); zwarning(cmd, fmt, ap); va_end(ap); - errflag = 1; + errflag |= ERRFLAG_ERROR; } /**/ @@ -330,7 +343,7 @@ zerrmsg(FILE *file, const char *fmt, va_list ap) num = va_arg(ap, int); if (num == EINTR) { fputs("interrupt\n", file); - errflag = 1; + errflag |= ERRFLAG_ERROR; return; } errmsg = strerror(num); @@ -343,6 +356,7 @@ zerrmsg(FILE *file, const char *fmt, va_list ap) fputs(errmsg + 1, file); } break; + /* When adding format codes, update the comment above zwarning(). */ } } else { putc(*fmt == Meta ? *++fmt ^ 32 : *fmt, file); @@ -400,7 +414,7 @@ nicechar(int c) static char buf[6]; char *s = buf; c &= 0xff; - if (isprint(c)) + if (ISPRINT(c)) goto done; if (c & 0x80) { if (isset(PRINTEIGHTBIT)) @@ -409,7 +423,7 @@ nicechar(int c) *s++ = 'M'; *s++ = '-'; c &= 0x7f; - if(isprint(c)) + if(ISPRINT(c)) goto done; } if (c == 0x7f) { @@ -566,7 +580,7 @@ wcs_nicechar(wchar_t c, size_t *widthp, char **swidep) return buf; } if (swidep) - *swidep = buf + *widthp; + *swidep = widthp ? buf + *widthp : buf; return buf; } @@ -693,12 +707,12 @@ slashsplit(char *s) int t0; if (!*s) - return (char **) zshcalloc(sizeof(char **)); + return (char **) zshcalloc(sizeof(char *)); for (t = s, t0 = 0; *t; t++) if (*t == '/') t0++; - q = r = (char **) zalloc(sizeof(char **) * (t0 + 2)); + q = r = (char **) zalloc(sizeof(char *) * (t0 + 2)); while ((t = strchr(s, '/'))) { *q++ = ztrduppfx(s, t - s); @@ -719,7 +733,7 @@ slashsplit(char *s) /**/ static int -xsymlinks(char *s) +xsymlinks(char *s, int full) { char **pp, **opp; char xbuf2[PATH_MAX*3], xbuf3[PATH_MAX*2]; @@ -727,7 +741,7 @@ xsymlinks(char *s) zulong xbuflen = strlen(xbuf); opp = pp = slashsplit(s); - for (; xbuflen < sizeof(xbuf) && *pp; pp++) { + for (; xbuflen < sizeof(xbuf) && *pp && ret >= 0; pp++) { if (!strcmp(*pp, ".")) continue; if (!strcmp(*pp, "..")) { @@ -741,6 +755,8 @@ xsymlinks(char *s) while (*--p != '/') xbuflen--; *p = '\0'; + /* The \0 isn't included in the length */ + xbuflen--; continue; } sprintf(xbuf2, "%s/%s", xbuf, *pp); @@ -758,13 +774,52 @@ xsymlinks(char *s) } else { ret = 1; metafy(xbuf3, t0, META_NOALLOC); + if (!full) { + /* + * If only one expansion requested, ensure the + * full path is in xbuf. + */ + zulong len = xbuflen; + if (*xbuf3 == '/') + strcpy(xbuf, xbuf3); + else if ((len += strlen(xbuf3) + 1) < sizeof(xbuf)) { + strcpy(xbuf + xbuflen, "/"); + strcpy(xbuf + xbuflen + 1, xbuf3); + } else { + *xbuf = 0; + ret = -1; + break; + } + + while (*++pp) { + zulong newlen = len + strlen(*pp) + 1; + if (newlen < sizeof(xbuf)) { + strcpy(xbuf + len, "/"); + strcpy(xbuf + len + 1, *pp); + len = newlen; + } else { + *xbuf = 01; + ret = -1; + break; + } + } + /* + * No need to update xbuflen, we're finished + * the expansion (for now). + */ + break; + } if (*xbuf3 == '/') { strcpy(xbuf, ""); - if (xsymlinks(xbuf3 + 1) < 0) + if (xsymlinks(xbuf3 + 1, 1) < 0) ret = -1; + else + xbuflen = strlen(xbuf); } else - if (xsymlinks(xbuf3) < 0) + if (xsymlinks(xbuf3, 1) < 0) ret = -1; + else + xbuflen = strlen(xbuf); } } freearray(opp); @@ -783,7 +838,7 @@ xsymlink(char *s) if (*s != '/') return NULL; *xbuf = '\0'; - if (xsymlinks(s + 1) < 0) + if (xsymlinks(s + 1, 1) < 0) zwarn("path expansion failed, using root directory"); if (!*xbuf) return ztrdup("/"); @@ -792,12 +847,30 @@ xsymlink(char *s) /**/ void -print_if_link(char *s) +print_if_link(char *s, int all) { if (*s == '/') { *xbuf = '\0'; - if (xsymlinks(s + 1) > 0) - printf(" -> "), zputs(*xbuf ? xbuf : "/", stdout); + if (all) { + char *start = s + 1; + char xbuflink[PATH_MAX]; + for (;;) { + if (xsymlinks(start, 0) > 0) { + printf(" -> "); + zputs(*xbuf ? xbuf : "/", stdout); + if (!*xbuf) + break; + strcpy(xbuflink, xbuf); + start = xbuflink + 1; + *xbuf = '\0'; + } else { + break; + } + } + } else { + if (xsymlinks(s + 1, 1) > 0) + printf(" -> "), zputs(*xbuf ? xbuf : "/", stdout); + } } } @@ -1049,10 +1122,13 @@ getnameddir(char *name) if ((pw = getpwnam(name))) { char *dir = isset(CHASELINKS) ? xsymlink(pw->pw_dir) : ztrdup(pw->pw_dir); - adduserdir(name, dir, ND_USERNAME, 1); - str = dupstring(dir); - zsfree(dir); - return str; + if (dir) { + adduserdir(name, dir, ND_USERNAME, 1); + str = dupstring(dir); + zsfree(dir); + return str; + } else + return dupstring(pw->pw_dir); } } #endif /* HAVE_GETPWNAM */ @@ -1456,7 +1532,7 @@ checkmailpath(char **s) setunderscore(*s); u = dupstring(u); - if (! parsestr(u)) { + if (!parsestr(&u)) { singsub(&u); zputs(u, shout); fputc('\n', shout); @@ -2078,6 +2154,8 @@ zstrtol_underscore(const char *s, char **t, int base, int underscore) base = 10; else if (*++s == 'x' || *s == 'X') base = 16, s++; + else if (*s == 'b' || *s == 'B') + base = 2, s++; else base = 8; } @@ -2197,6 +2275,10 @@ setblock_stdin(void) * Note that apart from setting (and restoring) non-blocking input, * this function does not change the input mode. The calling function * should have set cbreak mode if necessary. + * + * fd may be -1 to sleep until the timeout in microseconds. This is a + * fallback for old systems that don't have nanosleep(). Some very old + * systems might not have select: get with it, daddy-o. */ /**/ @@ -2218,6 +2300,8 @@ read_poll(int fd, int *readchar, int polltty, zlong microseconds) struct ttyinfo ti; #endif + if (fd < 0 || (polltty && !isatty(fd))) + polltty = 0; /* no tty to poll */ #if defined(HAS_TIO) && !defined(__CYGWIN__) /* @@ -2239,7 +2323,7 @@ read_poll(int fd, int *readchar, int polltty, zlong microseconds) * as plausible as it sounds, but it seems the right way to guess. * pws 2000/06/26 */ - if (polltty) { + if (polltty && fd >= 0) { gettyinfo(&ti); if ((polltty = ti.tio.c_cc[VMIN])) { ti.tio.c_cc[VMIN] = 0; @@ -2255,16 +2339,24 @@ read_poll(int fd, int *readchar, int polltty, zlong microseconds) expire_tv.tv_sec = (int) (microseconds / (zlong)1000000); expire_tv.tv_usec = microseconds % (zlong)1000000; FD_ZERO(&foofd); - FD_SET(fd, &foofd); - ret = select(fd+1, (SELECT_ARG_2_T) &foofd, NULL, NULL, &expire_tv); + if (fd > -1) { + FD_SET(fd, &foofd); + ret = select(fd+1, (SELECT_ARG_2_T) &foofd, NULL, NULL, &expire_tv); + } else + ret = select(0, NULL, NULL, NULL, &expire_tv); #else + if (fd < 0) { + /* OK, can't do that. Just quietly sleep for a second. */ + sleep(1); + return 1; + } #ifdef FIONREAD if (ioctl(fd, FIONREAD, (char *) &val) == 0) ret = (val > 0); #endif #endif - if (ret < 0) { + if (fd >= 0 && ret < 0) { /* * Final attempt: set non-blocking read and try to read a character. * Praise Bill, this works under Cygwin (nothing else seems to). @@ -2286,6 +2378,80 @@ read_poll(int fd, int *readchar, int polltty, zlong microseconds) return (ret > 0); } +/* + * Sleep for the given number of microseconds --- must be within + * range of a long at the moment, but this is only used for + * limited internal purposes. + */ + +/**/ +int +zsleep(long us) +{ +#ifdef HAVE_NANOSLEEP + struct timespec sleeptime; + + sleeptime.tv_sec = (time_t)us / (time_t)1000000; + sleeptime.tv_nsec = (us % 1000000L) * 1000L; + for (;;) { + struct timespec rem; + int ret = nanosleep(&sleeptime, &rem); + + if (ret == 0) + return 1; + else if (errno != EINTR) + return 0; + sleeptime = rem; + } +#else + int dummy; + return read_poll(-1, &dummy, 0, us); +#endif +} + +/** + * Sleep for time (fairly) randomly up to max_us microseconds. + * Don't let the wallclock time extend beyond end_time. + * Return 1 if that seemed to work, else 0. + * + * For best results max_us should be a multiple of 2**16 or large + * enough that it doesn't matter. + */ + +/**/ +int +zsleep_random(long max_us, time_t end_time) +{ + long r; + time_t now = time(NULL); + + /* + * Randomish backoff. Doesn't need to be fundamentally + * unpredictable, just probably unlike the value another + * exiting shell is using. On some systems the bottom 16 + * bits aren't that random but the use here doesn't + * really care. + */ + r = (long)(rand() & 0xFFFF); + /* + * Turn this into a fraction of sleep_us. Again, this + * doesn't need to be particularly accurate and the base time + * is sufficient that we can do the division first and not + * worry about the range. + */ + r = (max_us >> 16) * r; + /* + * Don't sleep beyond timeout. + * Not that important as timeout is ridiculously long, but + * if there's an interface, interface to it... + */ + while (r && now + (time_t)(r / 1000000) > end_time) + r >>= 1; + if (r) /* pedantry */ + return zsleep(r); + return 0; +} + /**/ int checkrmall(char *s) @@ -2294,7 +2460,7 @@ checkrmall(char *s) return 1; fprintf(shout, "zsh: sure you want to delete all the files in "); if (*s != '/') { - nicezputs(pwd[1] ? unmeta(pwd) : "", shout); + nicezputs(pwd[1] ? pwd : "", shout); fputc('/', shout); } nicezputs(s, shout); @@ -2949,7 +3115,7 @@ colonsplit(char *s, int uniq) for (t = s, ct = 0; *t; t++) /* count number of colons */ if (*t == ':') ct++; - ptr = ret = (char **) zalloc(sizeof(char **) * (ct + 2)); + ptr = ret = (char **) zalloc(sizeof(char *) * (ct + 2)); t = s; do { @@ -3482,7 +3648,7 @@ inittyptab(void) for (t0 = (int)STOUC(Snull); t0 <= (int)STOUC(Nularg); t0++) typtab[t0] |= ITOK | IMETA | INULL; for (s = ifs ? ifs : EMULATION(EMULATE_KSH|EMULATE_SH) ? - ztrdup(DEFAULT_IFS_SH) : ztrdup(DEFAULT_IFS); *s; s++) { + DEFAULT_IFS_SH : DEFAULT_IFS; *s; s++) { int c = STOUC(*s == Meta ? *++s ^ 32 : *s); #ifdef MULTIBYTE_SUPPORT if (!isascii(c)) { @@ -3517,7 +3683,7 @@ inittyptab(void) #ifdef MULTIBYTE_SUPPORT set_widearray(wordchars, &wordchars_wide); set_widearray(ifs ? ifs : EMULATION(EMULATE_KSH|EMULATE_SH) ? - ztrdup(DEFAULT_IFS_SH) : ztrdup(DEFAULT_IFS), &ifs_wide); + DEFAULT_IFS_SH : DEFAULT_IFS, &ifs_wide); #endif for (s = SPECCHARS; *s; s++) typtab[STOUC(*s)] |= ISPECIAL; @@ -4158,7 +4324,7 @@ unmetafy(char *s, int *len) for (p = s; *p && *p != Meta; p++); for (t = p; (*t = *p++);) - if (*t++ == Meta) + if (*t++ == Meta && *p) t[-1] = *p++ ^ 32; if (len) *len = t - s; @@ -4242,7 +4408,7 @@ unmeta(const char *file_name) } for (t = file_name, p = fn; *t; p++) - if ((*p = *t++) == Meta) + if ((*p = *t++) == Meta && *t) *p = *t++ ^ 32; *p = '\0'; return fn; @@ -4631,6 +4797,14 @@ mb_metacharlenconv_r(const char *s, wint_t *wcp, mbstate_t *mbsp) inchar = *++ptr ^ 32; DPUTS(!*ptr, "BUG: unexpected end of string in mb_metacharlen()\n"); + } else if (imeta(*ptr)) { + /* + * As this is metafied input, this is a token --- this + * can't be a part of the string. It might be + * something on the end of an unbracketed parameter + * reference, for example. + */ + break; } else inchar = *ptr; ptr++; diff --git a/Src/watch.c b/Src/watch.c index 8dea0b495..fe409f91a 100644 --- a/Src/watch.c +++ b/Src/watch.c @@ -372,6 +372,27 @@ watchlog2(int inout, WATCH_STRUCT_UTMP *u, char *fmt, int prnt, int fini) return fmt; } +/* See if the watch entry matches */ + +static int +watchlog_match(char *teststr, char *actual, int len) +{ + int ret = 0; + Patprog pprog; + char *str = dupstring(teststr); + + tokenize(str); + + if ((pprog = patcompile(str, PAT_STATIC, 0))) { + queue_signals(); + if (pattry(pprog, actual)) + ret = 1; + unqueue_signals(); + } else if (!strncmp(actual, teststr, len)) + ret = 1; + return ret; +} + /* check the List for login/logouts */ /**/ @@ -400,7 +421,7 @@ watchlog(int inout, WATCH_STRUCT_UTMP *u, char **w, char *fmt) for (vv = v; *vv && *vv != '@' && *vv != '%'; vv++); sav = *vv; *vv = '\0'; - if (strncmp(u->ut_name, v, sizeof(u->ut_name))) + if (!watchlog_match(v, u->ut_name, sizeof(u->ut_name))) bad = 1; *vv = sav; v = vv; @@ -410,7 +431,7 @@ watchlog(int inout, WATCH_STRUCT_UTMP *u, char **w, char *fmt) for (vv = ++v; *vv && *vv != '@'; vv++); sav = *vv; *vv = '\0'; - if (strncmp(u->ut_line, v, sizeof(u->ut_line))) + if (!watchlog_match(v, u->ut_line, sizeof(u->ut_line))) bad = 1; *vv = sav; v = vv; @@ -420,7 +441,7 @@ watchlog(int inout, WATCH_STRUCT_UTMP *u, char **w, char *fmt) for (vv = ++v; *vv && *vv != '%'; vv++); sav = *vv; *vv = '\0'; - if (strncmp(u->ut_host, v, strlen(v))) + if (!watchlog_match(v, u->ut_host, strlen(v))) bad = 1; *vv = sav; v = vv; @@ -163,40 +163,42 @@ struct mathfunc { #define Hat ((char) 0x86) #define Star ((char) 0x87) #define Inpar ((char) 0x88) -#define Outpar ((char) 0x89) -#define Qstring ((char) 0x8a) -#define Equals ((char) 0x8b) -#define Bar ((char) 0x8c) -#define Inbrace ((char) 0x8d) -#define Outbrace ((char) 0x8e) -#define Inbrack ((char) 0x8f) -#define Outbrack ((char) 0x90) -#define Tick ((char) 0x91) -#define Inang ((char) 0x92) -#define Outang ((char) 0x93) -#define OutangProc ((char) 0x94) -#define Quest ((char) 0x95) -#define Tilde ((char) 0x96) -#define Qtick ((char) 0x97) -#define Comma ((char) 0x98) +#define Inparmath ((char) 0x89) +#define Outpar ((char) 0x8a) +#define Outparmath ((char) 0x8b) +#define Qstring ((char) 0x8c) +#define Equals ((char) 0x8d) +#define Bar ((char) 0x8e) +#define Inbrace ((char) 0x8f) +#define Outbrace ((char) 0x90) +#define Inbrack ((char) 0x91) +#define Outbrack ((char) 0x92) +#define Tick ((char) 0x93) +#define Inang ((char) 0x94) +#define Outang ((char) 0x95) +#define OutangProc ((char) 0x96) +#define Quest ((char) 0x97) +#define Tilde ((char) 0x98) +#define Qtick ((char) 0x99) +#define Comma ((char) 0x9a) /* * Null arguments: placeholders for single and double quotes * and backslashes. */ -#define Snull ((char) 0x99) -#define Dnull ((char) 0x9a) -#define Bnull ((char) 0x9b) +#define Snull ((char) 0x9b) +#define Dnull ((char) 0x9c) +#define Bnull ((char) 0x9d) /* * Backslash which will be returned to "\" instead of being stripped * when we turn the string into a printable format. */ -#define Bnullkeep ((char) 0x9c) +#define Bnullkeep ((char) 0x9e) /* * Null argument that does not correspond to any character. * This should be last as it does not appear in ztokens and * is used to initialise the IMETA type in inittyptab(). */ -#define Nularg ((char) 0x9d) +#define Nularg ((char) 0x9f) /* * Take care to update the use of IMETA appropriately when adding @@ -409,7 +411,9 @@ enum { #define INP_HIST (1<<2) /* expanding history */ #define INP_CONT (1<<3) /* continue onto previously stacked input */ #define INP_ALCONT (1<<4) /* stack is continued from alias expn. */ -#define INP_LINENO (1<<5) /* update line number */ +#define INP_HISTCONT (1<<5) /* stack is continued from history expn. */ +#define INP_LINENO (1<<6) /* update line number */ +#define INP_APPEND (1<<7) /* Append new lines to allow backup */ /* Flags for metafy */ #define META_REALLOC 0 @@ -421,6 +425,15 @@ enum { #define META_HEAPDUP 6 #define META_HREALLOC 7 +/* Context to save and restore (bit fields) */ +enum { + /* History mechanism */ + ZCONTEXT_HIST = (1<<0), + /* Lexical analyser */ + ZCONTEXT_LEX = (1<<1), + /* Parser */ + ZCONTEXT_PARSE = (1<<2) +}; /**************************/ /* Abstract types for zsh */ @@ -1865,9 +1878,9 @@ typedef groupset *Groupset; #define PRINT_TYPESET (1<<5) /* flags for printing for the whence builtin */ -#define PRINT_WHENCE_CSH (1<<5) -#define PRINT_WHENCE_VERBOSE (1<<6) -#define PRINT_WHENCE_SIMPLE (1<<7) +#define PRINT_WHENCE_CSH (1<<6) +#define PRINT_WHENCE_VERBOSE (1<<7) +#define PRINT_WHENCE_SIMPLE (1<<8) #define PRINT_WHENCE_FUNCDEF (1<<9) #define PRINT_WHENCE_WORD (1<<10) @@ -2623,6 +2636,20 @@ enum trap_state { #define IN_EVAL_TRAP() \ (intrap && !trapisfunc && traplocallevel == locallevel) +/* + * Bits in the errflag variable. + */ +enum errflag_bits { + /* + * Standard internal error bit. + */ + ERRFLAG_ERROR = 1, + /* + * User interrupt. + */ + ERRFLAG_INT = 2 +}; + /***********/ /* Sorting */ /***********/ @@ -2668,6 +2695,87 @@ struct sortelt { typedef struct sortelt *SortElt; +/*********************************************************/ +/* Structures to save and restore for individual modules */ +/*********************************************************/ + +/* History */ +struct hist_stack { + int histactive; + int histdone; + int stophist; + int hlinesz; + char *hline; + char *hptr; + short *chwords; + int chwordlen; + int chwordpos; + int (*hgetc) _((void)); + void (*hungetc) _((int)); + void (*hwaddc) _((int)); + void (*hwbegin) _((int)); + void (*hwend) _((void)); + void (*addtoline) _((int)); + unsigned char *cstack; + int csp; +}; + +/* + * State of a lexical token buffer. + * + * It would be neater to include the pointer to the start of the buffer, + * however the current code structure means that the standard instance + * of this, tokstr, is visible in lots of places, so that's not + * convenient. + */ + +struct lexbufstate { + /* + * Next character to be added. + * Set to NULL when the buffer is to be visible from elsewhere. + */ + char *ptr; + /* Allocated buffer size */ + int siz; + /* Length in use */ + int len; +}; + +/* Lexical analyser */ +struct lex_stack { + int dbparens; + int isfirstln; + int isfirstch; + int lexflags; + enum lextok tok; + char *tokstr; + char *zshlextext; + struct lexbufstate lexbuf; + int lex_add_raw; + char *tokstr_raw; + struct lexbufstate lexbuf_raw; + int lexstop; + zlong toklineno; +}; + +/* Parser */ +struct parse_stack { + struct heredocs *hdocs; + + int incmdpos; + int aliasspaceflag; + int incond; + int inredir; + int incasepat; + int isnewlin; + int infor; + + int eclen, ecused, ecnpats; + Wordcode ecbuf; + Eccstr ecstrs; + int ecsoffs, ecssub, ecnfunc; +}; + /************************/ /* Flags to casemodifiy */ /************************/ diff --git a/Src/zsh.mdd b/Src/zsh.mdd index 9a8c923f9..c2e59c910 100644 --- a/Src/zsh.mdd +++ b/Src/zsh.mdd @@ -9,7 +9,8 @@ alwayslink=1 # autobins not specified because of alwayslink -objects="builtin.o compat.o cond.o exec.o glob.o hashtable.o hashnameddir.o \ +objects="builtin.o compat.o cond.o context.o \ +exec.o glob.o hashtable.o hashnameddir.o \ hist.o init.o input.o jobs.o lex.o linklist.o loop.o math.o \ mem.o module.o options.o params.o parse.o pattern.o prompt.o signals.o \ signames.o sort.o string.o subst.o text.o utils.o watch.o" @@ -21,9 +22,18 @@ hdrdeps="zshcurses.h zshterm.h" :<<\Make @CONFIG_MK@ +# If we're using gcc as the preprocessor, get rid of the additional +# lines generated by the preprocessor as they can confuse the script. +# We don't need these in other cases either, but can't necessarily rely +# on the option to remove them being the same. signames.c: signames1.awk signames2.awk ../config.h @SIGNAL_H@ $(AWK) -f $(sdir)/signames1.awk @SIGNAL_H@ >sigtmp.c - $(CPP) sigtmp.c >sigtmp.out + case "`$(CPP) --version </dev/null 2>&1`" in \ + *"Free Software Foundation"*) \ + $(CPP) -P sigtmp.c >sigtmp.out;; \ + *) \ + $(CPP) sigtmp.c >sigtmp.out;; \ + esac $(AWK) -f $(sdir)/signames2.awk sigtmp.out > $@ rm -f sigtmp.c sigtmp.out diff --git a/Src/ztype.h b/Src/ztype.h index eef0f23db..d1bef0a5a 100644 --- a/Src/ztype.h +++ b/Src/ztype.h @@ -75,3 +75,9 @@ #define WC_ZISTYPE(X,Y) zistype((X),(Y)) #define WC_ISPRINT(X) isprint(X) #endif + +#if defined(__APPLE__) && defined(BROKEN_ISPRINT) +#define ISPRINT(c) isprint_ascii(c) +#else +#define ISPRINT(c) isprint(c) +#endif |