From 32c2ebbaa5d7927f33ee0ecf98472a71cf902cf3 Mon Sep 17 00:00:00 2001 From: Tanaka Akira Date: Thu, 15 Apr 1999 18:05:35 +0000 Subject: zsh-3.1.5 --- Src/Zle/.cvsignore | 14 + Src/Zle/.distfiles | 10 + Src/Zle/.exrc | 2 + Src/Zle/comp.h | 137 ++ Src/Zle/comp1.c | 291 ++++ Src/Zle/comp1.mdd | 3 + Src/Zle/compctl.c | 1085 +++++++++++++ Src/Zle/compctl.mdd | 5 + Src/Zle/deltochar.c | 91 ++ Src/Zle/deltochar.mdd | 3 + Src/Zle/iwidgets.list | 172 +++ Src/Zle/zle.h | 137 ++ Src/Zle/zle.mdd | 70 + Src/Zle/zle_bindings.c | 421 +++++ Src/Zle/zle_hist.c | 1139 ++++++++++++++ Src/Zle/zle_keymap.c | 1238 +++++++++++++++ Src/Zle/zle_main.c | 907 +++++++++++ Src/Zle/zle_misc.c | 816 ++++++++++ Src/Zle/zle_move.c | 502 ++++++ Src/Zle/zle_params.c | 196 +++ Src/Zle/zle_refresh.c | 1116 ++++++++++++++ Src/Zle/zle_things.sed | 9 + Src/Zle/zle_thingy.c | 491 ++++++ Src/Zle/zle_tricky.c | 4015 ++++++++++++++++++++++++++++++++++++++++++++++++ Src/Zle/zle_utils.c | 650 ++++++++ Src/Zle/zle_vi.c | 925 +++++++++++ Src/Zle/zle_widget.sed | 7 + Src/Zle/zle_word.c | 477 ++++++ 28 files changed, 14929 insertions(+) create mode 100644 Src/Zle/.cvsignore create mode 100644 Src/Zle/.distfiles create mode 100644 Src/Zle/.exrc create mode 100644 Src/Zle/comp.h create mode 100644 Src/Zle/comp1.c create mode 100644 Src/Zle/comp1.mdd create mode 100644 Src/Zle/compctl.c create mode 100644 Src/Zle/compctl.mdd create mode 100644 Src/Zle/deltochar.c create mode 100644 Src/Zle/deltochar.mdd create mode 100644 Src/Zle/iwidgets.list create mode 100644 Src/Zle/zle.h create mode 100644 Src/Zle/zle.mdd create mode 100644 Src/Zle/zle_bindings.c create mode 100644 Src/Zle/zle_hist.c create mode 100644 Src/Zle/zle_keymap.c create mode 100644 Src/Zle/zle_main.c create mode 100644 Src/Zle/zle_misc.c create mode 100644 Src/Zle/zle_move.c create mode 100644 Src/Zle/zle_params.c create mode 100644 Src/Zle/zle_refresh.c create mode 100644 Src/Zle/zle_things.sed create mode 100644 Src/Zle/zle_thingy.c create mode 100644 Src/Zle/zle_tricky.c create mode 100644 Src/Zle/zle_utils.c create mode 100644 Src/Zle/zle_vi.c create mode 100644 Src/Zle/zle_widget.sed create mode 100644 Src/Zle/zle_word.c (limited to 'Src/Zle') diff --git a/Src/Zle/.cvsignore b/Src/Zle/.cvsignore new file mode 100644 index 000000000..b10adcc3b --- /dev/null +++ b/Src/Zle/.cvsignore @@ -0,0 +1,14 @@ +Makefile +Makefile.in +*.pro +*.o +*.o.c +*.so +*.mdh +*.mdhi +*.mdhs +*.mdh.tmp +thingies.list +widgets.list +zle_things.h +zle_widget.h diff --git a/Src/Zle/.distfiles b/Src/Zle/.distfiles new file mode 100644 index 000000000..42c62efe9 --- /dev/null +++ b/Src/Zle/.distfiles @@ -0,0 +1,10 @@ +DISTFILES_SRC=' + .cvsignore .distfiles .exrc + comp1.mdd comp.h comp1.c + compctl.mdd compctl.c + deltochar.mdd deltochar.c + zle.mdd iwidgets.list zle.h zle_bindings.c zle_hist.c + zle_keymap.c zle_main.c zle_misc.c zle_move.c zle_params.c + zle_refresh.c zle_things.sed zle_thingy.c zle_tricky.c + zle_utils.c zle_vi.c zle_widget.sed zle_word.c +' diff --git a/Src/Zle/.exrc b/Src/Zle/.exrc new file mode 100644 index 000000000..91d0b39ef --- /dev/null +++ b/Src/Zle/.exrc @@ -0,0 +1,2 @@ +set ai +set sw=4 diff --git a/Src/Zle/comp.h b/Src/Zle/comp.h new file mode 100644 index 000000000..5d7ef74e2 --- /dev/null +++ b/Src/Zle/comp.h @@ -0,0 +1,137 @@ +/* + * comp.h - header file for completion + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#undef compctlread + +typedef struct compctlp *Compctlp; +typedef struct compctl *Compctl; +typedef struct compcond *Compcond; + +/* node for compctl hash table (compctltab) */ + +struct compctlp { + HashNode next; /* next in hash chain */ + char *nam; /* command name */ + int flags; /* CURRENTLY UNUSED */ + Compctl cc; /* pointer to the compctl desc. */ +}; + +/* compctl -x condition */ + +struct compcond { + Compcond and, or; /* the next or'ed/and'ed conditions */ + int type; /* the type (CCT_*) */ + int n; /* the array length */ + union { /* these structs hold the data used to */ + struct { /* test this condition */ + int *a, *b; /* CCT_POS, CCT_NUMWORDS */ + } + r; + struct { /* CCT_CURSTR, CCT_CURPAT,... */ + int *p; + char **s; + } + s; + struct { /* CCT_RANGESTR,... */ + char **a, **b; + } + l; + } + u; +}; + +#define CCT_UNUSED 0 +#define CCT_POS 1 +#define CCT_CURSTR 2 +#define CCT_CURPAT 3 +#define CCT_WORDSTR 4 +#define CCT_WORDPAT 5 +#define CCT_CURSUF 6 +#define CCT_CURPRE 7 +#define CCT_CURSUB 8 +#define CCT_CURSUBC 9 +#define CCT_NUMWORDS 10 +#define CCT_RANGESTR 11 +#define CCT_RANGEPAT 12 + +/* Contains the real description for compctls */ + +struct compctl { + int refc; /* reference count */ + Compctl next; /* next compctl for -x */ + unsigned long mask; /* mask of things to complete (CC_*) */ + char *keyvar; /* for -k (variable) */ + char *glob; /* for -g (globbing) */ + char *str; /* for -s (expansion) */ + char *func; /* for -K (function) */ + char *explain; /* for -X (explanation) */ + char *ylist; /* for -y (user-defined desc. for listing) */ + char *prefix, *suffix; /* for -P and -S (prefix, suffix) */ + char *subcmd; /* for -l (command name to use) */ + char *withd; /* for -w (with directory */ + char *hpat; /* for -H (history pattern) */ + int hnum; /* for -H (number of events to search) */ + Compctl ext; /* for -x (first of the compctls after -x) */ + Compcond cond; /* for -x (condition for this compctl) */ + Compctl xor; /* for + (next of the xor'ed compctls) */ +}; + +/* objects to complete */ +#define CC_FILES (1<<0) +#define CC_COMMPATH (1<<1) +#define CC_REMOVE (1<<2) +#define CC_OPTIONS (1<<3) +#define CC_VARS (1<<4) +#define CC_BINDINGS (1<<5) +#define CC_ARRAYS (1<<6) +#define CC_INTVARS (1<<7) +#define CC_SHFUNCS (1<<8) +#define CC_PARAMS (1<<9) +#define CC_ENVVARS (1<<10) +#define CC_JOBS (1<<11) +#define CC_RUNNING (1<<12) +#define CC_STOPPED (1<<13) +#define CC_BUILTINS (1<<14) +#define CC_ALREG (1<<15) +#define CC_ALGLOB (1<<16) +#define CC_USERS (1<<17) +#define CC_DISCMDS (1<<18) +#define CC_EXCMDS (1<<19) +#define CC_SCALARS (1<<20) +#define CC_READONLYS (1<<21) +#define CC_SPECIALS (1<<22) +#define CC_DELETE (1<<23) +#define CC_NAMED (1<<24) +#define CC_QUOTEFLAG (1<<25) +#define CC_EXTCMDS (1<<26) +#define CC_RESWDS (1<<27) +#define CC_DIRS (1<<28) + +#define CC_EXPANDEXPL (1<<30) +#define CC_RESERVED (1<<31) diff --git a/Src/Zle/comp1.c b/Src/Zle/comp1.c new file mode 100644 index 000000000..acd1288a6 --- /dev/null +++ b/Src/Zle/comp1.c @@ -0,0 +1,291 @@ +/* + * comp1.c - base of the completion system + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "comp1.mdh" + +#include "comp1.pro" + +/* default completion infos */ + +/**/ +struct compctl cc_compos, cc_default, cc_first, cc_dummy; + +/* hash table for completion info for commands */ + +/**/ +HashTable compctltab; + +/* Words on the command line, for use in completion */ + +/**/ +int clwsize, clwnum, clwpos; +/**/ +char **clwords; + +/* != 0 if in a shell function called from completion, such that read -[cl] * + * will work (i.e., the line is metafied, and the above word arrays are OK). */ + +/**/ +int incompctlfunc; + +/**/ +static void +createcompctltable(void) +{ + compctltab = newhashtable(23, "compctltab", NULL); + + compctltab->hash = hasher; + compctltab->emptytable = emptyhashtable; + compctltab->filltable = NULL; + compctltab->addnode = addhashnode; + compctltab->getnode = gethashnode2; + compctltab->getnode2 = gethashnode2; + compctltab->removenode = removehashnode; + compctltab->disablenode = NULL; + compctltab->enablenode = NULL; + compctltab->freenode = freecompctlp; + compctltab->printnode = NULL; +} + +/**/ +static void +freecompctlp(HashNode hn) +{ + Compctlp ccp = (Compctlp) hn; + + zsfree(ccp->nam); + freecompctl(ccp->cc); + zfree(ccp, sizeof(struct compctlp)); +} + +/**/ +void +freecompctl(Compctl cc) +{ + if (cc == &cc_default || + cc == &cc_first || + cc == &cc_compos || + --cc->refc > 0) + return; + + zsfree(cc->keyvar); + zsfree(cc->glob); + zsfree(cc->str); + zsfree(cc->func); + zsfree(cc->explain); + zsfree(cc->ylist); + zsfree(cc->prefix); + zsfree(cc->suffix); + zsfree(cc->hpat); + zsfree(cc->subcmd); + if (cc->cond) + freecompcond(cc->cond); + if (cc->ext) { + Compctl n, m; + + n = cc->ext; + do { + m = (Compctl) (n->next); + freecompctl(n); + n = m; + } + while (n); + } + if (cc->xor && cc->xor != &cc_default) + freecompctl(cc->xor); + zfree(cc, sizeof(struct compctl)); +} + +/**/ +void +freecompcond(void *a) +{ + Compcond cc = (Compcond) a; + Compcond and, or, c; + int n; + + for (c = cc; c; c = or) { + or = c->or; + for (; c; c = and) { + and = c->and; + if (c->type == CCT_POS || + c->type == CCT_NUMWORDS) { + free(c->u.r.a); + free(c->u.r.b); + } else if (c->type == CCT_CURSUF || + c->type == CCT_CURPRE) { + for (n = 0; n < c->n; n++) + if (c->u.s.s[n]) + zsfree(c->u.s.s[n]); + free(c->u.s.s); + } else if (c->type == CCT_RANGESTR || + c->type == CCT_RANGEPAT) { + for (n = 0; n < c->n; n++) + if (c->u.l.a[n]) + zsfree(c->u.l.a[n]); + free(c->u.l.a); + for (n = 0; n < c->n; n++) + if (c->u.l.b[n]) + zsfree(c->u.l.b[n]); + free(c->u.l.b); + } else { + for (n = 0; n < c->n; n++) + if (c->u.s.s[n]) + zsfree(c->u.s.s[n]); + free(c->u.s.p); + free(c->u.s.s); + } + zfree(c, sizeof(struct compcond)); + } + } +} + +/**/ +int +compctlread(char *name, char **args, char *ops, char *reply) +{ + char *buf, *bptr; + + /* only allowed to be called for completion */ + if (!incompctlfunc) { + zwarnnam(name, "option valid only in functions called for completion", + NULL, 0); + return 1; + } + + if (ops['l']) { + /* -ln gives the index of the word the cursor is currently on, which is + available in cs (but remember that Zsh counts from one, not zero!) */ + if (ops['n']) { + char nbuf[14]; + + if (ops['e'] || ops['E']) + printf("%d\n", cs + 1); + if (!ops['e']) { + sprintf(nbuf, "%d", cs + 1); + setsparam(reply, ztrdup(nbuf)); + } + return 0; + } + /* without -n, the current line is assigned to the given parameter as a + scalar */ + if (ops['e'] || ops['E']) { + zputs((char *) line, stdout); + putchar('\n'); + } + if (!ops['e']) + setsparam(reply, ztrdup((char *) line)); + } else { + int i; + + /* -cn gives the current cursor position within the current word, which + is available in clwpos (but remember that Zsh counts from one, not + zero!) */ + if (ops['n']) { + char nbuf[14]; + + if (ops['e'] || ops['E']) + printf("%d\n", clwpos + 1); + if (!ops['e']) { + sprintf(nbuf, "%d", clwpos + 1); + setsparam(reply, ztrdup(nbuf)); + } + return 0; + } + /* without -n, the words of the current line are assigned to the given + parameters separately */ + if (ops['A'] && !ops['e']) { + /* the -A option means that one array is specified, instead of + many parameters */ + char **p, **b = (char **)zcalloc((clwnum + 1) * sizeof(char *)); + + for (i = 0, p = b; i < clwnum; p++, i++) + *p = ztrdup(clwords[i]); + + setaparam(reply, b); + return 0; + } + if (ops['e'] || ops['E']) { + for (i = 0; i < clwnum; i++) { + zputs(clwords[i], stdout); + putchar('\n'); + } + + if (ops['e']) + return 0; + } + + for (i = 0; i < clwnum && *args; reply = *args++, i++) + setsparam(reply, ztrdup(clwords[i])); + + if (i < clwnum) { + int j, len; + + for (j = i, len = 0; j < clwnum; len += strlen(clwords[j++])); + bptr = buf = zalloc(len + j - i); + while (i < clwnum) { + strucpy(&bptr, clwords[i++]); + *bptr++ = ' '; + } + bptr[-1] = '\0'; + } else + buf = ztrdup(""); + setsparam(reply, buf); + } + return 0; +} + +/**/ +int +boot_comp1(Module m) +{ + compctlreadptr = compctlread; + clwords = (char **) zcalloc((clwsize = 16) * sizeof(char *)); + createcompctltable(); + cc_compos.mask = CC_COMMPATH; + cc_default.refc = 10000; + cc_default.mask = CC_FILES; + cc_first.refc = 10000; + cc_first.mask = 0; + return 0; +} + +#ifdef MODULE + +/**/ +int +cleanup_comp1(Module m) +{ + deletehashtable(compctltab); + zfree(clwords, clwsize * sizeof(char *)); + compctlreadptr = fallback_compctlread; + return 0; +} + +#endif /* MODULE */ diff --git a/Src/Zle/comp1.mdd b/Src/Zle/comp1.mdd new file mode 100644 index 000000000..9037e568a --- /dev/null +++ b/Src/Zle/comp1.mdd @@ -0,0 +1,3 @@ +objects="comp1.o" + +headers="comp.h" diff --git a/Src/Zle/compctl.c b/Src/Zle/compctl.c new file mode 100644 index 000000000..658cf4161 --- /dev/null +++ b/Src/Zle/compctl.c @@ -0,0 +1,1085 @@ +/* + * compctl.c - the compctl builtin + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "compctl.mdh" +#include "compctl.pro" + +#define COMP_LIST (1<<0) /* -L */ +#define COMP_COMMAND (1<<1) /* -C */ +#define COMP_DEFAULT (1<<2) /* -D */ +#define COMP_FIRST (1<<3) /* -T */ +#define COMP_REMOVE (1<<4) + +#define COMP_SPECIAL (COMP_COMMAND|COMP_DEFAULT|COMP_FIRST) + +/* Flag for listing, command, default, or first completion */ +static int cclist; + +/* Mask for determining what to print */ +static unsigned long showmask = 0; + +/* Parse the basic flags for `compctl' */ + +/**/ +static int +get_compctl(char *name, char ***av, Compctl cc, int first, int isdef) +{ + /* Parse the basic flags for completion: + * first is a flag that we are not in extended completion, + * while hx indicates or (+) completion (need to know for + * default and command completion as the initial compctl is special). + * cct is a temporary just to hold flags; it never needs freeing. + */ + struct compctl cct; + char **argv = *av; + int ready = 0, hx = 0; + + /* Handle `compctl + foo ...' specially: turn it into + * a default compctl by removing it from the hash table. + */ + if (first && argv[0][0] == '+' && !argv[0][1] && + !(argv[1] && argv[1][0] == '-' && argv[1][1])) { + argv++; + if(argv[0] && argv[0][0] == '-') + argv++; + *av = argv; + freecompctl(cc); + cclist = COMP_REMOVE; + return 0; + } + + memset((void *)&cct, 0, sizeof(cct)); + + /* Loop through the flags until we have no more: * + * those with arguments are not properly allocated yet, * + * we just hang on to the argument that was passed. */ + for (; !ready && argv[0] && argv[0][0] == '-' && (argv[0][1] || !first);) { + if (!argv[0][1]) + *argv = "-+"; + while (!ready && *++(*argv)) { + if(**argv == Meta) + *++*argv ^= 32; + switch (**argv) { + case 'f': + cct.mask |= CC_FILES; + break; + case 'c': + cct.mask |= CC_COMMPATH; + break; + case 'm': + cct.mask |= CC_EXTCMDS; + break; + case 'w': + cct.mask |= CC_RESWDS; + break; + case 'o': + cct.mask |= CC_OPTIONS; + break; + case 'v': + cct.mask |= CC_VARS; + break; + case 'b': + cct.mask |= CC_BINDINGS; + break; + case 'A': + cct.mask |= CC_ARRAYS; + break; + case 'I': + cct.mask |= CC_INTVARS; + break; + case 'F': + cct.mask |= CC_SHFUNCS; + break; + case 'p': + cct.mask |= CC_PARAMS; + break; + case 'E': + cct.mask |= CC_ENVVARS; + break; + case 'j': + cct.mask |= CC_JOBS; + break; + case 'r': + cct.mask |= CC_RUNNING; + break; + case 'z': + cct.mask |= CC_STOPPED; + break; + case 'B': + cct.mask |= CC_BUILTINS; + break; + case 'a': + cct.mask |= CC_ALREG | CC_ALGLOB; + break; + case 'R': + cct.mask |= CC_ALREG; + break; + case 'G': + cct.mask |= CC_ALGLOB; + break; + case 'u': + cct.mask |= CC_USERS; + break; + case 'd': + cct.mask |= CC_DISCMDS; + break; + case 'e': + cct.mask |= CC_EXCMDS; + break; + case 'N': + cct.mask |= CC_SCALARS; + break; + case 'O': + cct.mask |= CC_READONLYS; + break; + case 'Z': + cct.mask |= CC_SPECIALS; + break; + case 'q': + cct.mask |= CC_REMOVE; + break; + case 'U': + cct.mask |= CC_DELETE; + break; + case 'n': + cct.mask |= CC_NAMED; + break; + case 'Q': + cct.mask |= CC_QUOTEFLAG; + break; + case '/': + cct.mask |= CC_DIRS; + break; + case 'k': + if ((*argv)[1]) { + cct.keyvar = (*argv) + 1; + *argv = "" - 1; + } else if (!argv[1]) { + zwarnnam(name, "variable name expected after -%c", NULL, + **argv); + return 1; + } else { + cct.keyvar = *++argv; + *argv = "" - 1; + } + break; + case 'K': + if ((*argv)[1]) { + cct.func = (*argv) + 1; + *argv = "" - 1; + } else if (!argv[1]) { + zwarnnam(name, "function name expected after -%c", NULL, + **argv); + return 1; + } else { + cct.func = *++argv; + *argv = "" - 1; + } + break; + case 'Y': + cct.mask |= CC_EXPANDEXPL; + goto expl; + case 'X': + cct.mask &= ~CC_EXPANDEXPL; + expl: + if ((*argv)[1]) { + cct.explain = (*argv) + 1; + *argv = "" - 1; + } else if (!argv[1]) { + zwarnnam(name, "string expected after -%c", NULL, **argv); + return 1; + } else { + cct.explain = *++argv; + *argv = "" - 1; + } + break; + case 'y': + if ((*argv)[1]) { + cct.ylist = (*argv) + 1; + *argv = "" - 1; + } else if (!argv[1]) { + zwarnnam(name, "function/variable expected after -%c", + NULL, **argv); + } else { + cct.ylist = *++argv; + *argv = "" - 1; + } + break; + case 'P': + if ((*argv)[1]) { + cct.prefix = (*argv) + 1; + *argv = "" - 1; + } else if (!argv[1]) { + zwarnnam(name, "string expected after -%c", NULL, **argv); + return 1; + } else { + cct.prefix = *++argv; + *argv = "" - 1; + } + break; + case 'S': + if ((*argv)[1]) { + cct.suffix = (*argv) + 1; + *argv = "" - 1; + } else if (!argv[1]) { + zwarnnam(name, "string expected after -%c", NULL, **argv); + return 1; + } else { + cct.suffix = *++argv; + *argv = "" - 1; + } + break; + case 'g': + if ((*argv)[1]) { + cct.glob = (*argv) + 1; + *argv = "" - 1; + } else if (!argv[1]) { + zwarnnam(name, "glob pattern expected after -%c", NULL, + **argv); + return 1; + } else { + cct.glob = *++argv; + *argv = "" - 1; + } + break; + case 's': + if ((*argv)[1]) { + cct.str = (*argv) + 1; + *argv = "" - 1; + } else if (!argv[1]) { + zwarnnam(name, "command string expected after -%c", NULL, + **argv); + return 1; + } else { + cct.str = *++argv; + *argv = "" - 1; + } + break; + case 'l': + if ((*argv)[1]) { + cct.subcmd = (*argv) + 1; + *argv = "" - 1; + } else if (!argv[1]) { + zwarnnam(name, "command name expected after -%c", NULL, + **argv); + return 1; + } else { + cct.subcmd = *++argv; + *argv = "" - 1; + } + break; + case 'W': + if ((*argv)[1]) { + cct.withd = (*argv) + 1; + *argv = "" - 1; + } else if (!argv[1]) { + zwarnnam(name, "path expected after -%c", NULL, + **argv); + return 1; + } else { + cct.withd = *++argv; + *argv = "" - 1; + } + break; + case 'H': + if ((*argv)[1]) + cct.hnum = atoi((*argv) + 1); + else if (argv[1]) + cct.hnum = atoi(*++argv); + else { + zwarnnam(name, "number expected after -%c", NULL, + **argv); + return 1; + } + if (!argv[1]) { + zwarnnam(name, "missing pattern after -%c", NULL, + **argv); + return 1; + } + cct.hpat = *++argv; + if (cct.hnum < 1) + cct.hnum = 0; + if (*cct.hpat == '*' && !cct.hpat[1]) + cct.hpat = ""; + *argv = "" - 1; + break; + case 'C': + if (first && !hx) { + cclist |= COMP_COMMAND; + } else { + zwarnnam(name, "misplaced command completion (-C) flag", + NULL, 0); + return 1; + } + break; + case 'D': + if (first && !hx) { + isdef = 1; + cclist |= COMP_DEFAULT; + } else { + zwarnnam(name, "misplaced default completion (-D) flag", + NULL, 0); + return 1; + } + break; + case 'T': + if (first && !hx) { + cclist |= COMP_FIRST; + } else { + zwarnnam(name, "misplaced first completion (-T) flag", + NULL, 0); + return 1; + } + break; + case 'L': + if (!first || hx) { + zwarnnam(name, "illegal use of -L flag", NULL, 0); + return 1; + } + cclist |= COMP_LIST; + break; + case 'x': + if (!argv[1]) { + zwarnnam(name, "condition expected after -%c", NULL, + **argv); + return 1; + } + if (first) { + argv++; + if (get_xcompctl(name, &argv, &cct, isdef)) { + if (cct.ext) + freecompctl(cct.ext); + return 1; + } + ready = 2; + } else { + zwarnnam(name, "recursive extended completion not allowed", + NULL, 0); + return 1; + } + break; + default: + if (!first && (**argv == '-' || **argv == '+')) + (*argv)--, argv--, ready = 1; + else { + zwarnnam(name, "bad option: -%c", NULL, **argv); + return 1; + } + } + } + + if (*++argv && (!ready || ready == 2) && + **argv == '+' && !argv[0][1]) { + /* There's an alternative (+) completion: assign + * what we have so far before moving on to that. + */ + if (cc_assign(name, &cc, &cct, first && !hx)) + return 1; + + hx = 1; + ready = 0; + + if (!*++argv || **argv != '-' || + (**argv == '-' && (!argv[0][1] || + (argv[0][1] == '-' && !argv[0][2])))) { + /* No argument to +, which means do default completion */ + if (isdef) + zwarnnam(name, + "recursive xor'd default completions not allowed", + NULL, 0); + else + cc->xor = &cc_default; + } else { + /* more flags follow: prepare to loop again */ + cc->xor = (Compctl) zcalloc(sizeof(*cc)); + cc = cc->xor; + memset((void *)&cct, 0, sizeof(cct)); + } + } + } + if (!ready && *argv && **argv == '-') + argv++; + + if (! (cct.mask & (CC_EXCMDS | CC_DISCMDS))) + cct.mask |= CC_EXCMDS; + + /* assign the last set of flags we parsed */ + if (cc_assign(name, &cc, &cct, first && !hx)) + return 1; + + *av = argv; + + return 0; +} + +/* Handle the -x ... -- part of compctl. */ + +/**/ +static int +get_xcompctl(char *name, char ***av, Compctl cc, int isdef) +{ + char **argv = *av, *t, *tt, sav; + int n, l = 0, ready = 0; + Compcond m, c, o; + Compctl *next = &(cc->ext); + + while (!ready) { + /* o keeps track of or's, m remembers the starting condition, + * c is the current condition being parsed + */ + o = m = c = (Compcond) zcalloc(sizeof(*c)); + /* Loop over each condition: something like 's[...][...], p[...]' */ + for (t = *argv; *t;) { + while (*t == ' ') + t++; + /* First get the condition code */ + switch (*t) { + case 's': + c->type = CCT_CURSUF; + break; + case 'S': + c->type = CCT_CURPRE; + break; + case 'p': + c->type = CCT_POS; + break; + case 'c': + c->type = CCT_CURSTR; + break; + case 'C': + c->type = CCT_CURPAT; + break; + case 'w': + c->type = CCT_WORDSTR; + break; + case 'W': + c->type = CCT_WORDPAT; + break; + case 'n': + c->type = CCT_CURSUB; + break; + case 'N': + c->type = CCT_CURSUBC; + break; + case 'm': + c->type = CCT_NUMWORDS; + break; + case 'r': + c->type = CCT_RANGESTR; + break; + case 'R': + c->type = CCT_RANGEPAT; + break; + default: + t[1] = '\0'; + zwarnnam(name, "unknown condition code: %s", t, 0); + zfree(m, sizeof(struct compcond)); + + return 1; + } + /* Now get the arguments in square brackets */ + if (t[1] != '[') { + t[1] = '\0'; + zwarnnam(name, "expected condition after condition code: %s", t, 0); + zfree(m, sizeof(struct compcond)); + + return 1; + } + t++; + /* First count how many or'd arguments there are, + * marking the active ]'s and ,'s with unprintable characters. + */ + for (n = 0, tt = t; *tt == '['; n++) { + for (l = 1, tt++; *tt && l; tt++) + if (*tt == '\\' && tt[1]) + tt++; + else if (*tt == '[') + l++; + else if (*tt == ']') + l--; + else if (l == 1 && *tt == ',') + *tt = '\201'; + if (tt[-1] == ']') + tt[-1] = '\200'; + } + + if (l) { + t[1] = '\0'; + zwarnnam(name, "error after condition code: %s", t, 0); + zfree(m, sizeof(struct compcond)); + + return 1; + } + c->n = n; + + /* Allocate space for all the arguments of the conditions */ + if (c->type == CCT_POS || + c->type == CCT_NUMWORDS) { + c->u.r.a = (int *)zcalloc(n * sizeof(int)); + c->u.r.b = (int *)zcalloc(n * sizeof(int)); + } else if (c->type == CCT_CURSUF || + c->type == CCT_CURPRE) + c->u.s.s = (char **)zcalloc(n * sizeof(char *)); + + else if (c->type == CCT_RANGESTR || + c->type == CCT_RANGEPAT) { + c->u.l.a = (char **)zcalloc(n * sizeof(char *)); + c->u.l.b = (char **)zcalloc(n * sizeof(char *)); + } else { + c->u.s.p = (int *)zcalloc(n * sizeof(int)); + c->u.s.s = (char **)zcalloc(n * sizeof(char *)); + } + /* Now loop over the actual arguments */ + for (l = 0; *t == '['; l++, t++) { + for (t++; *t && *t == ' '; t++); + tt = t; + if (c->type == CCT_POS || + c->type == CCT_NUMWORDS) { + /* p[...] or m[...]: one or two numbers expected */ + for (; *t && *t != '\201' && *t != '\200'; t++); + if (!(sav = *t)) { + zwarnnam(name, "error in condition", NULL, 0); + freecompcond(m); + return 1; + } + *t = '\0'; + c->u.r.a[l] = atoi(tt); + /* Second argument is optional: see if it's there */ + if (sav == '\200') + /* no: copy first argument */ + c->u.r.b[l] = c->u.r.a[l]; + else { + tt = ++t; + for (; *t && *t != '\200'; t++); + if (!*t) { + zwarnnam(name, "error in condition", NULL, 0); + freecompcond(m); + return 1; + } + *t = '\0'; + c->u.r.b[l] = atoi(tt); + } + } else if (c->type == CCT_CURSUF || + c->type == CCT_CURPRE) { + /* -s[..] or -S[..]: single string expected */ + for (; *t && *t != '\200'; t++) + if (*t == '\201') + *t = ','; + if (!*t) { + zwarnnam(name, "error in condition", NULL, 0); + freecompcond(m); + return 1; + } + *t = '\0'; + c->u.s.s[l] = ztrdup(tt); + } else if (c->type == CCT_RANGESTR || + c->type == CCT_RANGEPAT) { + /* -r[..,..] or -R[..,..]: two strings expected */ + for (; *t && *t != '\201'; t++); + if (!*t) { + zwarnnam(name, "error in condition", NULL, 0); + freecompcond(m); + return 1; + } + *t = '\0'; + c->u.l.a[l] = ztrdup(tt); + tt = ++t; + /* any more commas are text, not active */ + for (; *t && *t != '\200'; t++) + if (*t == '\201') + *t = ','; + if (!*t) { + zwarnnam(name, "error in condition", NULL, 0); + freecompcond(m); + return 1; + } + *t = '\0'; + c->u.l.b[l] = ztrdup(tt); + } else { + /* remaining patterns are number followed by string */ + for (; *t && *t != '\200' && *t != '\201'; t++); + if (!*t || *t == '\200') { + zwarnnam(name, "error in condition", NULL, 0); + freecompcond(m); + return 1; + } + *t = '\0'; + c->u.s.p[l] = atoi(tt); + tt = ++t; + for (; *t && *t != '\200'; t++) + if (*t == '\201') + *t = ','; + if (!*t) { + zwarnnam(name, "error in condition", NULL, 0); + freecompcond(m); + return 1; + } + *t = '\0'; + c->u.s.s[l] = ztrdup(tt); + } + } + while (*t == ' ') + t++; + if (*t == ',') { + /* Another condition to `or' */ + o->or = c = (Compcond) zcalloc(sizeof(*c)); + o = c; + t++; + } else if (*t) { + /* Another condition to `and' */ + c->and = (Compcond) zcalloc(sizeof(*c)); + c = c->and; + } + } + /* Assign condition to current compctl */ + *next = (Compctl) zcalloc(sizeof(*cc)); + (*next)->cond = m; + argv++; + /* End of the condition; get the flags that go with it. */ + if (get_compctl(name, &argv, *next, 0, isdef)) + return 1; + if ((!argv || !*argv) && (cclist & COMP_SPECIAL)) + /* default, first, or command completion finished */ + ready = 1; + else { + /* see if we are looking for more conditions or are + * ready to return (ready = 1) + */ + if (!argv || !*argv || **argv != '-' || + ((!argv[0][1] || argv[0][1] == '+') && !argv[1])) { + zwarnnam(name, "missing command names", NULL, 0); + return 1; + } + if (!strcmp(*argv, "--")) + ready = 1; + else if (!strcmp(*argv, "-+") && argv[1] && + !strcmp(argv[1], "--")) { + ready = 1; + argv++; + } + argv++; + /* prepare to put the next lot of conditions on the end */ + next = &((*next)->next); + } + } + /* save position at end of parsing */ + *av = argv - 1; + return 0; +} + +/**/ +static int +cc_assign(char *name, Compctl *ccptr, Compctl cct, int reass) +{ + /* Copy over the details from the values in cct to those in *ccptr */ + Compctl cc; + + if (cct->subcmd && (cct->keyvar || cct->glob || cct->str || + cct->func || cct->explain || cct->ylist || + cct->prefix)) { + zwarnnam(name, "illegal combination of options", NULL, 0); + return 1; + } + + /* Handle assignment of new default or command completion */ + if (reass && !(cclist & COMP_LIST)) { + /* if not listing */ + if (cclist == (COMP_COMMAND|COMP_DEFAULT) + || cclist == (COMP_COMMAND|COMP_FIRST) + || cclist == (COMP_DEFAULT|COMP_FIRST) + || cclist == COMP_SPECIAL) { + zwarnnam(name, "can't set -D, -T, and -C simultaneously", NULL, 0); + /* ... because the following code wouldn't work. */ + return 1; + } + if (cclist & COMP_COMMAND) { + /* command */ + *ccptr = &cc_compos; + cc_reassign(*ccptr); + } else if (cclist & COMP_DEFAULT) { + /* default */ + *ccptr = &cc_default; + cc_reassign(*ccptr); + } else if (cclist & COMP_FIRST) { + /* first */ + *ccptr = &cc_first; + cc_reassign(*ccptr); + } + } + + /* Free the old compctl */ + cc = *ccptr; + zsfree(cc->keyvar); + zsfree(cc->glob); + zsfree(cc->str); + zsfree(cc->func); + zsfree(cc->explain); + zsfree(cc->ylist); + zsfree(cc->prefix); + zsfree(cc->suffix); + zsfree(cc->subcmd); + zsfree(cc->withd); + zsfree(cc->hpat); + + /* and copy over the new stuff, (permanently) allocating + * space for strings. + */ + cc->mask = cct->mask; + cc->keyvar = ztrdup(cct->keyvar); + cc->glob = ztrdup(cct->glob); + cc->str = ztrdup(cct->str); + cc->func = ztrdup(cct->func); + cc->explain = ztrdup(cct->explain); + cc->ylist = ztrdup(cct->ylist); + cc->prefix = ztrdup(cct->prefix); + cc->suffix = ztrdup(cct->suffix); + cc->subcmd = ztrdup(cct->subcmd); + cc->withd = ztrdup(cct->withd); + cc->hpat = ztrdup(cct->hpat); + cc->hnum = cct->hnum; + + /* careful with extended completion: it's already allocated */ + cc->ext = cct->ext; + + return 0; +} + +/**/ +static void +cc_reassign(Compctl cc) +{ + /* Free up a new default or command completion: + * this is a hack to free up the parts which should be deleted, + * without removing the basic variable which is statically allocated + */ + Compctl c2; + + c2 = (Compctl) zcalloc(sizeof *cc); + c2->xor = cc->xor; + c2->ext = cc->ext; + c2->refc = 1; + + freecompctl(c2); + + cc->ext = cc->xor = NULL; +} + +/**/ +static void +compctl_process_cc(char **s, Compctl cc) +{ + Compctlp ccp; + + if (cclist & COMP_REMOVE) { + /* Delete entries for the commands listed */ + for (; *s; s++) { + if ((ccp = (Compctlp) compctltab->removenode(compctltab, *s))) + compctltab->freenode((HashNode) ccp); + } + } else { + /* Add the compctl just read to the hash table */ + + cc->refc = 0; + for (; *s; s++) { + cc->refc++; + ccp = (Compctlp) zalloc(sizeof *ccp); + ccp->cc = cc; + compctltab->addnode(compctltab, ztrdup(*s), ccp); + } + } +} + +/* Print a `compctl' */ + +/**/ +static void +printcompctl(char *s, Compctl cc, int printflags) +{ + Compctl cc2; + char *css = "fcqovbAIFpEjrzBRGudeNOZUnQmw/"; + char *mss = " pcCwWsSnNmrR"; + unsigned long t = 0x7fffffff; + unsigned long flags = cc->mask; + unsigned long oldshowmask; + + if ((flags & CC_EXCMDS) && !(flags & CC_DISCMDS)) + flags &= ~CC_EXCMDS; + + /* If showmask is non-zero, then print only those * + * commands with that flag set. */ + if (showmask && !(flags & showmask)) + return; + + /* Temporarily clear showmask in case we make * + * recursive calls to printcompctl. */ + oldshowmask = showmask; + showmask = 0; + + /* print either command name or start of compctl command itself */ + if (s) { + if (cclist & COMP_LIST) { + printf("compctl"); + if (cc == &cc_compos) + printf(" -C"); + if (cc == &cc_default) + printf(" -D"); + if (cc == &cc_first) + printf(" -T"); + } else + quotedzputs(s, stdout); + } + + /* loop through flags w/o args that are set, printing them if so */ + if (flags & t) { + printf(" -"); + if ((flags & (CC_ALREG | CC_ALGLOB)) == (CC_ALREG | CC_ALGLOB)) + putchar('a'), flags &= ~(CC_ALREG | CC_ALGLOB); + while (*css) { + if (flags & t & 1) + putchar(*css); + css++; + flags >>= 1; + t >>= 1; + } + } + + /* now flags with arguments */ + flags = cc->mask; + printif(cc->keyvar, 'k'); + printif(cc->func, 'K'); + printif(cc->explain, (cc->mask & CC_EXPANDEXPL) ? 'Y' : 'X'); + printif(cc->ylist, 'y'); + printif(cc->prefix, 'P'); + printif(cc->suffix, 'S'); + printif(cc->glob, 'g'); + printif(cc->str, 's'); + printif(cc->subcmd, 'l'); + printif(cc->withd, 'W'); + if (cc->hpat) { + printf(" -H %d ", cc->hnum); + quotedzputs(cc->hpat, stdout); + } + + /* now the -x ... -- extended completion part */ + if (cc->ext) { + Compcond c, o; + int i; + + cc2 = cc->ext; + printf(" -x"); + + while (cc2) { + /* loop over conditions */ + c = cc2->cond; + + printf(" '"); + for (c = cc2->cond; c;) { + /* loop over or's */ + o = c->or; + while (c) { + /* loop over and's */ + putchar(mss[c->type]); + + for (i = 0; i < c->n; i++) { + /* for all [...]'s of a given condition */ + putchar('['); + switch (c->type) { + case CCT_POS: + case CCT_NUMWORDS: + printf("%d,%d", c->u.r.a[i], c->u.r.b[i]); + break; + case CCT_CURSUF: + case CCT_CURPRE: + printqt(c->u.s.s[i]); + break; + case CCT_RANGESTR: + case CCT_RANGEPAT: + printqt(c->u.l.a[i]); + putchar(','); + printqt(c->u.l.b[i]); + break; + default: + printf("%d,", c->u.s.p[i]); + printqt(c->u.s.s[i]); + } + putchar(']'); + } + if ((c = c->and)) + putchar(' '); + } + if ((c = o)) + printf(" , "); + } + putchar('\''); + c = cc2->cond; + cc2->cond = NULL; + /* now print the flags for the current condition */ + printcompctl(NULL, cc2, 0); + cc2->cond = c; + if ((cc2 = (Compctl) (cc2->next))) + printf(" -"); + } + if (cclist & COMP_LIST) + printf(" --"); + } + if (cc && cc->xor) { + /* print xor'd (+) completions */ + printf(" +"); + if (cc->xor != &cc_default) + printcompctl(NULL, cc->xor, 0); + } + if (s) { + if ((cclist & COMP_LIST) && (cc != &cc_compos) + && (cc != &cc_default) && (cc != &cc_first)) { + if(s[0] == '-' || s[0] == '+') + printf(" -"); + putchar(' '); + quotedzputs(s, stdout); + } + putchar('\n'); + } + + showmask = oldshowmask; +} + +/**/ +static void +printcompctlp(HashNode hn, int printflags) +{ + Compctlp ccp = (Compctlp) hn; + + /* Function needed for use by scanhashtable() */ + printcompctl(ccp->nam, ccp->cc, printflags); +} + +/* Main entry point for the `compctl' builtin */ + +/**/ +static int +bin_compctl(char *name, char **argv, char *ops, int func) +{ + Compctl cc = NULL; + int ret = 0; + + /* clear static flags */ + cclist = 0; + showmask = 0; + + /* Parse all the arguments */ + if (*argv) { + cc = (Compctl) zcalloc(sizeof(*cc)); + if (get_compctl(name, &argv, cc, 1, 0)) { + freecompctl(cc); + return 1; + } + + /* remember flags for printing */ + showmask = cc->mask; + if ((showmask & CC_EXCMDS) && !(showmask & CC_DISCMDS)) + showmask &= ~CC_EXCMDS; + + /* if no command arguments or just listing, we don't want cc */ + if (!*argv || (cclist & COMP_LIST)) + freecompctl(cc); + } + + /* If no commands and no -C, -T, or -D, print all the compctl's * + * If some flags (other than -C, -T, or -D) were given, then * + * only print compctl containing those flags. */ + if (!*argv && !(cclist & COMP_SPECIAL)) { + scanhashtable(compctltab, 1, 0, 0, compctltab->printnode, 0); + printcompctl((cclist & COMP_LIST) ? "" : "COMMAND", &cc_compos, 0); + printcompctl((cclist & COMP_LIST) ? "" : "DEFAULT", &cc_default, 0); + printcompctl((cclist & COMP_LIST) ? "" : "FIRST", &cc_first, 0); + return ret; + } + + /* If we're listing and we've made it to here, then there are arguments * + * or a COMP_SPECIAL flag (-D, -C, -T), so print only those. */ + if (cclist & COMP_LIST) { + HashNode hn; + char **ptr; + + showmask = 0; + for (ptr = argv; *ptr; ptr++) { + if ((hn = compctltab->getnode(compctltab, *ptr))) { + compctltab->printnode(hn, 0); + } else { + zwarnnam(name, "no compctl defined for %s", *ptr, 0); + ret = 1; + } + } + if (cclist & COMP_COMMAND) + printcompctl("", &cc_compos, 0); + if (cclist & COMP_DEFAULT) + printcompctl("", &cc_default, 0); + if (cclist & COMP_FIRST) + printcompctl("", &cc_first, 0); + return ret; + } + + /* Assign the compctl to the commands given */ + if (*argv) { + if(cclist & COMP_SPECIAL) + /* Ideally we'd handle this properly, setting both the * + * special and normal completions. For the moment, * + * this is better than silently failing. */ + zwarnnam(name, "extraneous commands ignored", NULL, 0); + else + compctl_process_cc(argv, cc); + } + + return ret; +} + +static struct builtin bintab[] = { + BUILTIN("compctl", 0, bin_compctl, 0, -1, 0, NULL, NULL), +}; + +/**/ +int +boot_compctl(Module m) +{ + if(!addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab))) + return 1; + compctltab->printnode = printcompctlp; + return 0; +} + +#ifdef MODULE + +/**/ +int +cleanup_compctl(Module m) +{ + compctltab->printnode = NULL; + deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + return 0; +} +#endif diff --git a/Src/Zle/compctl.mdd b/Src/Zle/compctl.mdd new file mode 100644 index 000000000..c83ecda29 --- /dev/null +++ b/Src/Zle/compctl.mdd @@ -0,0 +1,5 @@ +moddeps="comp1" + +autobins="compctl" + +objects="compctl.o" diff --git a/Src/Zle/deltochar.c b/Src/Zle/deltochar.c new file mode 100644 index 000000000..87f8593b8 --- /dev/null +++ b/Src/Zle/deltochar.c @@ -0,0 +1,91 @@ +/* + * deltochar.c - ZLE module implementing Emacs' zap-to-char + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1996-1997 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. + * + */ + +#include "deltochar.mdh" +#include "deltochar.pro" + +static Widget w_deletetochar; + +/**/ +static void +deltochar(void) +{ + int c = getkey(0), dest = cs, ok = 0, n = zmult; + + if (n > 0) { + while (n-- && dest != ll) { + while (dest != ll && line[dest] != c) + dest++; + if (dest != ll) { + dest++; + if (!n) { + foredel(dest - cs); + ok++; + } + } + } + } else { + /* ignore character cursor is on when scanning backwards */ + if (dest) + dest--; + while (n++ && dest != 0) { + while (dest != 0 && line[dest] != c) + dest--; + if (line[dest] == c && !n) { + backdel(cs - dest); + ok++; + } + } + } + if (!ok) + feep(); +} + +/**/ +int +boot_deltochar(Module m) +{ + w_deletetochar = addzlefunction("delete-to-char", deltochar, ZLE_KEEPSUFFIX); + if (w_deletetochar) + return 0; + zwarnnam(m->nam, "name clash when adding ZLE function `delete-to-char'", + NULL, 0); + return -1; +} + +#ifdef MODULE + +/**/ +int +cleanup_deltochar(Module m) +{ + deletezlefunction(w_deletetochar); + return 0; +} +#endif diff --git a/Src/Zle/deltochar.mdd b/Src/Zle/deltochar.mdd new file mode 100644 index 000000000..4d1f89d1c --- /dev/null +++ b/Src/Zle/deltochar.mdd @@ -0,0 +1,3 @@ +moddeps="zle" + +objects="deltochar.o" diff --git a/Src/Zle/iwidgets.list b/Src/Zle/iwidgets.list new file mode 100644 index 000000000..01862160e --- /dev/null +++ b/Src/Zle/iwidgets.list @@ -0,0 +1,172 @@ +# +# intwidgets.list - list of internally implemented ZLE widgets +# +# Each line has the form: +# +# "canonical-name" , functionname , ZLE_FLAGS +# +# `#' starts a comment. Blank lines are ignored. +# + +"accept-and-hold", acceptandhold, 0 +"accept-and-infer-next-history", acceptandinfernexthistory, 0 +"accept-and-menu-complete", acceptandmenucomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"accept-line", acceptline, 0 +"accept-line-and-down-history", acceptlineanddownhistory, 0 +"backward-char", backwardchar, 0 +"backward-delete-char", backwarddeletechar, ZLE_KEEPSUFFIX +"backward-delete-word", backwarddeleteword, ZLE_KEEPSUFFIX +"backward-kill-line", backwardkillline, ZLE_KILL | ZLE_KEEPSUFFIX +"backward-kill-word", backwardkillword, ZLE_KILL | ZLE_KEEPSUFFIX +"backward-word", backwardword, 0 +"beginning-of-buffer-or-history", beginningofbufferorhistory, 0 +"beginning-of-history", beginningofhistory, 0 +"beginning-of-line", beginningofline, 0 +"beginning-of-line-hist", beginningoflinehist, 0 +"capitalize-word", capitalizeword, 0 +"clear-screen", clearscreen, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"complete-word", completeword, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"copy-prev-word", copyprevword, 0 +"copy-region-as-kill", copyregionaskill, ZLE_KEEPSUFFIX +"delete-char", deletechar, ZLE_KEEPSUFFIX +"delete-char-or-list", deletecharorlist, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"delete-word", deleteword, ZLE_KEEPSUFFIX +"describe-key-briefly", describekeybriefly, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"digit-argument", digitargument, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"down-case-word", downcaseword, 0 +"down-history", downhistory, 0 +"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-history", endofhistory, 0 +"end-of-line", endofline, 0 +"end-of-line-hist", endoflinehist, 0 +"exchange-point-and-mark", exchangepointandmark, 0 +"execute-last-named-cmd", NULL, 0 +"execute-named-cmd", NULL, 0 +"expand-cmd-path", expandcmdpath, 0 +"expand-history", expandhistory, 0 +"expand-or-complete", expandorcomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"expand-or-complete-prefix", expandorcompleteprefix, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"expand-word", expandword, 0 +"forward-char", forwardchar, 0 +"forward-word", forwardword, 0 +"get-line", getline, 0 +"gosmacs-transpose-chars", gosmacstransposechars, 0 +"history-beginning-search-backward", historybeginningsearchbackward, 0 +"history-beginning-search-forward", historybeginningsearchforward, 0 +"history-incremental-search-backward", historyincrementalsearchbackward, 0 +"history-incremental-search-forward", historyincrementalsearchforward, 0 +"history-search-backward", historysearchbackward, 0 +"history-search-forward", historysearchforward, 0 +"infer-next-history", infernexthistory, 0 +"insert-last-word", insertlastword, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"kill-buffer", killbuffer, ZLE_KILL | ZLE_KEEPSUFFIX +"kill-line", killline, ZLE_KILL | ZLE_KEEPSUFFIX +"kill-region", killregion, ZLE_KILL | ZLE_KEEPSUFFIX +"kill-whole-line", killwholeline, ZLE_KILL | ZLE_KEEPSUFFIX +"kill-word", killword, ZLE_KILL | ZLE_KEEPSUFFIX +"list-choices", listchoices, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"list-expand", listexpand, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"magic-space", magicspace, 0 +"menu-complete", menucomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"menu-expand-or-complete", menuexpandorcomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"neg-argument", negargument, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"overwrite-mode", overwritemode, 0 +"pound-insert", poundinsert, 0 +"push-input", pushinput, 0 +"push-line", pushline, 0 +"push-line-or-edit", pushlineoredit, 0 +"quoted-insert", quotedinsert, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"quote-line", quoteline, 0 +"quote-region", quoteregion, 0 +"redisplay", redisplay, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"redo", redo, 0 +"reverse-menu-complete", reversemenucomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"run-help", processcmd, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"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 +"spell-word", spellword, 0 +"transpose-chars", transposechars, 0 +"transpose-words", transposewords, 0 +"undefined-key", undefinedkey, 0 +"undo", undo, 0 +"universal-argument", universalargument, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"up-case-word", upcaseword, 0 +"up-history", uphistory, 0 +"up-line-or-history", uplineorhistory, ZLE_LINEMOVE | ZLE_LASTCOL +"up-line-or-search", uplineorsearch, ZLE_LINEMOVE | ZLE_LASTCOL +"vi-add-eol", viaddeol, 0 +"vi-add-next", viaddnext, 0 +"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-word", vibackwardword, 0 +"vi-beginning-of-line", vibeginningofline, 0 +"vi-caps-lock-panic", vicapslockpanic, 0 +"vi-change", vichange, 0 +"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-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-find-next-char", vifindnextchar, 0 +"vi-find-next-char-skip", vifindnextcharskip, 0 +"vi-find-prev-char", vifindprevchar, 0 +"vi-find-prev-char-skip", vifindprevcharskip, 0 +"vi-first-non-blank", vifirstnonblank, 0 +"vi-forward-blank-word", viforwardblankword, 0 +"vi-forward-blank-word-end", viforwardblankwordend, 0 +"vi-forward-char", viforwardchar, 0 +"vi-forward-word", viforwardword, 0 +"vi-forward-word-end", viforwardwordend, 0 +"vi-goto-column", vigotocolumn, 0 +"vi-goto-mark", vigotomark, 0 +"vi-goto-mark-line", vigotomarkline, 0 +"vi-history-search-backward", vihistorysearchbackward, 0 +"vi-history-search-forward", vihistorysearchforward, 0 +"vi-indent", viindent, 0 +"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-match-bracket", vimatchbracket, 0 +"vi-open-line-above", viopenlineabove, 0 +"vi-open-line-below", viopenlinebelow, 0 +"vi-oper-swap-case", vioperswapcase, 0 +"vi-pound-insert", vipoundinsert, 0 +"vi-put-after", viputafter, ZLE_YANK +"vi-put-before", viputbefore, ZLE_YANK +"vi-quoted-insert", viquotedinsert, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"vi-repeat-change", virepeatchange, 0 +"vi-repeat-find", virepeatfind, 0 +"vi-repeat-search", virepeatsearch, 0 +"vi-replace", vireplace, 0 +"vi-replace-chars", vireplacechars, 0 +"vi-rev-repeat-find", virevrepeatfind, 0 +"vi-rev-repeat-search", virevrepeatsearch, 0 +"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-undo-change", viundochange, 0 +"vi-unindent", viunindent, 0 +"vi-up-line-or-history", viuplineorhistory, ZLE_LINEMOVE +"vi-yank", viyank, 0 +"vi-yank-eol", viyankeol, 0 +"vi-yank-whole-line", viyankwholeline, 0 +"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 +"yank-pop", yankpop, ZLE_YANK diff --git a/Src/Zle/zle.h b/Src/Zle/zle.h new file mode 100644 index 000000000..faf6cf878 --- /dev/null +++ b/Src/Zle/zle.h @@ -0,0 +1,137 @@ +/* + * zle.h - header file for line editor + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#undef trashzle +#undef zleread +#undef spaceinline +#undef gotword +#undef refresh + +typedef struct widget *Widget; +typedef struct thingy *Thingy; + +/* widgets (ZLE functions) */ + +typedef void (*ZleIntFunc) _((void)); + +struct widget { + int flags; /* flags (see below) */ + Thingy first; /* `first' thingy that names this widget */ + union { + ZleIntFunc fn; /* pointer to internally implemented widget */ + char *fnnam; /* name of the shell function for user-defined widget */ + } u; +}; + +#define WIDGET_INT (1<<0) /* widget is internally implemented */ +#define ZLE_MENUCMP (1<<1) /* 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<<9) /* DON'T remove added suffix */ + +/* thingies */ + +struct thingy { + HashNode next; /* next node in the hash chain */ + char *nam; /* name of the thingy */ + int flags; /* TH_* flags (see below) */ + int rc; /* reference count */ + Widget widget; /* widget named by this thingy */ + Thingy samew; /* `next' thingy (circularly) naming the same widget */ +}; + +/* DISABLED is (1<<0) */ +#define TH_IMMORTAL (1<<1) /* can't refer to a different widget */ + +/* command modifier prefixes */ + +struct modifier { + int flags; /* MOD_* flags (see below) */ + int mult; /* repeat count */ + int tmult; /* repeat count actually being edited */ + int vibuf; /* vi cut buffer */ +}; + +#define MOD_MULT (1<<0) /* a repeat count has been selected */ +#define MOD_TMULT (1<<1) /* a repeat count is being entered */ +#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 */ + +/* current modifier status */ + +#define zmult (zmod.mult) + +/* undo system */ + +struct change { + struct change *prev, *next; /* adjacent changes */ + int flags; /* see below */ + int hist; /* history line being changed */ + int off; /* offset of the text changes */ + char *del; /* characters to delete (metafied) */ + char *ins; /* characters to insert (metafied) */ +}; + +#define CH_NEXT (1<<0) /* next structure is also part of this change */ +#define CH_PREV (1<<1) /* previous structure is also part of this change */ + +/* known thingies */ + +#define Th(X) (&thingies[X]) + +/* opaque keymap type */ + +typedef struct keymap *Keymap; + +typedef void (*KeyScanFunc) _((char *, Thingy, char *, void *)); + +#define invicmdmode() (!strcmp(curkeymapname, "vicmd")) + +/* Standard type of suffix removal. */ + +#define removesuffix() iremovesuffix(256) + +/* Cut/kill buffer type. The buffer itself is purely binary data, * + * not NUL-terminated. len is a length count. flags uses the * + * CUTBUFFER_* constants defined below. */ + +struct cutbuffer { + char *buf; + size_t len; + char flags; +}; + +typedef struct cutbuffer *Cutbuffer; + +#define CUTBUFFER_LINE 1 /* for vi: buffer contains whole lines of data */ + +#define KRINGCT 8 /* number of buffers in the kill ring */ diff --git a/Src/Zle/zle.mdd b/Src/Zle/zle.mdd new file mode 100644 index 000000000..29f39d363 --- /dev/null +++ b/Src/Zle/zle.mdd @@ -0,0 +1,70 @@ +moddeps="comp1" + +autobins="bindkey vared 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" + +headers="zle.h zle_things.h" + +:<<\Make +zle_things.h: thingies.list zle_things.sed + ( \ + echo '/** zle_things.h **/'; \ + echo '/** indices of and pointers to known thingies **/'; \ + echo; \ + echo 'enum {'; \ + sed -n -f $(sdir)/zle_things.sed < thingies.list; \ + echo ' ZLE_BUILTIN_THINGY_COUNT'; \ + echo '};'; \ + ) > $@ + +zle_widget.h: widgets.list zle_widget.sed + ( \ + echo '/** zle_widget.h **/'; \ + echo '/** indices of and pointers to internal widgets **/'; \ + echo; \ + echo 'enum {'; \ + sed -n -f $(sdir)/zle_widget.sed < widgets.list; \ + echo ' ZLE_BUILTIN_WIDGET_COUNT'; \ + echo '};'; \ + ) > $@ + +thingies.list: iwidgets.list + ( \ + echo '/** thingies.list **/'; \ + echo '/** thingy structures for the known thingies **/'; \ + echo; \ + echo '/* format: T("name", TH_FLAGS, w_widget, t_nextthingy) */'; \ + echo; \ + sed -e 's/#.*//; /^$$/d; s/" *,.*/"/' \ + -e 's/^"/T("/; s/$$/, 0,/; h' \ + -e 's/-//g; s/^.*"\(.*\)".*/w_\1, t_D\1)/' \ + -e 'H; g; s/\n/ /' \ + < $(sdir)/iwidgets.list; \ + sed -e 's/#.*//; /^$$/d; s/" *,.*/"/' \ + -e 's/^"/T("./; s/$$/, TH_IMMORTAL,/; h' \ + -e 's/-//g; s/^.*"\.\(.*\)".*/w_\1, t_\1)/' \ + -e 'H; g; s/\n/ /' \ + < $(sdir)/iwidgets.list; \ + ) > $@ + +widgets.list: iwidgets.list + ( \ + echo '/** widgets.list **/'; \ + echo '/** widget structures for the internal widgets **/'; \ + echo; \ + echo '/* format: W(ZLE_FLAGS, t_firstname, functionname) */'; \ + echo; \ + sed -e 's/#.*//; /^$$/d; s/-//g' \ + -e 's/^"\(.*\)" *, *\([^ ]*\) *, *\(.*\)/W(\3, t_\1, \2)/' \ + < $(sdir)/iwidgets.list; \ + ) > $@ + +zle_bindings.o zle_bindings..o: zle_widget.h widgets.list thingies.list + +clean-here: clean.zle +clean.zle: + rm -f zle_things.h zle_widget.h widgets.list thingies.list +Make diff --git a/Src/Zle/zle_bindings.c b/Src/Zle/zle_bindings.c new file mode 100644 index 000000000..40e555ad1 --- /dev/null +++ b/Src/Zle/zle_bindings.c @@ -0,0 +1,421 @@ +/* + * zle_bindings.c - commands and keymaps + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "zle.mdh" +#include "zle_widget.h" + +#include "zle_bindings.pro" + +/* + * widgets is the table of internally implemented widgets. This + * table is not used directly, but each widget in it is referenced + * by address from within the table of thingies (below). The only + * complication here is that not all systems support union + * initialisation. + */ + +static +#ifdef HAVE_UNION_INIT +# define BR(X) {X} +struct widget +#else /* !HAVE_UNION_INIT */ +# define BR(X) X +struct intwidget { + int flags; + Thingy first; + ZleIntFunc fn; +} +#endif /* !HAVE_UNION_INIT */ +widgets[] = { +#define W(zle_flags, t_firstname, functionname) \ + { WIDGET_INT | zle_flags, t_firstname, BR(functionname) }, +#include "widgets.list" +#undef W +}; + +/* + * thingies is the table of `known thingies', that exist on startup. + * Some bits of ZLE rely on some of these thingies always being the + * ones in this table, rather than doing a name lookup and accepting + * any semantically identical thingy. The initial reference count of + * these thingies is 2: 1 for the widget they name, and 1 extra to + * make sure they never get deleted. + */ + +/**/ +struct thingy thingies[] = { +#define T(name, th_flags, w_idget, t_next) \ + { NULL, name, th_flags, 2, w_idget, t_next }, +#include "thingies.list" +#undef T + { NULL, NULL, 0, 0, NULL, NULL } +}; + +/* + * Default key binding tables: + * + * In these tables, each element is bound to a single thingy, the index + * of which in the above table is stored here. + */ + +/**/ +int emacsbind[32] = { + /* ^@ */ z_setmarkcommand, + /* ^A */ z_beginningofline, + /* ^B */ z_backwardchar, + /* ^C */ z_undefinedkey, + /* ^D */ z_deletecharorlist, + /* ^E */ z_endofline, + /* ^F */ z_forwardchar, + /* ^G */ z_sendbreak, + /* ^H */ z_backwarddeletechar, + /* ^I */ z_expandorcomplete, + /* ^J */ z_acceptline, + /* ^K */ z_killline, + /* ^L */ z_clearscreen, + /* ^M */ z_acceptline, + /* ^N */ z_downlineorhistory, + /* ^O */ z_acceptlineanddownhistory, + /* ^P */ z_uplineorhistory, + /* ^Q */ z_pushline, + /* ^R */ z_historyincrementalsearchbackward, + /* ^S */ z_historyincrementalsearchforward, + /* ^T */ z_transposechars, + /* ^U */ z_killwholeline, + /* ^V */ z_quotedinsert, + /* ^W */ z_backwardkillword, + /* ^X */ z_undefinedkey, + /* ^Y */ z_yank, + /* ^Z */ z_undefinedkey, + /* ^[ */ z_undefinedkey, + /* ^\ */ z_undefinedkey, + /* ^] */ z_undefinedkey, + /* ^^ */ z_undefinedkey, + /* ^_ */ z_undo, +}; + +/**/ +int metabind[128] = { + /* M-^@ */ z_undefinedkey, + /* M-^A */ z_undefinedkey, + /* M-^B */ z_undefinedkey, + /* M-^C */ z_undefinedkey, + /* M-^D */ z_listchoices, + /* M-^E */ z_undefinedkey, + /* M-^F */ z_undefinedkey, + /* M-^G */ z_sendbreak, + /* M-^H */ z_backwardkillword, + /* M-^I */ z_selfinsertunmeta, + /* M-^J */ z_selfinsertunmeta, + /* M-^K */ z_undefinedkey, + /* M-^L */ z_clearscreen, + /* M-^M */ z_selfinsertunmeta, + /* M-^N */ z_undefinedkey, + /* M-^O */ z_undefinedkey, + /* M-^P */ z_undefinedkey, + /* M-^Q */ z_undefinedkey, + /* M-^R */ z_undefinedkey, + /* M-^S */ z_undefinedkey, + /* M-^T */ z_undefinedkey, + /* M-^U */ z_undefinedkey, + /* M-^V */ z_undefinedkey, + /* M-^W */ z_undefinedkey, + /* M-^X */ z_undefinedkey, + /* M-^Y */ z_undefinedkey, + /* M-^Z */ z_undefinedkey, + /* M-^[ */ z_undefinedkey, + /* M-^\ */ z_undefinedkey, + /* M-^] */ z_undefinedkey, + /* M-^^ */ z_undefinedkey, + /* M-^_ */ z_copyprevword, + /* M- */ z_expandhistory, + /* M-! */ z_expandhistory, + /* M-" */ z_quoteregion, + /* M-# */ z_undefinedkey, + /* M-$ */ z_spellword, + /* M-% */ z_undefinedkey, + /* M-& */ z_undefinedkey, + /* M-' */ z_quoteline, + /* M-( */ z_undefinedkey, + /* M-) */ z_undefinedkey, + /* M-* */ z_undefinedkey, + /* M-+ */ z_undefinedkey, + /* M-, */ z_undefinedkey, + /* M-- */ z_negargument, + /* M-. */ z_insertlastword, + /* M-/ */ z_undefinedkey, + /* M-0 */ z_digitargument, + /* M-1 */ z_digitargument, + /* M-2 */ z_digitargument, + /* M-3 */ z_digitargument, + /* M-4 */ z_digitargument, + /* M-5 */ z_digitargument, + /* M-6 */ z_digitargument, + /* M-7 */ z_digitargument, + /* M-8 */ z_digitargument, + /* M-9 */ z_digitargument, + /* M-: */ z_undefinedkey, + /* M-; */ z_undefinedkey, + /* M-< */ z_beginningofbufferorhistory, + /* M-= */ z_undefinedkey, + /* M-> */ z_endofbufferorhistory, + /* M-? */ z_whichcommand, + /* M-@ */ z_undefinedkey, + /* M-A */ z_acceptandhold, + /* M-B */ z_backwardword, + /* M-C */ z_capitalizeword, + /* M-D */ z_killword, + /* M-E */ z_undefinedkey, + /* M-F */ z_forwardword, + /* M-G */ z_getline, + /* M-H */ z_runhelp, + /* M-I */ z_undefinedkey, + /* M-J */ z_undefinedkey, + /* M-K */ z_undefinedkey, + /* M-L */ z_downcaseword, + /* M-M */ z_undefinedkey, + /* M-N */ z_historybeginningsearchforward, + /* M-O */ z_undefinedkey, + /* M-P */ z_historybeginningsearchbackward, + /* M-Q */ z_pushline, + /* M-R */ z_undefinedkey, + /* M-S */ z_spellword, + /* M-T */ z_transposewords, + /* M-U */ z_upcaseword, + /* M-V */ z_undefinedkey, + /* M-W */ z_copyregionaskill, + /* M-X */ z_undefinedkey, + /* M-Y */ z_undefinedkey, + /* M-Z */ z_undefinedkey, + /* M-[ */ z_undefinedkey, + /* M-\ */ z_undefinedkey, + /* M-] */ z_undefinedkey, + /* M-^ */ z_undefinedkey, + /* M-_ */ z_insertlastword, + /* M-` */ z_undefinedkey, + /* M-a */ z_acceptandhold, + /* M-b */ z_backwardword, + /* M-c */ z_capitalizeword, + /* M-d */ z_killword, + /* M-e */ z_undefinedkey, + /* M-f */ z_forwardword, + /* M-g */ z_getline, + /* M-h */ z_runhelp, + /* M-i */ z_undefinedkey, + /* M-j */ z_undefinedkey, + /* M-k */ z_undefinedkey, + /* M-l */ z_downcaseword, + /* M-m */ z_undefinedkey, + /* M-n */ z_historybeginningsearchforward, + /* M-o */ z_undefinedkey, + /* M-p */ z_historybeginningsearchbackward, + /* M-q */ z_pushline, + /* M-r */ z_undefinedkey, + /* M-s */ z_spellword, + /* M-t */ z_transposewords, + /* M-u */ z_upcaseword, + /* M-v */ z_undefinedkey, + /* M-w */ z_copyregionaskill, + /* M-x */ z_executenamedcmd, + /* M-y */ z_yankpop, + /* M-z */ z_executelastnamedcmd, + /* M-{ */ z_undefinedkey, + /* M-| */ z_vigotocolumn, + /* M-} */ z_undefinedkey, + /* M-~ */ z_undefinedkey, + /* M-^? */ z_backwardkillword, +}; + +/**/ +int viinsbind[32] = { + /* ^@ */ z_undefinedkey, + /* ^A */ z_selfinsert, + /* ^B */ z_selfinsert, + /* ^C */ z_selfinsert, + /* ^D */ z_listchoices, + /* ^E */ z_selfinsert, + /* ^F */ z_selfinsert, + /* ^G */ z_listexpand, + /* ^H */ z_vibackwarddeletechar, + /* ^I */ z_expandorcomplete, + /* ^J */ z_acceptline, + /* ^K */ z_selfinsert, + /* ^L */ z_clearscreen, + /* ^M */ z_acceptline, + /* ^N */ z_selfinsert, + /* ^O */ z_selfinsert, + /* ^P */ z_selfinsert, + /* ^Q */ z_viquotedinsert, + /* ^R */ z_redisplay, + /* ^S */ z_selfinsert, + /* ^T */ z_selfinsert, + /* ^U */ z_vikillline, + /* ^V */ z_viquotedinsert, + /* ^W */ z_vibackwardkillword, + /* ^X */ z_selfinsert, + /* ^Y */ z_selfinsert, + /* ^Z */ z_selfinsert, + /* ^[ */ z_vicmdmode, + /* ^\ */ z_selfinsert, + /* ^] */ z_selfinsert, + /* ^^ */ z_selfinsert, + /* ^_ */ z_selfinsert, +}; + +/**/ +int vicmdbind[128] = { + /* ^@ */ z_undefinedkey, + /* ^A */ z_undefinedkey, + /* ^B */ z_undefinedkey, + /* ^C */ z_undefinedkey, + /* ^D */ z_listchoices, + /* ^E */ z_undefinedkey, + /* ^F */ z_undefinedkey, + /* ^G */ z_listexpand, + /* ^H */ z_vibackwardchar, + /* ^I */ z_undefinedkey, + /* ^J */ z_acceptline, + /* ^K */ z_undefinedkey, + /* ^L */ z_clearscreen, + /* ^M */ z_acceptline, + /* ^N */ z_downhistory, + /* ^O */ z_undefinedkey, + /* ^P */ z_uphistory, + /* ^Q */ z_undefinedkey, + /* ^R */ z_redisplay, + /* ^S */ z_undefinedkey, + /* ^T */ z_undefinedkey, + /* ^U */ z_undefinedkey, + /* ^V */ z_undefinedkey, + /* ^W */ z_undefinedkey, + /* ^X */ z_undefinedkey, + /* ^Y */ z_undefinedkey, + /* ^Z */ z_undefinedkey, + /* ^[ */ z_undefinedkey, + /* ^\ */ z_undefinedkey, + /* ^] */ z_undefinedkey, + /* ^^ */ z_undefinedkey, + /* ^_ */ z_undefinedkey, + /* */ z_viforwardchar, + /* ! */ z_undefinedkey, + /* " */ z_visetbuffer, + /* # */ z_poundinsert, + /* $ */ z_viendofline, + /* % */ z_vimatchbracket, + /* & */ z_undefinedkey, + /* ' */ z_vigotomarkline, + /* ( */ z_undefinedkey, + /* ) */ z_undefinedkey, + /* * */ z_undefinedkey, + /* + */ z_vidownlineorhistory, + /* , */ z_virevrepeatfind, + /* - */ z_viuplineorhistory, + /* . */ z_virepeatchange, + /* / */ z_vihistorysearchbackward, + /* 0 */ z_vidigitorbeginningofline, + /* 1 */ z_digitargument, + /* 2 */ z_digitargument, + /* 3 */ z_digitargument, + /* 4 */ z_digitargument, + /* 5 */ z_digitargument, + /* 6 */ z_digitargument, + /* 7 */ z_digitargument, + /* 8 */ z_digitargument, + /* 9 */ z_digitargument, + /* : */ z_undefinedkey, + /* ; */ z_virepeatfind, + /* < */ z_viunindent, + /* = */ z_listchoices, + /* > */ z_viindent, + /* ? */ z_vihistorysearchforward, + /* @ */ z_undefinedkey, + /* A */ z_viaddeol, + /* B */ z_vibackwardblankword, + /* C */ z_vichangeeol, + /* D */ z_vikilleol, + /* E */ z_viforwardblankwordend, + /* F */ z_vifindprevchar, + /* G */ z_vifetchhistory, + /* H */ z_undefinedkey, + /* I */ z_viinsertbol, + /* J */ z_vijoin, + /* K */ z_undefinedkey, + /* L */ z_undefinedkey, + /* M */ z_undefinedkey, + /* N */ z_virevrepeatsearch, + /* O */ z_viopenlineabove, + /* P */ z_viputbefore, + /* Q */ z_undefinedkey, + /* R */ z_vireplace, + /* S */ z_vichangewholeline, + /* T */ z_vifindprevcharskip, + /* U */ z_undefinedkey, + /* V */ z_undefinedkey, + /* W */ z_viforwardblankword, + /* X */ z_vibackwarddeletechar, + /* Y */ z_viyankwholeline, + /* Z */ z_undefinedkey, + /* [ */ z_undefinedkey, + /* \ */ z_undefinedkey, + /* ] */ z_undefinedkey, + /* ^ */ z_vifirstnonblank, + /* _ */ z_undefinedkey, + /* ` */ z_vigotomark, + /* a */ z_viaddnext, + /* b */ z_vibackwardword, + /* c */ z_vichange, + /* d */ z_videlete, + /* e */ z_viforwardwordend, + /* f */ z_vifindnextchar, + /* g */ z_undefinedkey, + /* h */ z_vibackwardchar, + /* i */ z_viinsert, + /* j */ z_downlineorhistory, + /* k */ z_uplineorhistory, + /* l */ z_viforwardchar, + /* m */ z_visetmark, + /* n */ z_virepeatsearch, + /* o */ z_viopenlinebelow, + /* p */ z_viputafter, + /* q */ z_undefinedkey, + /* r */ z_vireplacechars, + /* s */ z_visubstitute, + /* t */ z_vifindnextcharskip, + /* u */ z_viundochange, + /* v */ z_undefinedkey, + /* w */ z_viforwardword, + /* x */ z_videletechar, + /* y */ z_viyank, + /* z */ z_undefinedkey, + /* { */ z_undefinedkey, + /* | */ z_vigotocolumn, + /* } */ z_undefinedkey, + /* ~ */ z_viswapcase, + /* ^? */ z_vibackwardchar, +}; diff --git a/Src/Zle/zle_hist.c b/Src/Zle/zle_hist.c new file mode 100644 index 000000000..76e421c1c --- /dev/null +++ b/Src/Zle/zle_hist.c @@ -0,0 +1,1139 @@ +/* + * zle_hist.c - history editing + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "zle.mdh" +#include "zle_hist.pro" + +/* Are references to earlier history lines permitted? == 0 if * + * editing or reading a standalone line, such as in vared or select. */ + +/**/ +int histallowed; + +/* Column position of vi ideal cursor. -1 if it is unknown -- most * + * movements and changes do this. */ + +/**/ +int lastcol; + +/* current history line number */ + +/**/ +int histline; + +/* the last line in the history (the current one), metafied */ + +/**/ +char *curhistline; + +/**/ +void +remember_edits(void) +{ + if (histline == curhist) { + zsfree(curhistline); + curhistline = metafy((char *) line, ll, META_DUP); + } + else { + Histent ent = gethistent(histline); + + if (metadiffer(ent->zle_text ? ent->zle_text : ent->text, + (char *) line, ll)) { + zsfree(ent->zle_text); + ent->zle_text = metafy((char *) line, ll, META_DUP); + } + } +} + +/**/ +void +forget_edits(void) +{ + int i; + + for (i = 0; i < histentct; i++) { + zsfree(histentarr[i].zle_text); + histentarr[i].zle_text = NULL; + } +} + +/**/ +void +uphistory(void) +{ + if (zmult < 0) { + zmult = -zmult; + downhistory(); + zmult = -zmult; + } else if(!zle_goto_hist(histline - zmult) && isset(HISTBEEP)) + feep(); +} + +/**/ +int +upline(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -zmult; + n = downline(); + zmult = -zmult; + return n; + } + if (lastcol == -1) + lastcol = cs - findbol(); + cs = findbol(); + while (n) { + if (!cs) + break; + cs--; + cs = findbol(); + n--; + } + if (!n) { + int x = findeol(); + + if ((cs += lastcol) >= x) { + cs = x; + if (cs > findbol() && invicmdmode()) + cs--; + } + } + return n; +} + +/**/ +void +uplineorhistory(void) +{ + int ocs = cs; + int n = upline(); + if (n) { + int m = zmult; + + cs = ocs; + if (virangeflag || !histallowed) { + feep(); + return; + } + zmult = n; + uphistory(); + zmult = m; + } +} + +/**/ +void +viuplineorhistory(void) +{ + int col = lastcol; + uplineorhistory(); + lastcol = col; + vifirstnonblank(); +} + + +/**/ +void +uplineorsearch(void) +{ + int ocs = cs; + int n = upline(); + if (n) { + int m = zmult; + + cs = ocs; + if (virangeflag || !histallowed) { + feep(); + return; + } + zmult = n; + historysearchbackward(); + zmult = m; + } +} + +/**/ +int +downline(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -zmult; + n = upline(); + zmult = -zmult; + return n; + } + if (lastcol == -1) + lastcol = cs - findbol(); + while (n) { + int x = findeol(); + + if (x == ll) + break; + cs = x + 1; + n--; + } + if (!n) { + int x = findeol(); + + if ((cs += lastcol) >= x) { + cs = x; + if (cs > findbol() && invicmdmode()) + cs--; + } + } + return n; +} + +/**/ +void +downlineorhistory(void) +{ + int ocs = cs; + int n = downline(); + if (n) { + int m = zmult; + + cs = ocs; + if (virangeflag || !histallowed) { + feep(); + return; + } + zmult = n; + downhistory(); + zmult = m; + } +} + +/**/ +void +vidownlineorhistory(void) +{ + int col = lastcol; + downlineorhistory(); + lastcol = col; + vifirstnonblank(); +} + +/**/ +void +downlineorsearch(void) +{ + int ocs = cs; + int n = downline(); + if (n) { + int m = zmult; + + cs = ocs; + if (virangeflag || !histallowed) { + feep(); + return; + } + zmult = n; + historysearchforward(); + zmult = m; + } +} + +/**/ +void +acceptlineanddownhistory(void) +{ + char *s; + + if (!(s = zle_get_event(histline + 1))) { + feep(); + return; + } + pushnode(bufstack, ztrdup(s)); + done = 1; + stackhist = histline + 1; +} + +/**/ +void +downhistory(void) +{ + if (zmult < 0) { + zmult = -zmult; + uphistory(); + zmult = -zmult; + } else if(!zle_goto_hist(histline + zmult) && isset(HISTBEEP)) + feep(); +} + +/**/ +void +historysearchbackward(void) +{ + int histpos, histmpos, hl = histline; + int n = zmult; + char *s; + + if (!n) + return; + if (n < 0) { + zmult = -n; + historysearchforward(); + zmult = n; + return; + } + for (histpos = histmpos = 0; histpos < ll && !iblank(line[histpos]); + histpos++, histmpos++) + if(imeta(line[histpos])) + histmpos++; + for (;;) { + hl--; + if (!(s = zle_get_event(hl))) { + feep(); + return; + } + if (metadiffer(s, (char *) line, histpos) < 0 && + iblank(s[histmpos] == Meta ? s[histmpos+1]^32 : s[histmpos]) && + metadiffer(s, (char *) line, ll) && !--n) + break; + } + zle_goto_hist(hl); +} + +/**/ +void +historysearchforward(void) +{ + int histpos, histmpos, hl = histline; + int n = zmult; + char *s; + + if (!n) + return; + if (n < 0) { + zmult = -n; + historysearchbackward(); + zmult = n; + return; + } + for (histpos = histmpos = 0; histpos < ll && !iblank(line[histpos]); + histpos++, histmpos++) + if(imeta(line[histpos])) + histmpos++; + for (;;) { + hl++; + if (!(s = zle_get_event(hl))) { + feep(); + return; + } + if (metadiffer(s, (char *) line, histpos) < (histline == curhist) && + (!s[histmpos] || + iblank(s[histmpos] == Meta ? s[histmpos+1]^32 : s[histmpos])) && + metadiffer(s, (char *) line, ll) && !--n) + break; + } + zle_goto_hist(hl); +} + +/**/ +void +beginningofbufferorhistory(void) +{ + if (findbol()) + cs = 0; + else + beginningofhistory(); +} + +/**/ +void +beginningofhistory(void) +{ + if (!zle_goto_hist(firsthist()) && isset(HISTBEEP)) + feep(); +} + +/**/ +void +endofbufferorhistory(void) +{ + if (findeol() != ll) + cs = ll; + else + endofhistory(); +} + +/**/ +void +endofhistory(void) +{ + zle_goto_hist(curhist); +} + +/**/ +void +insertlastword(void) +{ + int n; + char *s, *t; + Histent he; + +/* multiple calls will now search back through the history, pem */ + static char *lastinsert; + static int lasthist, lastpos; + int evhist = curhist - 1, save; + + if (lastinsert) { + int lastlen = ztrlen(lastinsert); + int pos = cs; + + if (lastpos <= pos && + lastlen == pos - lastpos && + memcmp(lastinsert, (char *)&line[lastpos], lastlen) == 0) { + evhist = --lasthist; + cs = lastpos; + foredel(pos - cs); + } + zsfree(lastinsert); + lastinsert = NULL; + } + if (!(he = quietgethist(evhist)) || !he->nwords) { + feep(); + return; + } + if (zmult > 0) { + n = he->nwords - (zmult - 1); + } else { + n = 1 - zmult; + } + if (n < 1 || n > he->nwords) { + feep(); + return; + } + s = he->text + he->words[2*n-2]; + t = he->text + he->words[2*n-1]; + save = *t; + *t = '\0'; /* ignore trailing whitespace */ + + lasthist = evhist; + lastpos = cs; + lastinsert = ztrdup(s); + n = zmult; + zmult = 1; + doinsert(s); + zmult = n; + *t = save; +} + +/**/ +char * +qgetevent(int ev) +{ + return ((ev == curhist) ? curhistline : quietgetevent(ev)); +} + +/**/ +char * +zle_get_event(int ev) +{ + Histent ent; + + if (ev == curhist) + return curhistline; + if (! (ent = quietgethist(ev))) + return NULL; + if (ent->zle_text) + return ent->zle_text; + return ent->text; +} + +/**/ +static int +zle_goto_hist(int ev) +{ + char *t; + + remember_edits(); + if(!(t = zle_get_event(ev))) + return 0; + mkundoent(); + histline = ev; + setline(t); + setlastline(); + return 1; +} + +/**/ +void +pushline(void) +{ + int n = zmult; + + if (n < 0) + return; + pushnode(bufstack, metafy((char *) line, ll, META_DUP)); + while (--n) + pushnode(bufstack, ztrdup("")); + stackcs = cs; + *line = '\0'; + ll = cs = 0; +} + +/**/ +void +pushlineoredit(void) +{ + int ics; + unsigned char *s; + char *hline = hgetline(); + + if (zmult < 0) + return; + if (hline && *hline) { + ics = ztrlen(hline); + sizeline(ics + ll + 1); + for (s = line + ll; --s >= line; *(s + ics) = *s); + for (s = line; *hline; hline++) + *s++ = *hline == Meta ? *++hline ^ 32 : *hline; + ll += ics; + cs += ics; + } + pushline(); + if (!isfirstln) { + errflag = done = 1; + } +} + +/**/ +void +pushinput(void) +{ + int i; + + if (zmult < 0) + return; + zmult += i = !isfirstln; + pushlineoredit(); + zmult -= i; +} + +/**/ +void +getline(void) +{ + char *s = (char *)getlinknode(bufstack); + + if (!s) + feep(); + else { + int cc; + + unmetafy(s, &cc); + spaceinline(cc); + memcpy((char *)line + cs, s, cc); + cs += cc; + free(s); + } +} + +/**/ +void +historyincrementalsearchbackward(void) +{ + doisearch(-1); +} + +/**/ +void +historyincrementalsearchforward(void) +{ + doisearch(1); +} + +static struct isrch_spot { + int hl; /* This spot's histline */ + unsigned short pos; /* The search position in our metafied str */ + unsigned short cs; /* The visible search position to the user */ + unsigned short len; /* The search string's length */ + unsigned short flags; /* This spot's flags */ +#define ISS_FAILING 1 +#define ISS_FORWARD 2 +} *isrch_spots; + +static int max_spot = 0; + +#ifdef MODULE + +/**/ +void +free_isrch_spots(void) +{ + zfree(isrch_spots, max_spot * sizeof(*isrch_spots)); +} + +#endif /* MODULE */ + +/**/ +static void +set_isrch_spot(int num, int hl, int pos, int cs, int len, int dir, int nomatch) +{ + if (num >= max_spot) { + if (!isrch_spots) { + isrch_spots = (struct isrch_spot*) + zalloc((max_spot = 64) * sizeof *isrch_spots); + } else { + isrch_spots = (struct isrch_spot*)realloc((char*)isrch_spots, + (max_spot += 64) * sizeof *isrch_spots); + } + } + + isrch_spots[num].hl = hl; + isrch_spots[num].pos = (unsigned short)pos; + isrch_spots[num].cs = (unsigned short)cs; + isrch_spots[num].len = (unsigned short)len; + isrch_spots[num].flags = (dir > 0? ISS_FORWARD : 0) + + (nomatch? ISS_FAILING : 0); +} + +/**/ +static void +get_isrch_spot(int num, int *hlp, int *posp, int *csp, int *lenp, int *dirp, int *nomatch) +{ + *hlp = isrch_spots[num].hl; + *posp = (int)isrch_spots[num].pos; + *csp = (int)isrch_spots[num].cs; + *lenp = (int)isrch_spots[num].len; + *dirp = (isrch_spots[num].flags & ISS_FORWARD)? 1 : -1; + *nomatch = (isrch_spots[num].flags & ISS_FAILING); +} + +#define ISEARCH_PROMPT "failing XXX-i-search: " +#define NORM_PROMPT_POS 8 +#define FIRST_SEARCH_CHAR (NORM_PROMPT_POS + 14) + +/**/ +static void +doisearch(int dir) +{ + char *s, *ibuf = halloc(80), *sbuf = ibuf + FIRST_SEARCH_CHAR; + int sbptr = 0, top_spot = 0, pos, sibuf = 80; + int nomatch = 0, skip_line = 0, skip_pos = 0; + int odir = dir, sens = zmult == 1 ? 3 : 1; + int hl = histline; + Thingy cmd; + char *okeymap = curkeymapname; + static char *previous_search = NULL; + static int previous_search_len = 0; + + strcpy(ibuf, ISEARCH_PROMPT); + memcpy(ibuf + NORM_PROMPT_POS, (dir == 1) ? "fwd" : "bck", 3); + remember_edits(); + s = zle_get_event(hl); + selectkeymap("main", 1); + pos = metalen(s, cs); + for (;;) { + /* Remember the current values in case search fails (doesn't push). */ + set_isrch_spot(top_spot, hl, pos, cs, sbptr, dir, nomatch); + if (sbptr == 1 && sbuf[0] == '^') { + cs = 0; + nomatch = 0; + statusline = ibuf + NORM_PROMPT_POS; + } else if (sbptr > 0) { + char *last_line = s; + + for (;;) { + char *t; + + if (skip_pos) { + if (dir < 0) { + if (pos == 0) + skip_line = 1; + else + pos -= 1 + (pos != 1 && s[pos-2] == Meta); + } else if (sbuf[0] != '^') { + if (pos >= strlen(s+1)) + skip_line = 1; + else + pos += 1 + (s[pos] == Meta); + } else + skip_line = 1; + skip_pos = 0; + } + if (!skip_line && ((sbuf[0] == '^') ? + (t = metadiffer(s, sbuf + 1, sbptr - 1) < sens ? s : NULL) : + (t = hstrnstr(s, pos, sbuf, sbptr, dir, sens)))) { + zle_goto_hist(hl); + pos = t - s; + cs = ztrsub(t, s) + (dir == 1? sbptr - (sbuf[0]=='^') : 0); + nomatch = 0; + statusline = ibuf + NORM_PROMPT_POS; + break; + } + hl += dir; + if (!(s = zle_get_event(hl))) { + if (sbptr == (int)isrch_spots[top_spot-1].len + && (isrch_spots[top_spot-1].flags & ISS_FAILING)) + top_spot--; + get_isrch_spot(top_spot, &hl, &pos, &cs, &sbptr, + &dir, &nomatch); + if (!nomatch) { + feep(); + nomatch = 1; + } + s = last_line; + skip_line = 0; + statusline = ibuf; + break; + } + pos = dir == 1? 0 : strlen(s); + skip_line = !strcmp(last_line, s); + } + } else { + top_spot = 0; + nomatch = 0; + statusline = ibuf + NORM_PROMPT_POS; + } + sbuf[sbptr] = '_'; + statusll = sbuf - statusline + sbptr + 1; + ref: + refresh(); + if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak)) { + int i; + get_isrch_spot(0, &hl, &pos, &i, &sbptr, &dir, &nomatch); + s = zle_get_event(hl); + zle_goto_hist(hl); + cs = i; + break; + } + if(cmd == Th(z_clearscreen)) { + clearscreen(); + goto ref; + } else if(cmd == Th(z_redisplay)) { + redisplay(); + goto ref; + } else if(cmd == Th(z_vicmdmode)) { + if(selectkeymap(invicmdmode() ? "main" : "vicmd", 0)) + feep(); + goto ref; + } else if(cmd == Th(z_vibackwarddeletechar) || + cmd == Th(z_backwarddeletechar)) { + if (top_spot) + get_isrch_spot(--top_spot, &hl, &pos, &cs, &sbptr, + &dir, &nomatch); + else + feep(); + if (nomatch) { + statusline = ibuf; + skip_pos = 1; + } + s = zle_get_event(hl); + if (nomatch || !sbptr || (sbptr == 1 && sbuf[0] == '^')) { + int i = cs; + zle_goto_hist(hl); + cs = i; + } + memcpy(ibuf + NORM_PROMPT_POS, (dir == 1) ? "fwd" : "bck", 3); + continue; + } else if(cmd == Th(z_acceptandhold)) { + acceptandhold(); + break; + } else if(cmd == Th(z_acceptandinfernexthistory)) { + acceptandinfernexthistory(); + break; + } else if(cmd == Th(z_acceptlineanddownhistory)) { + acceptlineanddownhistory(); + break; + } else if(cmd == Th(z_acceptline)) { + acceptline(); + break; + } else if(cmd == Th(z_historyincrementalsearchbackward)) { + set_isrch_spot(top_spot++, hl, pos, cs, sbptr, dir, nomatch); + if (dir != -1) + dir = -1; + else + skip_pos = 1; + goto rpt; + } else if(cmd == Th(z_historyincrementalsearchforward)) { + set_isrch_spot(top_spot++, hl, pos, cs, sbptr, dir, nomatch); + if (dir != 1) + dir = 1; + else + skip_pos = 1; + goto rpt; + } else if(cmd == Th(z_virevrepeatsearch)) { + set_isrch_spot(top_spot++, hl, pos, cs, sbptr, dir, nomatch); + dir = -odir; + skip_pos = 1; + goto rpt; + } else if(cmd == Th(z_virepeatsearch)) { + set_isrch_spot(top_spot++, hl, pos, cs, sbptr, dir, nomatch); + dir = odir; + skip_pos = 1; + rpt: + if (!sbptr && previous_search_len) { + if (previous_search_len > sibuf - FIRST_SEARCH_CHAR - 2) { + ibuf = hrealloc(ibuf, sibuf, sibuf + previous_search_len); + sbuf = ibuf + FIRST_SEARCH_CHAR; + sibuf += previous_search_len; + } + memcpy(sbuf, previous_search, sbptr = previous_search_len); + } + memcpy(ibuf + NORM_PROMPT_POS, (dir == 1) ? "fwd" : "bck", 3); + continue; + } else if(cmd == Th(z_viquotedinsert) || + cmd == Th(z_quotedinsert)) { + if(cmd == Th(z_viquotedinsert)) { + sbuf[sbptr] = '^'; + refresh(); + } + if ((c = getkey(0)) == EOF) + feep(); + else + goto ins; + } else { + if(cmd == Th(z_selfinsertunmeta)) { + c &= 0x7f; + if(c == '\r') + c = '\n'; + } else if (cmd == Th(z_magicspace)) + c = ' '; + else if (cmd != Th(z_selfinsert)) { + ungetkeycmd(); + if (cmd == Th(z_sendbreak)) + sbptr = 0; + break; + } + ins: + if (sbptr == PATH_MAX) { + feep(); + continue; + } + set_isrch_spot(top_spot++, hl, pos, cs, sbptr, dir, nomatch); + if (sbptr == sibuf - FIRST_SEARCH_CHAR - 2) { + ibuf = hrealloc(ibuf, sibuf, sibuf * 2); + sbuf = ibuf + FIRST_SEARCH_CHAR; + sibuf *= 2; + } + sbuf[sbptr++] = c; + } + handlefeep(); + } + if (sbptr) { + zfree(previous_search, previous_search_len); + previous_search = zalloc(sbptr); + memcpy(previous_search, sbuf, previous_search_len = sbptr); + } + statusline = NULL; + selectkeymap(okeymap, 1); +} + +/**/ +void +acceptandinfernexthistory(void) +{ + int t0; + char *s; + + done = 1; + for (t0 = histline - 2;; t0--) { + if (!(s = qgetevent(t0))) + return; + if (!metadiffer(s, (char *) line, ll)) + break; + } + if (!(s = qgetevent(t0 + 1))) + return; + pushnode(bufstack, ztrdup(s)); + stackhist = t0 + 1; +} + +/**/ +void +infernexthistory(void) +{ + int t0; + char *s; + + for (t0 = histline - 2;; t0--) { + if (!(s = qgetevent(t0))) { + feep(); + return; + } + if (! metadiffer(s, (char *) line, ll)) + break; + } + if (!(s = qgetevent(t0 + 1))) { + feep(); + return; + } + zle_goto_hist(t0 + 1); +} + +/**/ +void +vifetchhistory(void) +{ + if (zmult < 0) + return; + if (histline == curhist) { + if (!(zmod.flags & MOD_MULT)) { + cs = ll; + cs = findbol(); + return; + } + } + if (!zle_goto_hist((zmod.flags & MOD_MULT) ? zmult : curhist) && + isset(HISTBEEP)) + feep(); +} + +/* the last vi search */ + +static char *visrchstr; +static int visrchsense; + +/**/ +static int +getvisrchstr(void) +{ + char *sbuf = halloc(80); + int sptr = 1, ret = 0, ssbuf = 80; + Thingy cmd; + char *okeymap = curkeymapname; + + if (visrchstr) { + zsfree(visrchstr); + visrchstr = NULL; + } + statusline = sbuf; + sbuf[0] = (visrchsense == -1) ? '?' : '/'; + selectkeymap("main", 1); + while (sptr) { + sbuf[sptr] = '_'; + statusll = sptr + 1; + refresh(); + if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak)) { + ret = 0; + break; + } + if(cmd == Th(z_magicspace)) { + c = ' '; + cmd = Th(z_selfinsert); + } + if(cmd == Th(z_redisplay)) { + redisplay(); + } else if(cmd == Th(z_clearscreen)) { + clearscreen(); + } else if(cmd == Th(z_acceptline) || + cmd == Th(z_vicmdmode)) { + sbuf[sptr] = 0; + visrchstr = metafy(sbuf + 1, sptr - 1, META_DUP); + ret = 1; + sptr = 0; + } else if(cmd == Th(z_backwarddeletechar) || + cmd == Th(z_vibackwarddeletechar)) { + sptr--; + } else if(cmd == Th(z_backwardkillword) || + cmd == Th(z_vibackwardkillword)) { + while(sptr != 1 && iblank(sbuf[sptr - 1])) + sptr--; + if(iident(sbuf[sptr - 1])) + while(sptr != 1 && iident(sbuf[sptr - 1])) + sptr--; + else + while(sptr != 1 && !iident(sbuf[sptr - 1]) && !iblank(sbuf[sptr - 1])) + sptr--; + } else if(cmd == Th(z_viquotedinsert) || cmd == Th(z_quotedinsert)) { + if(cmd == Th(z_viquotedinsert)) { + sbuf[sptr] = '^'; + refresh(); + } + if ((c = getkey(0)) == EOF) + feep(); + else + goto ins; + } else if(cmd == Th(z_selfinsertunmeta) || cmd == Th(z_selfinsert)) { + if(cmd == Th(z_selfinsertunmeta)) { + c &= 0x7f; + if(c == '\r') + c = '\n'; + } + ins: + if(sptr == ssbuf - 1) { + char *newbuf = halloc(ssbuf *= 2); + strcpy(newbuf, sbuf); + statusline = sbuf = newbuf; + } + sbuf[sptr++] = c; + } else { + feep(); + } + handlefeep(); + } + statusline = NULL; + selectkeymap(okeymap, 1); + return ret; +} + +/**/ +void +vihistorysearchforward(void) +{ + visrchsense = 1; + if (getvisrchstr()) + virepeatsearch(); +} + +/**/ +void +vihistorysearchbackward(void) +{ + visrchsense = -1; + if (getvisrchstr()) + virepeatsearch(); +} + +/**/ +void +virepeatsearch(void) +{ + int hl = histline, t0; + int n = zmult; + char *s; + + if (!visrchstr) { + feep(); + return; + } + if (!n) + return; + if (n < 0) { + n = -n; + visrchsense = -visrchsense; + } + t0 = strlen(visrchstr); + for (;;) { + hl += visrchsense; + if (!(s = zle_get_event(hl))) { + feep(); + return; + } + if (!metadiffer(s, (char *) line, ll)) + continue; + if (*visrchstr == '^') { + if (strncmp(s, visrchstr + 1, t0 - 1) != 0) + continue; + } else if (!hstrnstr(s, 0, visrchstr, t0, 1, 1)) + continue; + if (--n <= 0) + break; + } + zle_goto_hist(hl); +} + +/**/ +void +virevrepeatsearch(void) +{ + visrchsense = -visrchsense; + virepeatsearch(); + visrchsense = -visrchsense; +} + +/* Extra function added by A.R. Iano-Fletcher. */ +/*The extern variable "cs" is the position of the cursor. */ +/* history-beginning-search-backward */ + +/**/ +void +historybeginningsearchbackward(void) +{ + int cpos = cs; /* save cursor position */ + int hl = histline; + int n = zmult; + char *s; + + if (!n) + return; + if (n < 0) { + zmult = -n; + historybeginningsearchforward(); + zmult = n; + return; + } + for (;;) { + hl--; + if (!(s = zle_get_event(hl))) { + feep(); + return; + } + if (metadiffer(s, (char *)line, cs) < 0 && + metadiffer(s, (char *)line, ll)) + if (--n <= 0) + break; + } + + zle_goto_hist(hl); + cs = cpos; +} + +/* Extra function added by A.R. Iano-Fletcher. */ + +/* history-beginning-search-forward */ +/**/ +void +historybeginningsearchforward(void) +{ + int cpos = cs; /* save cursor position */ + int hl = histline; + int n = zmult; + char *s; + + if (!n) + return; + if (n < 0) { + zmult = -n; + historybeginningsearchbackward(); + zmult = n; + return; + } + for (;;) { + hl++; + if (!(s = zle_get_event(hl))) { + feep(); + return; + } + if (metadiffer(s, (char *)line, cs) < (hl == curhist) && + metadiffer(s, (char *)line, ll)) + if (--n <= 0) + break; + } + + zle_goto_hist(hl); + cs = cpos; +} diff --git a/Src/Zle/zle_keymap.c b/Src/Zle/zle_keymap.c new file mode 100644 index 000000000..7de96bd03 --- /dev/null +++ b/Src/Zle/zle_keymap.c @@ -0,0 +1,1238 @@ +/* + * zle_keymap.c - keymaps and key bindings + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "zle.mdh" + +/* + * Keymap structures: + * + * There is a hash table of keymap names. Each name just points to a keymap. + * More than one name may point to the same keymap. + * + * Each keymap consists of a table of bindings for each character, and a + * hash table of multi-character key bindings. The keymap has no individual + * name, but maintains a reference count. + * + * In a keymap's table of initial bindings, each character is either bound to + * a thingy, or is a prefix (in which case NULL is stored). Those prefix + * entries are matched by more complex entries in the multi-character + * binding hash table. Each entry in this hash table (which is indexed by + * metafied key sequence) either has a normal thingy binding or a string to + * send (in which case the NULL thingy is used). Each entry also has a count + * of other entries for which it is a prefix. + */ + +typedef struct keymapname *KeymapName; +typedef struct key *Key; + +struct keymapname { + HashNode next; /* next in the hash chain */ + char *nam; /* name of the keymap */ + int flags; /* various flags (see below) */ + Keymap keymap; /* the keymap itsef */ +}; + +#define KMN_IMMORTAL (1<<1) + +struct keymap { + Thingy first[256]; /* base binding of each character */ + HashTable multi; /* multi-character bindings */ + int flags; /* various flags (see below) */ + int rc; /* reference count */ +}; + +#define KM_IMMUTABLE (1<<1) + +struct key { + HashNode next; /* next in hash chain */ + char *nam; /* key sequence (metafied) */ + Thingy bind; /* binding of this key sequence */ + char *str; /* string for send-string (metafied) */ + int prefixct; /* number of sequences for which this is a prefix */ +}; + +/* This structure is used when listing keymaps. */ + +struct bindstate { + int flags; + char *kmname; + char *firstseq; + char *lastseq; + Thingy bind; + char *str; +}; + +#define BS_LIST (1<<0) +#define BS_ALL (1<<1) + +/* local functions */ + +#include "zle_keymap.pro" + +/* currently selected keymap, and its name */ + +/**/ +Keymap curkeymap; +/**/ +char *curkeymapname; + +/* the hash table of keymap names */ + +static HashTable keymapnamtab; + +/* key sequence reading data */ + +static char *keybuf; +static int keybuflen, keybufsz = 20; + +/* last command executed with execute-named-command */ + +static Thingy lastnamed; + +/**********************************/ +/* hashtable management functions */ +/**********************************/ + +/**/ +static void +createkeymapnamtab(void) +{ + keymapnamtab = newhashtable(7, "keymapnamtab", NULL); + + keymapnamtab->hash = hasher; + keymapnamtab->emptytable = emptyhashtable; + keymapnamtab->filltable = NULL; + keymapnamtab->addnode = addhashnode; + keymapnamtab->getnode = gethashnode2; + keymapnamtab->getnode2 = gethashnode2; + keymapnamtab->removenode = removehashnode; + keymapnamtab->disablenode = NULL; + keymapnamtab->enablenode = NULL; + keymapnamtab->freenode = freekeymapnamnode; + keymapnamtab->printnode = NULL; +} + +/**/ +static KeymapName +makekeymapnamnode(Keymap keymap) +{ + KeymapName kmn = (KeymapName) zcalloc(sizeof(*kmn)); + + kmn->keymap = keymap; + return kmn; +} + +/**/ +static void +freekeymapnamnode(HashNode hn) +{ + KeymapName kmn = (KeymapName) hn; + + zsfree(kmn->nam); + if(!--kmn->keymap->rc) + deletekeymap(kmn->keymap); + zfree(kmn, sizeof(*kmn)); +} + +/**/ +static HashTable +newkeytab(char *kmname) +{ + HashTable ht = newhashtable(19, + kmname ? dyncat("keytab:", kmname) : "keytab:", NULL); + + ht->hash = hasher; + ht->emptytable = emptyhashtable; + ht->filltable = NULL; + ht->addnode = addhashnode; + ht->getnode = gethashnode2; + ht->getnode2 = gethashnode2; + ht->removenode = removehashnode; + ht->disablenode = NULL; + ht->enablenode = NULL; + ht->freenode = freekeynode; + ht->printnode = NULL; + + return ht; +} + +/**/ +static Key +makekeynode(Thingy t, char *str) +{ + Key k = (Key) zcalloc(sizeof(*k)); + + k->bind = t; + k->str = str; + return k; +} + +/**/ +static void +freekeynode(HashNode hn) +{ + Key k = (Key) hn; + + zsfree(k->nam); + unrefthingy(k->bind); + zsfree(k->str); + zfree(k, sizeof(*k)); +} + +/**************************/ +/* main keymap operations */ +/**************************/ + +static HashTable copyto; + +/**/ +static Keymap +newkeymap(Keymap tocopy, char *kmname) +{ + Keymap km = zcalloc(sizeof(*km)); + int i; + + km->rc = 0; + km->multi = newkeytab(kmname); + if(tocopy) { + for(i = 256; i--; ) + km->first[i] = refthingy(tocopy->first[i]); + copyto = km->multi; + scanhashtable(tocopy->multi, 0, 0, 0, scancopykeys, 0); + } else { + for(i = 256; i--; ) + km->first[i] = refthingy(t_undefinedkey); + } + return km; +} + +/**/ +static void +scancopykeys(HashNode hn, int flags) +{ + Key k = (Key) hn; + Key kn = zalloc(sizeof(*k)); + + memcpy(kn, k, sizeof(*k)); + refthingy(kn->bind); + kn->str = ztrdup(k->str); + copyto->addnode(copyto, ztrdup(k->nam), kn); +} + +/**/ +static void +deletekeymap(Keymap km) +{ + int i; + + deletehashtable(km->multi); + for(i = 256; i--; ) + unrefthingy(km->first[i]); + zfree(km, sizeof(*km)); +} + +static Keymap skm_km; +static int skm_last; +static KeyScanFunc skm_func; +static void *skm_magic; + +/**/ +void +scankeymap(Keymap km, int sort, KeyScanFunc func, void *magic) +{ + char m[3]; + + skm_km = km; + skm_last = sort ? -1 : 255; + skm_func = func; + skm_magic = magic; + scanhashtable(km->multi, sort, 0, 0, scankeys, 0); + if(!sort) + skm_last = -1; + while(skm_last < 255) { + skm_last++; + if(km->first[skm_last] && km->first[skm_last] != t_undefinedkey) { + m[0] = skm_last; + metafy(m, 1, META_NOALLOC); + func(m, km->first[skm_last], NULL, magic); + } + } +} + +/**/ +static void +scankeys(HashNode hn, int flags) +{ + Key k = (Key) hn; + int f = k->nam[0] == Meta ? STOUC(k->nam[1])^32 : STOUC(k->nam[0]); + char m[3]; + + while(skm_last < f) { + skm_last++; + if(skm_km->first[skm_last] && + skm_km->first[skm_last] != t_undefinedkey) { + m[0] = skm_last; + metafy(m, 1, META_NOALLOC); + skm_func(m, skm_km->first[skm_last], NULL, skm_magic); + } + } + skm_func(k->nam, k->bind, k->str, skm_magic); +} + +/**************************/ +/* keymap name operations */ +/**************************/ + +/**/ +Keymap +openkeymap(char *name) +{ + KeymapName n = (KeymapName) keymapnamtab->getnode(keymapnamtab, name); + return n ? n->keymap : NULL; +} + +/**/ +static int +unlinkkeymap(char *name) +{ + KeymapName n = (KeymapName) keymapnamtab->getnode(keymapnamtab, name); + if(!n) + return 2; + if(n->flags & KMN_IMMORTAL) + return 1; + keymapnamtab->freenode(keymapnamtab->removenode(keymapnamtab, name)); + return 0; +} + +/**/ +static int +linkkeymap(Keymap km, char *name) +{ + KeymapName n = (KeymapName) keymapnamtab->getnode(keymapnamtab, name); + if(n) { + if(n->flags & KMN_IMMORTAL) + return 1; + if(n->keymap == km) + return 0; + if(!--n->keymap->rc) + deletekeymap(n->keymap); + n->keymap = km; + } else + keymapnamtab->addnode(keymapnamtab, ztrdup(name), + makekeymapnamnode(km)); + km->rc++; + return 0; +} + +/* Select a keymap as the current ZLE keymap. Can optionally fall back * + * on the guaranteed safe keymap if it fails. */ + +/**/ +int +selectkeymap(char *name, int fb) +{ + Keymap km = openkeymap(name); + + if(!km) { + char *nm = niceztrdup(name); + char *msg = tricat("No such keymap `", nm, "'"); + + zsfree(nm); + showmsg(msg); + zsfree(msg); + if(!fb) + return 1; + km = openkeymap(name = ".safe"); + } + curkeymapname = name; + curkeymap = km; + return 0; +} + +/* Reopen the currently selected keymap, in case it got deleted. This * + * should be called after doing anything that might have run an * + * arbitrary user-specified command. */ + +/**/ +void +reselectkeymap(void) +{ + selectkeymap(curkeymapname, 1); +} + +/******************************/ +/* operations on key bindings */ +/******************************/ + +/* Add/delete/change a keybinding in some keymap. km is the keymap to be * + * altered. seq is the metafied key sequence whose binding is to change. * + * bind is the thingy to which the key sequence is to be bound. For * + * send-string, bind is NULL and str is the metafied key sequence to push * + * back onto the input. */ + +/**/ +int +bindkey(Keymap km, char *seq, Thingy bind, char *str) +{ + Key k; + int f = seq[0] == Meta ? STOUC(seq[1])^32 : STOUC(seq[0]); + char *buf, *ptr; + + if(km->flags & KM_IMMUTABLE) + return 1; + if(!*seq) + return 2; + if(!bind || ztrlen(seq) > 1) { + /* key needs to become a prefix if isn't one already */ + if(km->first[f]) { + char fs[3]; + fs[0] = f; + metafy(fs, 1, META_NOALLOC); + km->multi->addnode(km->multi, ztrdup(fs), + makekeynode(km->first[f], NULL)); + km->first[f] = NULL; + } + k = (Key) km->multi->getnode(km->multi, seq); + } else { + /* If the sequence is a prefix entry only due to being * + * a send-string binding, we can remove that entry. */ + if(!km->first[f]) { + k = (Key) km->multi->getnode(km->multi, seq); + if(!k->prefixct) + km->multi->freenode(km->multi->removenode(km->multi, seq)); + else + goto domulti; + } else + unrefthingy(km->first[f]); + /* Just replace the single-character binding. */ + km->first[f] = bind; + return 0; + } + domulti: + buf = ztrdup(seq); + ptr = strchr(buf, 0); + if(bind == t_undefinedkey) { + if(k) { + zsfree(k->str); + unrefthingy(k->bind); + k->bind = t_undefinedkey; + k->str = NULL; + while(!k->prefixct && k->bind == t_undefinedkey) { + km->multi->freenode(km->multi->removenode(km->multi, buf)); + *--ptr = 0; + if(ptr[-1] == Meta) + *--ptr = 0; + k = (Key) km->multi->getnode(km->multi, buf); + k->prefixct--; + if(!k->prefixct && k->bind && + (!buf[1] || (buf[0] == Meta && !buf[2]))) { + km->first[f] = refthingy(k->bind); + km->multi->freenode(km->multi->removenode(km->multi, buf)); + break; + } + } + } + } else { + if(!k) { + int added; + + km->multi->addnode(km->multi, ztrdup(buf), makekeynode(bind, ztrdup(str))); + do { + *--ptr = 0; + if(ptr > buf && ptr[-1] == Meta) + *--ptr = 0; + k = (Key) km->multi->getnode(km->multi, buf); + if((added = !k)) + km->multi->addnode(km->multi, ztrdup(buf), + k = makekeynode(refthingy(t_undefinedkey), NULL)); + k->prefixct++; + } while(added); + } else { + unrefthingy(k->bind); + zsfree(k->str); + k->bind = bind; + k->str = bind ? NULL : ztrdup(str); + } + } + free(buf); + return 0; +} + +/* Look up a key binding. The binding is returned. In the case of a * + * send-string, NULL is returned and *strp is modified to point to the * + * metafied string of characters to be pushed back. */ + +/**/ +Thingy +keybind(Keymap km, char *seq, char **strp) +{ + Key k; + + if(ztrlen(seq) == 1) { + int f = seq[0] == Meta ? STOUC(seq[1])^32 : STOUC(seq[0]); + Thingy bind = km->first[f]; + + if(bind) + return bind; + } + k = (Key) km->multi->getnode(km->multi, seq); + if(!k) + return t_undefinedkey; + *strp = k->str; + return k->bind; +} + +/* Check whether a key sequence is a prefix of a longer bound sequence. * + * One oddity: if *nothing* in the keymap is bound, this returns true * + * for the empty sequence, even though this is not strictly accurate. */ + +/**/ +static int +keyisprefix(Keymap km, char *seq) +{ + Key k; + + if(!*seq) + return 1; + if(ztrlen(seq) == 1) { + int f = seq[0] == Meta ? STOUC(seq[1])^32 : STOUC(seq[0]); + + if(km->first[f]) + return 0; + } + k = (Key) km->multi->getnode(km->multi, seq); + return k && k->prefixct; +} + +/*******************/ +/* bindkey builtin */ +/*******************/ + +/* + * THE BINDKEY BUILTIN + * + * Keymaps can be specified to bindkey in the following ways: + * + * -e select "emacs", also link it to "main" + * -v select "viins", also link it to "main" + * -a select "vicmd" + * -M first argument gives map name + * defaults to "main" + * + * These operations cannot have a keymap selected in the normal way: + * + * -l list all the keymap names + * -d delete all keymaps and reset to the default state (no arguments) + * -D delete named keymaps + * -A link the two named keymaps (2 arguments) + * -N create new empty keymap (1 argument) + * -N create new keymap, copying the second named keymap (2 arguments) + * + * Other operations: + * + * -m add the meta bindings to the selected keymap (no arguments) + * -r unbind each named string in the selected keymap + * -s bind send-strings in the selected keymap (2+ arguments) + * bind commands in the selected keymap (2+ arguments) + * display one binding in the selected keymap (1 argument) + * display the entire selected keymap (no arguments) + * + * There is an exception that the entire keymap display will not be performed + * if the -e or -v options were used. + * + * Other options: + * + * -L do listings in the form of bindkey commands + * -R for the binding operations, accept ranges instead of sequences + */ + +/**/ +int +bin_bindkey(char *name, char **argv, char *ops, int func) +{ + static struct opn { + char o; + char selp; + int (*func) _((char *, char *, Keymap, char **, char *, char)); + int min, max; + } const opns[] = { + { 'l', 0, bin_bindkey_lsmaps, 0, 0 }, + { 'd', 0, bin_bindkey_delall, 0, 0 }, + { 'D', 0, bin_bindkey_del, 1, -1 }, + { 'A', 0, bin_bindkey_link, 2, 2 }, + { 'N', 0, bin_bindkey_new, 1, 2 }, + { 'm', 1, bin_bindkey_meta, 0, 0 }, + { 'r', 1, bin_bindkey_bind, 1, -1 }, + { 's', 1, bin_bindkey_bind, 2, -1 }, + { 0, 1, bin_bindkey_bind, 0, -1 }, + }; + struct opn const *op, *opp; + char *kmname; + Keymap km; + int n; + + /* select operation and ensure no clashing arguments */ + for(op = opns; op->o && !ops[op->o]; op++) ; + if(op->o) + for(opp = op; (++opp)->o; ) + if(ops[opp->o]) { + zwarnnam(name, "incompatible operation selection options", + NULL, 0); + return 1; + } + n = ops['e'] + ops['v'] + ops['a'] + ops['M']; + if(!op->selp && n) { + zwarnnam(name, "keymap cannot be selected with -%c", NULL, op->o); + return 1; + } + if(n > 1) { + zwarnnam(name, "incompatible keymap selection options", NULL, 0); + return 1; + } + + /* keymap selection */ + if(op->selp) { + if(ops['e']) + kmname = "emacs"; + else if(ops['v']) + kmname = "viins"; + else if(ops['a']) + kmname = "vicmd"; + else if(ops['M']) { + kmname = *argv++; + if(!kmname) { + zwarnnam(name, "-M option requires a keymap argument", NULL, 0); + return 1; + } + } else + kmname = "main"; + km = openkeymap(kmname); + if(!km) { + zwarnnam(name, "no such keymap `%s'", kmname, 0); + return 1; + } + if(ops['e'] || ops['v']) + linkkeymap(km, "main"); + } else { + kmname = NULL; + km = NULL; + } + + /* listing is a special case */ + if(!op->o && (!argv[0] || !argv[1])) { + if(ops['e'] || ops['v']) + return 0; + return bin_bindkey_list(name, kmname, km, argv, ops, op->o); + } + + /* check number of arguments */ + for(n = 0; argv[n]; n++) ; + if(n < op->min) { + zwarnnam(name, "not enough arguments for -%c", NULL, op->o); + return 1; + } else if(op->max != -1 && n > op->max) { + zwarnnam(name, "too many arguments for -%c", NULL, op->o); + return 1; + } + + /* pass on the work to the operation function */ + return op->func(name, kmname, km, argv, ops, op->o); +} + +/* list the available keymaps */ + +/**/ +static int +bin_bindkey_lsmaps(char *name, char *kmname, Keymap km, char **argv, char *ops, char func) +{ + scanhashtable(keymapnamtab, 1, 0, 0, scanlistmaps, ops['L']); + return 0; +} + +/**/ +static void +scanlistmaps(HashNode hn, int list) +{ + KeymapName n = (KeymapName) hn; + + if(list) { + fputs("bindkey -N ", stdout); + if(n->nam[0] == '-') + fputs("-- ", stdout); + quotedzputs(n->nam, stdout); + } else + nicezputs(n->nam, stdout); + putchar('\n'); +} + +/* reset all keymaps to the default state */ + +/**/ +static int +bin_bindkey_delall(char *name, char *kmname, Keymap km, char **argv, char *ops, char func) +{ + keymapnamtab->emptytable(keymapnamtab); + default_bindings(); + return 0; +} + +/* delete named keymaps */ + +/**/ +static int +bin_bindkey_del(char *name, char *kmname, Keymap km, char **argv, char *ops, char func) +{ + int ret = 0; + + do { + int r = unlinkkeymap(*argv); + if(r == 1) + zwarnnam(name, "keymap name `%s' is protected", *argv, 0); + else if(r == 2) + zwarnnam(name, "no such keymap `%s'", *argv, 0); + ret |= !!r; + } while(*++argv); + return ret; +} + +/* link named keymaps */ + +/**/ +static int +bin_bindkey_link(char *name, char *kmname, Keymap km, char **argv, char *ops, char func) +{ + km = openkeymap(argv[0]); + if(!km) { + zwarnnam(name, "no such keymap `%s'", argv[0], 0); + return 1; + } else if(linkkeymap(km, argv[1])) { + zwarnnam(name, "keymap name `%s' is protected", argv[1], 0); + return 1; + } + return 0; +} + +/* create a new keymap */ + +/**/ +static int +bin_bindkey_new(char *name, char *kmname, Keymap km, char **argv, char *ops, char func) +{ + KeymapName kmn = (KeymapName) keymapnamtab->getnode(keymapnamtab, argv[0]); + + if(kmn && (kmn -> flags & KMN_IMMORTAL)) { + zwarnnam(name, "keymap name `%s' is protected", argv[0], 0); + return 1; + } + if(argv[1]) { + km = openkeymap(argv[1]); + if(!km) { + zwarnnam(name, "no such keymap `%s'", argv[0], 0); + return 1; + } + } else + km = NULL; + linkkeymap(newkeymap(km, argv[0]), argv[0]); + return 0; +} + +/* Add standard meta bindings to a keymap. Only sequences currently either * + * unbound or bound to self-insert are affected. Note that the use of * + * bindkey() is quite necessary: if this function were to go through the * + * km->first table itself, it would miss any prefix sequences that should * + * be rebound. */ + +/**/ +static int +bin_bindkey_meta(char *name, char *kmname, Keymap km, char **argv, char *ops, char func) +{ + char m[3], *str; + int i; + Thingy fn; + + if(km->flags & KM_IMMUTABLE) { + zwarnnam(name, "keymap `%s' is protected", kmname, 0); + return 1; + } + for(i = 128; i < 256; i++) + if(metabind[i - 128] != z_undefinedkey) { + m[0] = i; + metafy(m, 1, META_NOALLOC); + fn = keybind(km, m, &str); + if(fn == t_selfinsert || fn == t_undefinedkey) + bindkey(km, m, refthingy(Th(metabind[i - 128])), NULL); + } + return 0; +} + +/* Change key bindings. func can be: * + * 'r' bind sequences to undefined-key * + * 's' bind sequneces to specified send-strings * + * 0 bind sequences to specified functions * + * If the -R option is used, bind to key ranges * + * instead of single key sequences. */ + +/**/ +static int +bin_bindkey_bind(char *name, char *kmname, Keymap km, char **argv, char *ops, char func) +{ + int ret = 0; + + if(!func || func == 's') { + char **a; + + for(a = argv+2; *a; a++) + if(!*++a) { + zwarnnam(name, "even number of arguments required", NULL, 0); + return 1; + } + } + if(km->flags & KM_IMMUTABLE) { + zwarnnam(name, "keymap `%s' is protected", kmname, 0); + return 1; + } + do { + char *useq = *argv, *bseq, *seq, *str; + int len; + Thingy fn; + + if(func == 'r') { + fn = refthingy(t_undefinedkey); + str = NULL; + } else if(func == 's') { + str = getkeystring(*++argv, &len, 2, NULL); + fn = NULL; + str = metafy(str, len, META_HREALLOC); + } else { + fn = rthingy(*++argv); + str = NULL; + } + bseq = getkeystring(useq, &len, 2, NULL); + seq = metafy(bseq, len, META_USEHEAP); + if(ops['R']) { + int first, last; + char m[3]; + + if(len < 2 || len > 2 + (bseq[1] == '-') || + (first = STOUC(bseq[0])) > (last = STOUC(bseq[len - 1]))) { + zwarnnam(name, "malformed key range `%s'", useq, 0); + ret = 1; + } else { + for(; first <= last; first++) { + m[0] = first; + metafy(m, 1, META_NOALLOC); + bindkey(km, m, refthingy(fn), str); + } + unrefthingy(fn); + } + } else { + if(bindkey(km, seq, fn, str)) { + zwarnnam(name, "cannot bind to an empty key sequence", NULL, 0); + ret = 1; + } + } + } while(*++argv); + return ret; +} + +/* List key bindings. If an argument is given, list just that one * + * binding, otherwise list the entire keymap. If the -L option is * + * given, list in the form of bindkey commands. */ + +/**/ +static int +bin_bindkey_list(char *name, char *kmname, Keymap km, char **argv, char *ops, char func) +{ + struct bindstate bs; + + bs.flags = ops['L'] ? BS_LIST : 0; + bs.kmname = kmname; + if(argv[0]) { + int len; + char *seq; + + seq = getkeystring(argv[0], &len, 2, NULL); + seq = metafy(seq, len, META_HREALLOC); + bs.flags |= BS_ALL; + bs.firstseq = bs.lastseq = seq; + bs.bind = keybind(km, seq, &bs.str); + bindlistout(&bs); + } else { + bs.firstseq = ztrdup(""); + bs.lastseq = ztrdup(""); + bs.bind = t_undefinedkey; + bs.str = NULL; + scankeymap(km, 1, scanbindlist, &bs); + bindlistout(&bs); + zsfree(bs.firstseq); + zsfree(bs.lastseq); + } + return 0; +} + +/**/ +static void +scanbindlist(char *seq, Thingy bind, char *str, void *magic) +{ + struct bindstate *bs = magic; + + if(bind == bs->bind && (bind || !strcmp(str, bs->str)) && + ztrlen(seq) == 1 && ztrlen(bs->lastseq) == 1) { + int l = bs->lastseq[1] ? + STOUC(bs->lastseq[1]) ^ 32 : STOUC(bs->lastseq[0]); + int t = seq[1] ? STOUC(seq[1]) ^ 32 : STOUC(seq[0]); + + if(t == l + 1) { + zsfree(bs->lastseq); + bs->lastseq = ztrdup(seq); + return; + } + } + bindlistout(bs); + zsfree(bs->firstseq); + bs->firstseq = ztrdup(seq); + zsfree(bs->lastseq); + bs->lastseq = ztrdup(seq); + bs->bind = bind; + bs->str = str; +} + +/**/ +static void +bindlistout(struct bindstate *bs) +{ + int range; + + if(bs->bind == t_undefinedkey && !(bs->flags & BS_ALL)) + return; + range = strcmp(bs->firstseq, bs->lastseq); + if(bs->flags & BS_LIST) { + int nodash = 1; + + fputs("bindkey ", stdout); + if(range) + fputs("-R ", stdout); + if(!bs->bind) + fputs("-s ", stdout); + if(!strcmp(bs->kmname, "main")) + ; + else if(!strcmp(bs->kmname, "vicmd")) + fputs("-a ", stdout); + else { + fputs("-M ", stdout); + quotedzputs(bs->kmname, stdout); + putchar(' '); + nodash = 0; + } + if(nodash && bs->firstseq[0] == '-') + fputs("-- ", stdout); + } + printbind(bs->firstseq, stdout); + if(range) { + putchar('-'); + printbind(bs->lastseq, stdout); + } + putchar(' '); + if(bs->bind) { + ((bs->flags & BS_LIST) ? quotedzputs : nicezputs) + (bs->bind->nam, stdout); + } else + printbind(bs->str, stdout); + putchar('\n'); +} + +/****************************/ +/* initialisation functions */ +/****************************/ + +/* main initialisation entry point */ + +/**/ +void +init_keymaps(void) +{ + createkeymapnamtab(); + default_bindings(); + keybuf = (char *)zalloc(keybufsz); + lastnamed = refthingy(t_undefinedkey); +} + +#ifdef MODULE + +/* cleanup entry point (for unloading the zle module) */ + +/**/ +void +cleanup_keymaps(void) +{ + unrefthingy(lastnamed); + deletehashtable(keymapnamtab); + zfree(keybuf, keybufsz); +} + +#endif /* MODULE */ + +/* Create the default keymaps. For efficiency reasons, this function * + * assigns directly to the km->first array. It knows that there are no * + * prefix bindings in the way, and that it is using a simple keymap. */ + +/**/ +static void +default_bindings(void) +{ + Keymap vmap = newkeymap(NULL, "viins"); + Keymap emap = newkeymap(NULL, "emacs"); + Keymap amap = newkeymap(NULL, "vicmd"); + Keymap smap = newkeymap(NULL, ".safe"); + char buf[3], *ed; + int i; + + /* vi insert mode and emacs mode: * + * 0-31 taken from the tables * + * 32-126 self-insert * + * 127 same as entry[8] * + * 128-255 self-insert */ + for (i = 0; i < 32; i++) { + vmap->first[i] = refthingy(Th(viinsbind[i])); + emap->first[i] = refthingy(Th(emacsbind[i])); + } + for (i = 32; i < 256; i++) { + vmap->first[i] = refthingy(t_selfinsert); + emap->first[i] = refthingy(t_selfinsert); + } + unrefthingy(t_selfinsert); + unrefthingy(t_selfinsert); + vmap->first[127] = refthingy(vmap->first[8]); + emap->first[127] = refthingy(emap->first[8]); + + /* vi command mode: * + * 0-127 taken from the table * + * 128-255 undefined-key */ + for (i = 0; i < 128; i++) + amap->first[i] = refthingy(Th(vicmdbind[i])); + for (i = 128; i < 256; i++) + amap->first[i] = refthingy(t_undefinedkey); + + /* safe fallback keymap: + * 0-255 self-insert, except: * + * '\n' accept-line * + * '\r' accept-line */ + for (i = 0; i < 256; i++) + smap->first[i] = refthingy(t_selfinsert); + unrefthingy(t_selfinsert); + unrefthingy(t_selfinsert); + smap->first['\n'] = refthingy(t_acceptline); + smap->first['\r'] = refthingy(t_acceptline); + + /* vt100 arrow keys are bound by default, for historical reasons. * + * Both standard and keypad modes are supported. */ + + /* vi command mode: arrow keys */ + bindkey(amap, "\33[A", refthingy(t_uplineorhistory), NULL); + bindkey(amap, "\33[B", refthingy(t_downlineorhistory), NULL); + bindkey(amap, "\33[C", refthingy(t_viforwardchar), NULL); + bindkey(amap, "\33[D", refthingy(t_vibackwardchar), NULL); + bindkey(amap, "\33OA", refthingy(t_uplineorhistory), NULL); + bindkey(amap, "\33OB", refthingy(t_downlineorhistory), NULL); + bindkey(amap, "\33OC", refthingy(t_viforwardchar), NULL); + bindkey(amap, "\33OD", refthingy(t_vibackwardchar), NULL); + + /* emacs mode: arrow keys */ + bindkey(emap, "\33[A", refthingy(t_uplineorhistory), NULL); + bindkey(emap, "\33[B", refthingy(t_downlineorhistory), NULL); + bindkey(emap, "\33[C", refthingy(t_forwardchar), NULL); + bindkey(emap, "\33[D", refthingy(t_backwardchar), NULL); + bindkey(emap, "\33OA", refthingy(t_uplineorhistory), NULL); + bindkey(emap, "\33OB", refthingy(t_downlineorhistory), NULL); + bindkey(emap, "\33OC", refthingy(t_forwardchar), NULL); + bindkey(emap, "\33OD", refthingy(t_backwardchar), NULL); + + /* emacs mode: ^X sequences */ + bindkey(emap, "\30*", refthingy(t_expandword), NULL); + bindkey(emap, "\30g", refthingy(t_listexpand), NULL); + bindkey(emap, "\30G", refthingy(t_listexpand), NULL); + bindkey(emap, "\30\16", refthingy(t_infernexthistory), NULL); + bindkey(emap, "\30\13", refthingy(t_killbuffer), NULL); + bindkey(emap, "\30\6", refthingy(t_vifindnextchar), NULL); + bindkey(emap, "\30\17", refthingy(t_overwritemode), NULL); + bindkey(emap, "\30\25", refthingy(t_undo), NULL); + bindkey(emap, "\30\26", refthingy(t_vicmdmode), NULL); + bindkey(emap, "\30\12", refthingy(t_vijoin), NULL); + bindkey(emap, "\30\2", refthingy(t_vimatchbracket), NULL); + bindkey(emap, "\30s", refthingy(t_historyincrementalsearchforward), NULL); + bindkey(emap, "\30r", refthingy(t_historyincrementalsearchbackward), NULL); + bindkey(emap, "\30u", refthingy(t_undo), NULL); + bindkey(emap, "\30\30", refthingy(t_exchangepointandmark), NULL); + bindkey(emap, "\30=", refthingy(t_whatcursorposition), NULL); + + /* emacs mode: ESC sequences, all taken from the meta binding table */ + buf[0] = '\33'; + buf[2] = 0; + for (i = 0; i < 128; i++) + if (metabind[i] != z_undefinedkey) { + buf[1] = i; + bindkey(emap, buf, refthingy(Th(metabind[i])), NULL); + } + + /* Put the keymaps in the right namespace. The "main" keymap * + * will be linked to the "emacs" keymap, except that if VISUAL * + * or EDITOR contain the string "vi" then it will be linked to * + * the "viins" keymap. */ + linkkeymap(vmap, "viins"); + linkkeymap(emap, "emacs"); + linkkeymap(amap, "vicmd"); + linkkeymap(smap, ".safe"); + if (((ed = zgetenv("VISUAL")) && strstr(ed, "vi")) || + ((ed = zgetenv("EDITOR")) && strstr(ed, "vi"))) + linkkeymap(vmap, "main"); + else + linkkeymap(emap, "main"); + + /* the .safe map cannot be modified or deleted */ + smap->flags |= KM_IMMUTABLE; + ((KeymapName) keymapnamtab->getnode(keymapnamtab, ".safe"))->flags + |= KMN_IMMORTAL; +} + +/*************************/ +/* reading key sequences */ +/*************************/ + +/* read a sequence of keys that is bound to some command in a keymap */ + +/**/ +char * +getkeymapcmd(Keymap km, Thingy *funcp, char **strp) +{ + Thingy func = t_undefinedkey; + char *str = NULL; + int lastlen = 0, lastc = c; + + keybuflen = 0; + keybuf[0] = 0; + while((c = getkeybuf(!!lastlen)) != EOF) { + char *s; + Thingy f = keybind(km, keybuf, &s); + + if(f != t_undefinedkey) { + lastlen = keybuflen; + func = f; + str = s; + lastc = c; + } + if(!keyisprefix(km, keybuf)) + break; + } + if(!lastlen && keybuflen) + lastlen = keybuflen; + else + c = lastc; + if(lastlen != keybuflen) { + unmetafy(keybuf + lastlen, &keybuflen); + ungetkeys(keybuf+lastlen, keybuflen); + if(vichgflag) + vichgbufptr -= keybuflen; + keybuf[lastlen] = 0; + } + *funcp = func; + *strp = str; + return keybuf; +} + +/**/ +static int +getkeybuf(int w) +{ + int c = getkey(w); + + if(c < 0) + return EOF; + if(keybuflen + 3 > keybufsz) + keybuf = realloc(keybuf, keybufsz *= 2); + if(imeta(c)) { + keybuf[keybuflen++] = Meta; + keybuf[keybuflen++] = c ^ 32; + } else + keybuf[keybuflen++] = c; + keybuf[keybuflen] = 0; + return c; +} + +/* Push back the last command sequence read by getkeymapcmd(). * + * Must be executed at most once after each getkeymapcmd(). */ + +/**/ +void +ungetkeycmd(void) +{ + ungetkeys(keybuf, keybuflen); +} + +/* read a command from the current keymap, with widgets */ + +/**/ +Thingy +getkeycmd(void) +{ + Thingy func; + int hops = 0; + char *seq, *str; + + sentstring: + seq = getkeymapcmd(curkeymap, &func, &str); + if(!*seq) + return NULL; + if(!func) { + int len; + char *pb; + + if (++hops == 20) { + zerr("string inserting another one too many times", NULL, 0); + hops = 0; + return NULL; + } + pb = unmetafy(ztrdup(str), &len); + ungetkeys(pb, len); + zfree(pb, strlen(str) + 1); + goto sentstring; + } + if (func == Th(z_executenamedcmd) && !statusline) { + while(func == Th(z_executenamedcmd)) + func = executenamedcommand("execute: "); + if(!func) + func = t_undefinedkey; + else if(func != Th(z_executelastnamedcmd)) { + unrefthingy(lastnamed); + lastnamed = refthingy(func); + } + } + if (func == Th(z_executelastnamedcmd)) + func = lastnamed; + return func; +} diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c new file mode 100644 index 000000000..338cdef41 --- /dev/null +++ b/Src/Zle/zle_main.c @@ -0,0 +1,907 @@ +/* + * zle_main.c - main routines for line editor + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "zle.mdh" +#include "zle_main.pro" + +/* != 0 if we're done editing */ + +/**/ +int done; + +/* location of mark */ + +/**/ +int mark; + +/* last character pressed */ + +/**/ +int c; + +/* the binding for this key */ + +/**/ +Thingy bindk; + +/* insert mode/overwrite mode flag */ + +/**/ +int insmode; + +static int eofchar, eofsent; +static long keytimeout; + +#ifdef HAVE_SELECT +/* Terminal baud rate */ + +static int baud; +#endif + +/* flags associated with last command */ + +/**/ +int lastcmd; + +/* the status line, and its length */ + +/**/ +char *statusline; +/**/ +int statusll; + +/* The current history line and cursor position for the top line * + * on the buffer stack. */ + +/**/ +int stackhist, stackcs; + +/* != 0 if we are making undo records */ + +/**/ +int undoing; + +/* current modifier status */ + +/**/ +struct modifier zmod; + +/* Current command prefix status. This is normally 0. Prefixes set * + * this to 1. Each time round the main loop, this is checked: if it * + * is 0, the modifier status is reset; if it is 1, the modifier * + * status is left unchanged, and this flag is reset to 0. The * + * effect is that several prefix commands can be executed, and have * + * cumulative effect, but any other command execution will clear the * + * modifiers. */ + +/**/ +int prefixflag; + +/* != 0 if there is a pending beep (usually indicating an error) */ + +/**/ +int feepflag; + +/* set up terminal */ + +/**/ +void +setterm(void) +{ + struct ttyinfo ti; + +#if defined(CLOBBERS_TYPEAHEAD) && defined(FIONREAD) + int val; + + ioctl(SHTTY, FIONREAD, (char *)&val); + if (val) + return; +#endif + +/* sanitize the tty */ +#ifdef HAS_TIO + shttyinfo.tio.c_lflag |= ICANON | ECHO; +# ifdef FLUSHO + shttyinfo.tio.c_lflag &= ~FLUSHO; +# endif +#else /* not HAS_TIO */ + shttyinfo.sgttyb.sg_flags = (shttyinfo.sgttyb.sg_flags & ~CBREAK) | ECHO; + shttyinfo.lmodes &= ~LFLUSHO; +#endif + + attachtty(mypgrp); + ti = shttyinfo; +#ifdef HAS_TIO + if (unset(FLOWCONTROL)) + ti.tio.c_iflag &= ~IXON; + ti.tio.c_lflag &= ~(ICANON | ECHO +# ifdef FLUSHO + | FLUSHO +# endif + ); +# ifdef TAB3 + ti.tio.c_oflag &= ~TAB3; +# else +# ifdef OXTABS + ti.tio.c_oflag &= ~OXTABS; +# else + ti.tio.c_oflag &= ~XTABS; +# endif +# endif + ti.tio.c_oflag |= ONLCR; + ti.tio.c_cc[VQUIT] = +# ifdef VDISCARD + ti.tio.c_cc[VDISCARD] = +# endif +# ifdef VSUSP + ti.tio.c_cc[VSUSP] = +# endif +# ifdef VDSUSP + ti.tio.c_cc[VDSUSP] = +# endif +# ifdef VSWTCH + ti.tio.c_cc[VSWTCH] = +# endif +# ifdef VLNEXT + ti.tio.c_cc[VLNEXT] = +# endif + VDISABLEVAL; +# if defined(VSTART) && defined(VSTOP) + if (unset(FLOWCONTROL)) + ti.tio.c_cc[VSTART] = ti.tio.c_cc[VSTOP] = VDISABLEVAL; +# endif + eofchar = ti.tio.c_cc[VEOF]; + ti.tio.c_cc[VMIN] = 1; + ti.tio.c_cc[VTIME] = 0; + ti.tio.c_iflag |= (INLCR | ICRNL); + /* this line exchanges \n and \r; it's changed back in getkey + so that the net effect is no change at all inside the shell. + This double swap is to allow typeahead in common cases, eg. + + % bindkey -s '^J' 'echo foo^M' + % sleep 10 + echo foo <--- typed before sleep returns + + The shell sees \n instead of \r, since it was changed by the kernel + while zsh wasn't looking. Then in getkey() \n is changed back to \r, + and it sees "echo foo", as expected. Without the double + swap the shell would see "echo foo\n", which is translated to + "echo fooecho foo" because of the binding. + Note that if you type during the sleep the shell just sees + \n, which is translated to \r in getkey(), and you just get another + prompt. For type-ahead to work in ALL cases you have to use + stty inlcr. + + Unfortunately it's IMPOSSIBLE to have a general solution if both + and are mapped to the same character. The shell + could check if there is input and read it before setting it's own + terminal modes but if we get a \n we don't know whether to keep it or + change to \r :-( + */ + +#else /* not HAS_TIO */ + ti.sgttyb.sg_flags = (ti.sgttyb.sg_flags | CBREAK) & ~ECHO & ~XTABS; + ti.lmodes &= ~LFLUSHO; + eofchar = ti.tchars.t_eofc; + ti.tchars.t_quitc = + ti.ltchars.t_suspc = + ti.ltchars.t_flushc = + ti.ltchars.t_dsuspc = ti.ltchars.t_lnextc = -1; +#endif + +#if defined(TTY_NEEDS_DRAINING) && defined(TIOCOUTQ) && defined(HAVE_SELECT) + if (baud) { /**/ + int n = 0; + + while ((ioctl(SHTTY, TIOCOUTQ, (char *)&n) >= 0) && n) { + struct timeval tv; + + tv.tv_sec = n / baud; + tv.tv_usec = ((n % baud) * 1000000) / baud; + select(0, NULL, NULL, NULL, &tv); + } + } +#endif + + settyinfo(&ti); +} + +static char *kungetbuf; +static int kungetct, kungetsz; + +/**/ +void +ungetkey(int ch) +{ + if (kungetct == kungetsz) + kungetbuf = realloc(kungetbuf, kungetsz *= 2); + kungetbuf[kungetct++] = ch; +} + +/**/ +void +ungetkeys(char *s, int len) +{ + s += len; + while (len--) + ungetkey(*--s); +} + +#if defined(pyr) && defined(HAVE_SELECT) +static int +breakread(int fd, char *buf, int n) +{ + fd_set f; + + FD_ZERO(&f); + FD_SET(fd, &f); + return (select(fd + 1, (SELECT_ARG_2_T) & f, NULL, NULL, NULL) == -1 ? + EOF : read(fd, buf, n)); +} + +# define read breakread +#endif + +/**/ +int +getkey(int keytmout) +{ + char cc; + unsigned int ret; + long exp100ths; + int die = 0, r, icnt = 0; + int old_errno = errno; + +#ifdef HAVE_SELECT + fd_set foofd; + +#else +# ifdef HAS_TIO + struct ttyinfo ti; + +# endif +#endif + + if (kungetct) + ret = STOUC(kungetbuf[--kungetct]); + else { + if (keytmout) { + if (keytimeout > 500) + exp100ths = 500; + else if (keytimeout > 0) + exp100ths = keytimeout; + else + exp100ths = 0; +#ifdef HAVE_SELECT + if (exp100ths) { + struct timeval expire_tv; + + expire_tv.tv_sec = exp100ths / 100; + expire_tv.tv_usec = (exp100ths % 100) * 10000L; + FD_ZERO(&foofd); + FD_SET(SHTTY, &foofd); + if (select(SHTTY+1, (SELECT_ARG_2_T) & foofd, + NULL, NULL, &expire_tv) <= 0) + return EOF; + } +#else +# ifdef HAS_TIO + ti = shttyinfo; + ti.tio.c_lflag &= ~ICANON; + ti.tio.c_cc[VMIN] = 0; + ti.tio.c_cc[VTIME] = exp100ths / 10; +# ifdef HAVE_TERMIOS_H + tcsetattr(SHTTY, TCSANOW, &ti.tio); +# else + ioctl(SHTTY, TCSETA, &ti.tio); +# endif + r = read(SHTTY, &cc, 1); +# ifdef HAVE_TERMIOS_H + tcsetattr(SHTTY, TCSANOW, &shttyinfo.tio); +# else + ioctl(SHTTY, TCSETA, &shttyinfo.tio); +# endif + return (r <= 0) ? EOF : cc; +# endif +#endif + } + while ((r = read(SHTTY, &cc, 1)) != 1) { + if (r == 0) { + /* The test for IGNOREEOF was added to make zsh ignore ^Ds + that were typed while commands are running. Unfortuantely + this caused trouble under at least one system (SunOS 4.1). + Here shells that lost their xterm (e.g. if it was killed + with -9) didn't fail to read from the terminal but instead + happily continued to read EOFs, so that the above read + returned with 0, and, with IGNOREEOF set, this caused + an infinite loop. The simple way around this was to add + the counter (icnt) so that this happens 20 times and than + the shell gives up (yes, this is a bit dirty...). */ + if (isset(IGNOREEOF) && icnt++ < 20) + continue; + stopmsg = 1; + zexit(1, 0); + } + icnt = 0; + if (errno == EINTR) { + die = 0; + if (!errflag && !retflag && !breaks) + continue; + errflag = 0; + errno = old_errno; + return EOF; + } else if (errno == EWOULDBLOCK) { + fcntl(0, F_SETFL, 0); + } else if (errno == EIO && !die) { + ret = opts[MONITOR]; + opts[MONITOR] = 1; + attachtty(mypgrp); + refresh(); /* kludge! */ + opts[MONITOR] = ret; + die = 1; + } else if (errno != 0) { + zerr("error on TTY read: %e", NULL, errno); + stopmsg = 1; + zexit(1, 0); + } + } + if (cc == '\r') /* undo the exchange of \n and \r determined by */ + cc = '\n'; /* setterm() */ + else if (cc == '\n') + cc = '\r'; + + ret = STOUC(cc); + } + if (vichgflag) { + if (vichgbufptr == vichgbufsz) + vichgbuf = realloc(vichgbuf, vichgbufsz *= 2); + vichgbuf[vichgbufptr++] = ret; + } + errno = old_errno; + return ret; +} + +/* Read a line. It is returned metafied. */ + +/**/ +unsigned char * +zleread(char *lp, char *rp, int ha) +{ + unsigned char *s; + int old_errno = errno; + int tmout = getiparam("TMOUT"); + +#ifdef HAVE_SELECT + long costmult; + struct timeval tv; + fd_set foofd; + + baud = getiparam("BAUD"); + costmult = (baud) ? 3840000L / baud : 0; + tv.tv_sec = 0; +#endif + + /* ZLE doesn't currently work recursively. This is needed in case a * + * select loop is used in a function called from ZLE. vared handles * + * this differently itself. */ + if(zleactive) { + char *pptbuf; + int pptlen; + + pptbuf = unmetafy(promptexpand(lp, 0, NULL, NULL), &pptlen); + write(2, (WRITE_ARG_2_T)pptbuf, pptlen); + free(pptbuf); + return (unsigned char *)shingetline(); + } + + keytimeout = getiparam("KEYTIMEOUT"); + if (!shout) { + if (SHTTY != -1) + init_shout(); + + if (!shout) + return NULL; + /* We could be smarter and default to a system read. */ + + /* If we just got a new shout, make sure the terminal is set up. */ + if (termflags & TERM_UNKNOWN) + init_term(); + } + + fflush(shout); + fflush(stderr); + intr(); + insmode = unset(OVERSTRIKE); + eofsent = 0; + resetneeded = 0; + lpptbuf = promptexpand(lp, 1, NULL, NULL); + pmpt_attr = txtchange; + rpptbuf = promptexpand(rp, 1, NULL, NULL); + rpmpt_attr = txtchange; + histallowed = ha; + PERMALLOC { + histline = curhist; +#ifdef HAVE_SELECT + FD_ZERO(&foofd); +#endif + undoing = 1; + line = (unsigned char *)zalloc((linesz = 256) + 2); + virangeflag = lastcmd = done = cs = ll = mark = 0; + curhistline = NULL; + vichgflag = 0; + viinsbegin = 0; + statusline = NULL; + selectkeymap("main", 1); + fixsuffix(); + if ((s = (unsigned char *)getlinknode(bufstack))) { + setline((char *)s); + zsfree((char *)s); + if (stackcs != -1) { + cs = stackcs; + stackcs = -1; + if (cs > ll) + cs = ll; + } + if (stackhist != -1) { + histline = stackhist; + stackhist = -1; + } + } + initundo(); + if (isset(PROMPTCR)) + putc('\r', shout); + if (tmout) + alarm(tmout); + zleactive = 1; + resetneeded = 1; + errflag = retflag = 0; + lastcol = -1; + initmodifier(&zmod); + prefixflag = 0; + feepflag = 0; + refresh(); + while (!done && !errflag) { + + statusline = NULL; + vilinerange = 0; + reselectkeymap(); + bindk = getkeycmd(); + if (!ll && isfirstln && c == eofchar) { + eofsent = 1; + break; + } + if (bindk) { + execzlefunc(bindk); + handleprefixes(); + /* for vi mode, make sure the cursor isn't somewhere illegal */ + if (invicmdmode() && cs > findbol() && + (cs == ll || line[cs] == '\n')) + cs--; + if (undoing) + handleundo(); + } else { + errflag = 1; + break; + } +#ifdef HAVE_SELECT + if (baud && !(lastcmd & ZLE_MENUCMP)) { + FD_SET(SHTTY, &foofd); + if ((tv.tv_usec = cost * costmult) > 500000) + tv.tv_usec = 500000; + if (!kungetct && select(SHTTY+1, (SELECT_ARG_2_T) & foofd, + NULL, NULL, &tv) <= 0) + refresh(); + } else +#endif + if (!kungetct) + refresh(); + handlefeep(); + } + statusline = NULL; + invalidatelist(); + trashzle(); + free(lpptbuf); + free(rpptbuf); + zleactive = 0; + alarm(0); + } LASTALLOC; + zsfree(curhistline); + freeundo(); + if (eofsent) { + free(line); + line = NULL; + } else { + line[ll++] = '\n'; + line = (unsigned char *) metafy((char *) line, ll, META_REALLOC); + } + forget_edits(); + errno = old_errno; + return line; +} + +/* execute a widget */ + +/**/ +void +execzlefunc(Thingy func) +{ + Widget w; + + if(func->flags & DISABLED) { + /* this thingy is not the name of a widget */ + char *nm = niceztrdup(func->nam); + char *msg = tricat("No such widget `", nm, "'"); + + zsfree(nm); + showmsg(msg); + zsfree(msg); + feep(); + } else if((w = func->widget)->flags & WIDGET_INT) { + int wflags = w->flags; + + if(!(wflags & ZLE_KEEPSUFFIX)) + removesuffix(); + if(!(wflags & ZLE_MENUCMP)) { + fixsuffix(); + invalidatelist(); + } + if (wflags & ZLE_LINEMOVE) + vilinerange = 1; + if(!(wflags & ZLE_LASTCOL)) + lastcol = -1; + w->u.fn(); + lastcmd = wflags; + } else { + List l = getshfunc(w->u.fnnam); + + if(l == &dummy_list) { + /* the shell function doesn't exist */ + char *nm = niceztrdup(w->u.fnnam); + char *msg = tricat("No such shell function `", nm, "'"); + + zsfree(nm); + showmsg(msg); + zsfree(msg); + feep(); + } else { + startparamscope(); + makezleparams(); + doshfunc(l, NULL, 0, 1); + endparamscope(); + lastcmd = 0; + } + } +} + +/* initialise command modifiers */ + +/**/ +static void +initmodifier(struct modifier *mp) +{ + mp->flags = 0; + mp->mult = 1; + mp->tmult = 1; + mp->vibuf = 0; +} + +/* Reset command modifiers, unless the command just executed was a prefix. * + * Also set zmult, if the multiplier has been amended. */ + +/**/ +static void +handleprefixes(void) +{ + if (prefixflag) { + prefixflag = 0; + if(zmod.flags & MOD_TMULT) { + zmod.flags |= MOD_MULT; + zmod.mult = zmod.tmult; + } + } else + initmodifier(&zmod); +} + +/* vared: edit (literally) a parameter value */ + +/**/ +static int +bin_vared(char *name, char **args, char *ops, int func) +{ + char *s; + char *t; + Param pm; + int create = 0; + char *p1 = NULL, *p2 = NULL; + + /* all options are handled as arguments */ + while (*args && **args == '-') { + while (*++(*args)) + switch (**args) { + case 'c': + /* -c option -- allow creation of the parameter if it doesn't + yet exist */ + create = 1; + break; + case 'p': + /* -p option -- set main prompt string */ + if ((*args)[1]) + p1 = *args + 1, *args = "" - 1; + else if (args[1]) + p1 = *(++args), *args = "" - 1; + else { + zwarnnam(name, "prompt string expected after -%c", NULL, + **args); + return 1; + } + break; + case 'r': + /* -r option -- set right prompt string */ + if ((*args)[1]) + p2 = *args + 1, *args = "" - 1; + else if (args[1]) + p2 = *(++args), *args = "" - 1; + else { + zwarnnam(name, "prompt string expected after -%c", NULL, + **args); + return 1; + } + break; + case 'h': + /* -h option -- enable history */ + ops['h'] = 1; + break; + default: + /* unrecognised option character */ + zwarnnam(name, "unknown option: %s", *args, 0); + return 1; + } + args++; + } + + /* check we have a parameter name */ + if (!*args) { + zwarnnam(name, "missing variable", NULL, 0); + return 1; + } + /* handle non-existent parameter */ + if (!(s = getsparam(args[0]))) { + if (create) + createparam(args[0], PM_SCALAR); + else { + zwarnnam(name, "no such variable: %s", args[0], 0); + return 1; + } + } + + if(zleactive) { + zwarnnam(name, "ZLE cannot be used recursively (yet)", NULL, 0); + return 1; + } + + /* edit the parameter value */ + PERMALLOC { + pushnode(bufstack, ztrdup(s)); + } LASTALLOC; + t = (char *) zleread(p1, p2, ops['h']); + if (!t || errflag) { + /* error in editing */ + errflag = 0; + return 1; + } + /* strip off trailing newline, if any */ + if (t[strlen(t) - 1] == '\n') + t[strlen(t) - 1] = '\0'; + /* final assignment of parameter value */ + pm = (Param) paramtab->getnode(paramtab, args[0]); + if (pm && PM_TYPE(pm->flags) == PM_ARRAY) { + char **a; + + PERMALLOC { + a = spacesplit(t, 1); + } LASTALLOC; + setaparam(args[0], a); + } else + setsparam(args[0], t); + return 0; +} + +/**/ +void +describekeybriefly(void) +{ + char *seq, *str, *msg, *is; + Thingy func; + + if (statusline) + return; + statusline = "Describe key briefly: _"; + statusll = strlen(statusline); + refresh(); + seq = getkeymapcmd(curkeymap, &func, &str); + statusline = NULL; + if(!*seq) + return; + msg = bindztrdup(seq); + msg = appstr(msg, " is "); + if (!func) + is = bindztrdup(str); + else + is = niceztrdup(func->nam); + msg = appstr(msg, is); + zsfree(is); + showmsg(msg); + zsfree(msg); +} + +#define MAXFOUND 4 + +struct findfunc { + Thingy func; + int found; + char *msg; +}; + +/**/ +static void +scanfindfunc(char *seq, Thingy func, char *str, void *magic) +{ + struct findfunc *ff = magic; + + if(func != ff->func) + return; + if (!ff->found++) + ff->msg = appstr(ff->msg, " is on"); + if(ff->found <= MAXFOUND) { + char *b = bindztrdup(seq); + + ff->msg = appstr(ff->msg, " "); + ff->msg = appstr(ff->msg, b); + zsfree(b); + } +} + +/**/ +void +whereis(void) +{ + struct findfunc ff; + + if (!(ff.func = executenamedcommand("Where is: "))) + return; + ff.found = 0; + ff.msg = niceztrdup(ff.func->nam); + scankeymap(curkeymap, 1, scanfindfunc, &ff); + if (!ff.found) + ff.msg = appstr(ff.msg, " is not bound to any key"); + else if(ff.found > MAXFOUND) + ff.msg = appstr(ff.msg, " et al"); + showmsg(ff.msg); + zsfree(ff.msg); +} + +/**/ +void +trashzle(void) +{ + if (zleactive) { + /* This refresh() is just to get the main editor display right and * + * get the cursor in the right place. For that reason, we disable * + * list display (which would otherwise result in infinite * + * recursion [at least, it would if refresh() didn't have its * + * extra `inlist' check]). */ + int sl = showinglist; + showinglist = 0; + refresh(); + showinglist = sl; + moveto(nlnct, 0); + if (clearflag && tccan(TCCLEAREOD)) { + tcout(TCCLEAREOD); + clearflag = 0; + } + if (postedit) + fprintf(shout, "%s", postedit); + fflush(shout); + resetneeded = 1; + settyinfo(&shttyinfo); + } + if (errflag) + kungetct = 0; +} + +static struct builtin bintab[] = { + BUILTIN("bindkey", 0, bin_bindkey, 0, -1, 0, "evaMldDANmrsLR", NULL), + BUILTIN("vared", 0, bin_vared, 1, 7, 0, NULL, NULL), + BUILTIN("zle", 0, bin_zle, 0, -1, 0, "lDANL", NULL), +}; + +/**/ +int +boot_zle(Module m) +{ + /* Set up editor entry points */ + trashzleptr = trashzle; + gotwordptr = gotword; + refreshptr = refresh; + spaceinlineptr = spaceinline; + zlereadptr = zleread; + + /* initialise the thingies */ + init_thingies(); + + /* miscellaneous initialisations */ + stackhist = stackcs = -1; + kungetbuf = (char *) zalloc(kungetsz = 32); + + /* initialise the keymap system */ + init_keymaps(); + + addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + return 0; +} + +#ifdef MODULE + +/**/ +int +cleanup_zle(Module m) +{ + int i; + + if(zleactive) { + zerrnam(m->nam, "can't unload the zle module while zle is active", + NULL, 0); + return 1; + } + + deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + cleanup_keymaps(); + deletehashtable(thingytab); + + zfree(vichgbuf, vichgbufsz); + zfree(kungetbuf, kungetsz); + free_isrch_spots(); + + zfree(cutbuf.buf, cutbuf.len); + for(i = KRINGCT; i--; ) + zfree(kring[i].buf, kring[i].len); + for(i = 35; i--; ) + zfree(vibuf[i].buf, vibuf[i].len); + + /* editor entry points */ + trashzleptr = noop_function; + gotwordptr = noop_function; + refreshptr = noop_function; + spaceinlineptr = noop_function_int; + zlereadptr = fallback_zleread; + + return 0; +} + +#endif /* MODULE */ diff --git a/Src/Zle/zle_misc.c b/Src/Zle/zle_misc.c new file mode 100644 index 000000000..42953852f --- /dev/null +++ b/Src/Zle/zle_misc.c @@ -0,0 +1,816 @@ +/* + * zle_misc.c - miscellaneous editor routines + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "zle.mdh" +#include "zle_misc.pro" + +/* insert a metafied string, with repetition and suffix removal */ + +/**/ +void +doinsert(char *str) +{ + char *s; + int len = ztrlen(str); + int c1 = *str == Meta ? STOUC(str[1])^32 : STOUC(*str);/* first character */ + int neg = zmult < 0; /* insert *after* the cursor? */ + int m = neg ? -zmult : zmult; /* number of copies to insert */ + + iremovesuffix(c1); + invalidatelist(); + + if(insmode) + spaceinline(m * len); + else if(cs + m * len > ll) + spaceinline(cs + m * len - ll); + while(m--) + for(s = str; *s; s++) + line[cs++] = *s == Meta ? *++s ^ 32 : *s; + if(neg) + cs += zmult * len; +} + +/**/ +void +selfinsert(void) +{ + char s[3], *p = s; + + if(imeta(c)) { + *p++ = Meta; + c ^= 32; + } + *p++ = c; + *p = 0; + doinsert(s); +} + +/**/ +void +selfinsertunmeta(void) +{ + c &= 0x7f; + if (c == '\r') + c = '\n'; + selfinsert(); +} + +/**/ +void +deletechar(void) +{ + if (zmult < 0) { + zmult = -zmult; + backwarddeletechar(); + zmult = -zmult; + return; + } + if (cs + zmult <= ll) { + cs += zmult; + backdel(zmult); + } else + feep(); +} + +/**/ +void +backwarddeletechar(void) +{ + if (zmult < 0) { + zmult = -zmult; + deletechar(); + zmult = -zmult; + return; + } + backdel(zmult > cs ? cs : zmult); +} + +/**/ +void +killwholeline(void) +{ + int i, fg, n = zmult; + + if (n < 0) + return; + while (n--) { + if ((fg = (cs && cs == ll))) + cs--; + while (cs && line[cs - 1] != '\n') + cs--; + for (i = cs; i != ll && line[i] != '\n'; i++); + forekill(i - cs + (i != ll), fg); + } +} + +/**/ +void +killbuffer(void) +{ + cs = 0; + forekill(ll, 0); +} + +/**/ +void +backwardkillline(void) +{ + int i = 0, n = zmult; + + if (n < 0) { + zmult = -n; + killline(); + zmult = n; + return; + } + while (n--) { + if (cs && line[cs - 1] == '\n') + cs--, i++; + else + while (cs && line[cs - 1] != '\n') + cs--, i++; + } + forekill(i, 1); +} + +/**/ +void +gosmacstransposechars(void) +{ + int cc; + + if (cs < 2 || line[cs - 1] == '\n' || line[cs - 2] == '\n') { + if (cs == ll || line[cs] == '\n' || + ((cs + 1 == ll || line[cs + 1] == '\n') && + (!cs || line[cs - 1] == '\n'))) { + feep(); + return; + } + cs += (cs == 0 || line[cs - 1] == '\n') ? 2 : 1; + } + cc = line[cs - 2]; + line[cs - 2] = line[cs - 1]; + line[cs - 1] = cc; +} + +/**/ +void +transposechars(void) +{ + int cc, ct; + int n = zmult; + int neg = n < 0; + + if (neg) + n = -n; + while (n--) { + if (!(ct = cs) || line[cs - 1] == '\n') { + if (ll == cs || line[cs] == '\n') { + feep(); + return; + } + if (!neg) + cs++; + ct++; + } + if (neg) { + if (cs && line[cs - 1] != '\n') { + cs--; + if (ct > 1 && line[ct - 2] != '\n') + ct--; + } + } else { + if (cs != ll && line[cs] != '\n') + cs++; + } + if (ct == ll || line[ct] == '\n') + ct--; + if (ct < 1 || line[ct - 1] == '\n') { + feep(); + return; + } + cc = line[ct - 1]; + line[ct - 1] = line[ct]; + line[ct] = cc; + } +} + +/**/ +void +poundinsert(void) +{ + cs = 0; + vifirstnonblank(); + if (line[cs] != '#') { + spaceinline(1); + line[cs] = '#'; + cs = findeol(); + while(cs != ll) { + cs++; + vifirstnonblank(); + spaceinline(1); + line[cs] = '#'; + cs = findeol(); + } + } else { + foredel(1); + cs = findeol(); + while(cs != ll) { + cs++; + vifirstnonblank(); + if(line[cs] == '#') + foredel(1); + cs = findeol(); + } + } + done = 1; +} + +/**/ +void +acceptline(void) +{ + done = 1; +} + +/**/ +void +acceptandhold(void) +{ + pushnode(bufstack, metafy((char *)line, ll, META_DUP)); + stackcs = cs; + done = 1; +} + +/**/ +void +killline(void) +{ + int i = 0, n = zmult; + + if (n < 0) { + zmult = -n; + backwardkillline(); + zmult = n; + return; + } + while (n--) { + if (line[cs] == '\n') + cs++, i++; + else + while (cs != ll && line[cs] != '\n') + cs++, i++; + } + backkill(i, 0); +} + +/**/ +void +killregion(void) +{ + if (mark > ll) + mark = ll; + if (mark > cs) + forekill(mark - cs, 0); + else + backkill(cs - mark, 1); +} + +/**/ +void +copyregionaskill(void) +{ + if (mark > ll) + mark = ll; + if (mark > cs) + cut(cs, mark - cs, 0); + else + cut(mark, cs - mark, 1); +} + +static int kct, yankb, yanke; + +/**/ +void +yank(void) +{ + Cutbuffer buf = &cutbuf; + int n = zmult; + + if (n < 0) + return; + if (zmod.flags & MOD_VIBUF) + buf = &vibuf[zmod.vibuf]; + if (!buf->buf) { + feep(); + return; + } + mark = cs; + yankb = cs; + while (n--) { + kct = kringnum; + spaceinline(buf->len); + memcpy((char *)line + cs, buf->buf, buf->len); + cs += buf->len; + yanke = cs; + } +} + +/**/ +void +yankpop(void) +{ + int cc; + + if (!(lastcmd & ZLE_YANK) || !kring[kct].buf) { + feep(); + return; + } + cs = yankb; + foredel(yanke - yankb); + cc = kring[kct].len; + spaceinline(cc); + memcpy((char *)line + cs, kring[kct].buf, cc); + cs += cc; + yanke = cs; + kct = (kct + KRINGCT - 1) % KRINGCT; +} + +/**/ +void +overwritemode(void) +{ + insmode ^= 1; +} +/**/ +void +whatcursorposition(void) +{ + char msg[100]; + char *s = msg; + int bol = findbol(); + int c = STOUC(line[cs]); + + if (cs == ll) + strucpy(&s, "EOF"); + else { + strucpy(&s, "Char: "); + switch (c) { + case ' ': + strucpy(&s, "SPC"); + break; + case '\t': + strucpy(&s, "TAB"); + break; + case '\n': + strucpy(&s, "LFD"); + break; + default: + if (imeta(c)) { + *s++ = Meta; + *s++ = c ^ 32; + } else + *s++ = c; + } + sprintf(s, " (0%o, %d, 0x%x)", c, c, c); + s += strlen(s); + } + sprintf(s, " point %d of %d(%d%%) column %d", cs+1, ll+1, + ll ? 100 * cs / ll : 0, cs - bol); + showmsg(msg); +} + +/**/ +void +undefinedkey(void) +{ + feep(); +} + +/**/ +void +quotedinsert(void) +{ +#ifndef HAS_TIO + struct sgttyb sob; + + sob = shttyinfo.sgttyb; + sob.sg_flags = (sob.sg_flags | RAW) & ~ECHO; + ioctl(SHTTY, TIOCSETN, &sob); +#endif + c = getkey(0); +#ifndef HAS_TIO + setterm(); +#endif + if (c < 0) + feep(); + else + selfinsert(); +} + +/**/ +void +digitargument(void) +{ + int sign = (zmult < 0) ? -1 : 1; + + if (!(zmod.flags & MOD_TMULT)) + zmod.tmult = 0; + if (zmod.flags & MOD_NEG) { + /* If we just had a negative argument, this is the digit, * + * rather than the -1 assumed by negargument() */ + zmod.tmult = sign * (c & 0xf); + zmod.flags &= ~MOD_NEG; + } else + zmod.tmult = zmod.tmult * 10 + sign * (c & 0xf); + zmod.flags |= MOD_TMULT; + prefixflag = 1; +} + +/**/ +void +negargument(void) +{ + if(zmod.flags & MOD_TMULT) { + feep(); + return; + } + zmod.tmult = -1; + zmod.flags |= MOD_TMULT|MOD_NEG; + prefixflag = 1; +} + +/**/ +void +universalargument(void) +{ + int digcnt = 0, pref = 0, minus = 1, gotk; + while ((gotk = getkey(0)) != EOF) { + if (gotk == '-' && !digcnt) { + minus = -1; + digcnt++; + } else if (gotk >= '0' && gotk <= '9') { + pref = pref * 10 + (gotk & 0xf); + digcnt++; + } else { + ungetkey(gotk); + break; + } + } + if (digcnt) + zmod.tmult = minus * (pref ? pref : 1); + else + zmod.tmult *= 4; + zmod.flags |= MOD_TMULT; + prefixflag = 1; +} + +/**/ +void +copyprevword(void) +{ + int len, t0; + + for (t0 = cs - 1; t0 >= 0; t0--) + if (iword(line[t0])) + break; + for (; t0 >= 0; t0--) + if (!iword(line[t0])) + break; + if (t0) + t0++; + len = cs - t0; + spaceinline(len); + memcpy((char *)&line[cs], (char *)&line[t0], len); + cs += len; +} + +/**/ +void +sendbreak(void) +{ + errflag = 1; +} + +/**/ +void +quoteregion(void) +{ + char *str; + size_t len; + + if (mark > ll) + mark = ll; + if (mark < cs) { + int tmp = mark; + mark = cs; + cs = tmp; + } + str = (char *)hcalloc(len = mark - cs); + memcpy(str, (char *)&line[cs], len); + foredel(len); + str = makequote(str, &len); + spaceinline(len); + memcpy((char *)&line[cs], str, len); + mark = cs; + cs += len; +} + +/**/ +void +quoteline(void) +{ + char *str; + size_t len = ll; + + str = makequote((char *)line, &len); + sizeline(len); + memcpy(line, str, len); + cs = ll = len; +} + +/**/ +static char * +makequote(char *str, size_t *len) +{ + int qtct = 0; + char *l, *ol; + char *end = str + *len; + + for (l = str; l < end; l++) + if (*l == '\'') + qtct++; + *len += 2 + qtct*3; + l = ol = (char *)halloc(*len); + *l++ = '\''; + for (; str < end; str++) + if (*str == '\'') { + *l++ = '\''; + *l++ = '\\'; + *l++ = '\''; + *l++ = '\''; + } else + *l++ = *str; + *l++ = '\''; + return ol; +} + +static char *cmdbuf; +static LinkList cmdll; +static int cmdambig; + +/**/ +static void +scancompcmd(HashNode hn, int flags) +{ + int l; + Thingy t = (Thingy) hn; + + if(strpfx(cmdbuf, t->nam)) { + addlinknode(cmdll, t->nam); + l = pfxlen(peekfirst(cmdll), t->nam); + if (l < cmdambig) + cmdambig = l; + } + +} + +#define NAMLEN 60 + +/**/ +Thingy +executenamedcommand(char *prmt) +{ + Thingy cmd; + int len, l = strlen(prmt); + char *ptr; + char *okeymap = curkeymapname; + + cmdbuf = halloc(l + NAMLEN + 2); + strcpy(cmdbuf, prmt); + statusline = cmdbuf; + selectkeymap("main", 1); + ptr = cmdbuf += l; + len = 0; + for (;;) { + *ptr = '_'; + statusll = l + len + 1; + refresh(); + if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak)) { + statusline = NULL; + selectkeymap(okeymap, 1); + return NULL; + } + if(cmd == Th(z_clearscreen)) { + clearscreen(); + } else if(cmd == Th(z_redisplay)) { + redisplay(); + } else if(cmd == Th(z_viquotedinsert)) { + *ptr = '^'; + refresh(); + c = getkey(0); + if(c == EOF || !c || len == NAMLEN) + feep(); + else + *ptr++ = c, len++; + } else if(cmd == Th(z_quotedinsert)) { + if((c = getkey(0)) == EOF || !c || len == NAMLEN) + feep(); + else + *ptr++ = c, len++; + } else if(cmd == Th(z_backwarddeletechar) || + cmd == Th(z_vibackwarddeletechar)) { + if (len) + len--, ptr--; + } else if(cmd == Th(z_killregion) || cmd == Th(z_backwardkillword) || + cmd == Th(z_vibackwardkillword)) { + while (len && (len--, *--ptr != '-')); + } else if(cmd == Th(z_killwholeline) || cmd == Th(z_vikillline) || + cmd == Th(z_backwardkillline)) { + len = 0; + ptr = cmdbuf; + } else { + if(cmd == Th(z_acceptline) || cmd == Th(z_vicmdmode)) { + Thingy r; + unambiguous: + *ptr = 0; + r = rthingy(cmdbuf); + if (!(r->flags & DISABLED)) { + unrefthingy(r); + statusline = NULL; + selectkeymap(okeymap, 1); + return r; + } + unrefthingy(r); + } + if(cmd == Th(z_selfinsertunmeta)) { + c &= 0x7f; + if(c == '\r') + c = '\n'; + cmd = Th(z_selfinsert); + } + if (cmd == Th(z_listchoices) || cmd == Th(z_deletecharorlist) || + cmd == Th(z_expandorcomplete) || cmd == Th(z_completeword) || + cmd == Th(z_expandorcompleteprefix) || cmd == Th(z_vicmdmode) || + cmd == Th(z_acceptline) || c == ' ' || c == '\t') { + cmdambig = 100; + + HEAPALLOC { + cmdll = newlinklist(); + *ptr = 0; + + scanhashtable(thingytab, 1, 0, DISABLED, scancompcmd, 0); + } LASTALLOC; + if (empty(cmdll)) + feep(); + else if (cmd == Th(z_listchoices) || + cmd == Th(z_deletecharorlist)) { + int zmultsav = zmult; + *ptr = '_'; + statusll = l + len + 1; + zmult = 1; + listlist(cmdll); + zmult = zmultsav; + } else if (!nextnode(firstnode(cmdll))) { + strcpy(ptr = cmdbuf, peekfirst(cmdll)); + ptr += (len = strlen(ptr)); + if(cmd == Th(z_acceptline) || cmd == Th(z_vicmdmode)) + goto unambiguous; + } else { + strcpy(cmdbuf, peekfirst(cmdll)); + ptr = cmdbuf + cmdambig; + *ptr = '_'; + if (isset(AUTOLIST) && + !(isset(LISTAMBIGUOUS) && cmdambig > len)) { + int zmultsav = zmult; + if (isset(LISTBEEP)) + feep(); + statusll = l + cmdambig + 1; + zmult = 1; + listlist(cmdll); + zmult = zmultsav; + } + len = cmdambig; + } + } else { + if (len == NAMLEN || icntrl(c) || cmd != Th(z_selfinsert)) + feep(); + else + *ptr++ = c, len++; + } + } + handlefeep(); + } +} + +/*****************/ +/* Suffix system */ +/*****************/ + +/* + * The completion system sometimes tentatively adds a suffix to a word, + * which can be removed depending on what is inserted next. These + * functions provide the capability to handle a removable suffix. + * + * Any removable suffix consists of characters immediately before the + * cursor. Whether it is removed depends on the next editing action. + * There can be more than one suffix simultaneously present, with + * different actions deleting different numbers of characters. + * + * If the next editing action changes the buffer other than by inserting + * characters, normally the suffix should be removed so as to leave a + * meaningful complete word. The behaviour should be the same if the + * next character inserted is a word separator. If the next character + * reasonably belongs where it is typed, or if the next editing action + * is a deletion, the suffix should not be removed. Other reasons for + * suffix removal may have other behaviour. + * + * In order to maintain a consistent state, after a suffix has been added + * the table *must* be zeroed, one way or another, before the buffer is + * changed. If the suffix is not being removed, call fixsuffix() to + * indicate that it is being permanently fixed. + */ + +/* Length of suffix to remove when inserting each possible character value. * + * suffixlen[256] is the length to remove for non-insertion editing actions. */ + +/**/ +int suffixlen[257]; + +/* Set up suffix: the last n characters are a suffix that should be * + * removed in the usual word end conditions. */ + +/**/ +void +makesuffix(int n) +{ + suffixlen[256] = suffixlen[' '] = suffixlen['\t'] = suffixlen['\n'] = n; +} + +/* Set up suffix for parameter names: the last n characters are a suffix * + * that should be removed if the next character is one of the ones that * + * needs to go immediately after the parameter name. br indicates that * + * the name is in braces (${PATH} instead of $PATH), so the extra * + * characters that can only be used in braces are included. */ + +/**/ +void +makeparamsuffix(int br, int n) +{ + if(br || unset(KSHARRAYS)) + suffixlen[':'] = suffixlen['['] = n; + if(br) { + suffixlen['#'] = suffixlen['%'] = suffixlen['?'] = n; + suffixlen['-'] = suffixlen['+'] = suffixlen['='] = n; + /*{*/ suffixlen['}'] = n; + } +} + +/* Remove suffix, if there is one, when inserting character c. */ + +/**/ +void +iremovesuffix(int c) +{ + int sl = suffixlen[c]; + if(sl) { + backdel(sl); + invalidatelist(); + } + fixsuffix(); +} + +/* Fix the suffix in place, if there is one, making it non-removable. */ + +/**/ +void +fixsuffix(void) +{ + memset(suffixlen, 0, sizeof(suffixlen)); +} diff --git a/Src/Zle/zle_move.c b/Src/Zle/zle_move.c new file mode 100644 index 000000000..8ed4c657a --- /dev/null +++ b/Src/Zle/zle_move.c @@ -0,0 +1,502 @@ +/* + * zle_move.c - editor movement + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "zle.mdh" +#include "zle_move.pro" + +static vimarkcs[27], vimarkline[27]; + +/**/ +void +beginningofline(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + endofline(); + zmult = n; + return; + } + while (n--) { + if (cs == 0) + return; + if (line[cs - 1] == '\n') + if (!--cs) + return; + while (cs && line[cs - 1] != '\n') + cs--; + } +} + +/**/ +void +endofline(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + beginningofline(); + zmult = n; + return; + } + while (n--) { + if (cs >= ll) { + cs = ll; + return; + } + if (line[cs] == '\n') + if (++cs == ll) + return; + while (cs != ll && line[cs] != '\n') + cs++; + } +} + +/**/ +void +beginningoflinehist(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + endoflinehist(); + zmult = n; + return; + } + while (n) { + if (cs == 0) + break; + if (line[cs - 1] == '\n') + if (!--cs) + break; + while (cs && line[cs - 1] != '\n') + cs--; + n--; + } + if (n) { + int m = zmult; + + zmult = n; + uphistory(); + zmult = m; + cs = 0; + } +} + +/**/ +void +endoflinehist(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + beginningoflinehist(); + zmult = n; + return; + } + while (n) { + if (cs >= ll) { + cs = ll; + break; + } + if (line[cs] == '\n') + if (++cs == ll) + break; + while (cs != ll && line[cs] != '\n') + cs++; + n--; + } + if (n) { + int m = zmult; + + zmult = n; + downhistory(); + zmult = m; + } +} + +/**/ +void +forwardchar(void) +{ + cs += zmult; + if (cs > ll) + cs = ll; + if (cs < 0) + cs = 0; +} + +/**/ +void +backwardchar(void) +{ + cs -= zmult; + if (cs > ll) + cs = ll; + if (cs < 0) + cs = 0; +} + +/**/ +void +setmarkcommand(void) +{ + mark = cs; +} + +/**/ +void +exchangepointandmark(void) +{ + int x; + + x = mark; + mark = cs; + cs = x; + if (cs > ll) + cs = ll; +} + +/**/ +void +vigotocolumn(void) +{ + int x, y; + + findline(&x, &y); + if (zmult >= 0) + cs = x + zmult - (zmult > 0); + else + cs = y + zmult; + if (cs > y) + cs = y; + if (cs < x) + cs = x; +} + +/**/ +void +vimatchbracket(void) +{ + int ocs = cs, dir, ct; + unsigned char oth, me; + + otog: + if (cs == ll || line[cs] == '\n') { + feep(); + cs = ocs; + return; + } + switch (me = line[cs]) { + case '{': + dir = 1; + oth = '}'; + break; + case /*{*/ '}': + virangeflag = -virangeflag; + dir = -1; + oth = '{'; /*}*/ + break; + case '(': + dir = 1; + oth = ')'; + break; + case ')': + virangeflag = -virangeflag; + dir = -1; + oth = '('; + break; + case '[': + dir = 1; + oth = ']'; + break; + case ']': + virangeflag = -virangeflag; + dir = -1; + oth = '['; + break; + default: + cs++; + goto otog; + } + ct = 1; + while (cs >= 0 && cs < ll && ct) { + cs += dir; + if (line[cs] == oth) + ct--; + else if (line[cs] == me) + ct++; + } + if (cs < 0 || cs >= ll) { + feep(); + cs = ocs; + } else if(dir > 0 && virangeflag) + cs++; +} + +/**/ +void +viforwardchar(void) +{ + int lim = findeol() - invicmdmode(); + int n = zmult; + + if (n < 0) { + zmult = -n; + vibackwardchar(); + zmult = n; + return; + } + if (cs >= lim) { + feep(); + return; + } + while (n-- && cs < lim) + cs++; +} + +/**/ +void +vibackwardchar(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + viforwardchar(); + zmult = n; + return; + } + if (cs == findbol()) { + feep(); + return; + } + while (n--) { + cs--; + if (cs < 0 || line[cs] == '\n') { + cs++; + break; + } + } +} + +/**/ +void +viendofline(void) +{ + int oldcs = cs, n = zmult; + + if (n < 1) { + feep(); + return; + } + while(n--) { + if (cs > ll) { + cs = oldcs; + feep(); + return; + } + cs = findeol() + 1; + } + cs--; + lastcol = 1<<30; +} + +/**/ +void +vibeginningofline(void) +{ + cs = findbol(); +} + +static int vfindchar, vfinddir, tailadd; + +/**/ +void +vifindnextchar(void) +{ + if ((vfindchar = vigetkey()) != -1) { + vfinddir = 1; + tailadd = 0; + virepeatfind(); + } +} + +/**/ +void +vifindprevchar(void) +{ + if ((vfindchar = vigetkey()) != -1) { + vfinddir = -1; + tailadd = 0; + virepeatfind(); + } +} + +/**/ +void +vifindnextcharskip(void) +{ + if ((vfindchar = vigetkey()) != -1) { + vfinddir = 1; + tailadd = -1; + virepeatfind(); + } +} + +/**/ +void +vifindprevcharskip(void) +{ + if ((vfindchar = vigetkey()) != -1) { + vfinddir = -1; + tailadd = 1; + virepeatfind(); + } +} + +/**/ +void +virepeatfind(void) +{ + int ocs = cs, n = zmult; + + if (!vfinddir) { + feep(); + return; + } + if (n < 0) { + zmult = -n; + virevrepeatfind(); + zmult = n; + return; + } + while (n--) { + do + cs += vfinddir; + while (cs >= 0 && cs < ll && line[cs] != vfindchar && line[cs] != '\n'); + if (cs < 0 || cs >= ll || line[cs] == '\n') { + feep(); + cs = ocs; + return; + } + } + cs += tailadd; + if (vfinddir == 1 && virangeflag) + cs++; +} + +/**/ +void +virevrepeatfind(void) +{ + if (zmult < 0) { + zmult = -zmult; + virepeatfind(); + zmult = -zmult; + return; + } + vfinddir = -vfinddir; + virepeatfind(); + vfinddir = -vfinddir; +} + +/**/ +void +vifirstnonblank(void) +{ + cs = findbol(); + while (cs != ll && iblank(line[cs])) + cs++; +} + +/**/ +void +visetmark(void) +{ + int ch; + + ch = getkey(0); + if (ch < 'a' || ch > 'z') { + feep(); + return; + } + ch -= 'a'; + vimarkcs[ch] = cs; + vimarkline[ch] = histline; +} + +/**/ +void +vigotomark(void) +{ + int ch; + + ch = getkey(0); + if (ch == c) + ch = 26; + else { + if (ch < 'a' || ch > 'z') { + feep(); + return; + } + ch -= 'a'; + } + if (!vimarkline[ch]) { + feep(); + return; + } + if (curhist != vimarkline[ch]) { + char *s; + + remember_edits(); + if (!(s = qgetevent(vimarkline[ch]))) { + vimarkline[ch] = 0; + feep(); + return; + } + histline = vimarkline[ch]; + setline(s); + } + cs = vimarkcs[ch]; + if (cs > ll) + cs = ll; +} + +/**/ +void +vigotomarkline(void) +{ + vigotomark(); + vifirstnonblank(); +} diff --git a/Src/Zle/zle_params.c b/Src/Zle/zle_params.c new file mode 100644 index 000000000..ed1420829 --- /dev/null +++ b/Src/Zle/zle_params.c @@ -0,0 +1,196 @@ +/* + * zle_params.c - ZLE special parameters + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "zle.mdh" + +#include "zle_params.pro" + +/* + * ZLE SPECIAL PARAMETERS: + * + * These special parameters are created, with a local scope, when + * running user-defined widget functions. Reading and writing them + * reads and writes bits of ZLE state. The parameters are: + * + * BUFFER (scalar) entire buffer contents + * CURSOR (integer) cursor position; 0 <= $CURSOR <= $#BUFFER + * LBUFFER (scalar) portion of buffer to the left of the cursor + * RBUFFER (scalar) portion of buffer to the right of the cursor + */ + +#define FN(X) ( (void (*) _((void))) (X) ) +static struct zleparam { + char *name; + int type; + void (*setfn) _((void)); + void (*getfn) _((void)); + void (*unsetfn) _((Param, int)); + void *data; +} zleparams[] = { + { "BUFFER", PM_SCALAR, FN(set_buffer), FN(get_buffer), + zleunsetfn, NULL }, + { "CURSOR", PM_INTEGER, FN(set_cursor), FN(get_cursor), + zleunsetfn, NULL }, + { "LBUFFER", PM_SCALAR, FN(set_lbuffer), FN(get_lbuffer), + zleunsetfn, NULL }, + { "RBUFFER", PM_SCALAR, FN(set_rbuffer), FN(get_rbuffer), + zleunsetfn, NULL }, + { NULL, 0, NULL, NULL, NULL, NULL } +}; + +/**/ +void +makezleparams(void) +{ + struct zleparam *zp; + + for(zp = zleparams; zp->name; zp++) { + Param pm = createparam(zp->name, zp->type | PM_SPECIAL); + + pm->level = locallevel; + pm->u.data = zp->data; + switch(PM_TYPE(zp->type)) { + case PM_SCALAR: + pm->sets.cfn = (void (*) _((Param, char *))) zp->setfn; + pm->gets.cfn = (char *(*) _((Param))) zp->getfn; + break; + case PM_ARRAY: + pm->sets.afn = (void (*) _((Param, char **))) zp->setfn; + pm->gets.afn = (char **(*) _((Param))) zp->getfn; + break; + case PM_INTEGER: + pm->sets.ifn = (void (*) _((Param, long))) zp->setfn; + pm->gets.ifn = (long (*) _((Param))) zp->getfn; + break; + } + pm->unsetfn = zp->unsetfn; + } +} + +/* Special unset function for ZLE special parameters: act like the standard * + * unset function if this is a user-initiated unset, but nothing is done if * + * the parameter is merely going out of scope (which it will do). */ + +/**/ +static void +zleunsetfn(Param pm, int exp) +{ + if(exp) + stdunsetfn(pm, exp); +} + +/**/ +static void +set_buffer(Param pm, char *x) +{ + if(x) { + unmetafy(x, &ll); + sizeline(ll); + strcpy((char *)line, x); + zsfree(x); + if(cs > ll) + cs = ll; + } else + cs = ll = 0; +} + +/**/ +static char * +get_buffer(Param pm) +{ + return metafy((char *)line, ll, META_HEAPDUP); +} + +/**/ +static void +set_cursor(Param pm, long x) +{ + if(x < 0) + cs = 0; + else if(x > ll) + cs = ll; + else + cs = x; +} + +/**/ +static long +get_cursor(Param pm) +{ + return cs; +} + +/**/ +static void +set_lbuffer(Param pm, char *x) +{ + char *y; + int len; + + if(x) + unmetafy(y = x, &len); + else + y = "", len = 0; + sizeline(ll - cs + len); + memmove(line + len, line + cs, ll - cs); + memcpy(line, y, len); + ll = ll - cs + len; + cs = len; + zsfree(x); +} + +/**/ +static char * +get_lbuffer(Param pm) +{ + return metafy((char *)line, cs, META_HEAPDUP); +} + +/**/ +static void +set_rbuffer(Param pm, char *x) +{ + char *y; + int len; + + if(x) + unmetafy(y = x, &len); + else + y = "", len = 0; + sizeline(ll = cs + len); + memcpy(line + cs, y, len); + zsfree(x); +} + +/**/ +static char * +get_rbuffer(Param pm) +{ + return metafy((char *)line + cs, ll - cs, META_HEAPDUP); +} diff --git a/Src/Zle/zle_refresh.c b/Src/Zle/zle_refresh.c new file mode 100644 index 000000000..4621b5124 --- /dev/null +++ b/Src/Zle/zle_refresh.c @@ -0,0 +1,1116 @@ +/* + * zle_refresh.c - screen update + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "zle.mdh" +#include "zle_refresh.pro" + +/* Expanded prompts */ + +/**/ +char *lpptbuf, *rpptbuf; + +/* Text attributes after displaying prompts */ + +/**/ +unsigned pmpt_attr, rpmpt_attr; + +/* number of lines displayed */ + +/**/ +int nlnct; + +/* Most lines of the buffer we've shown at once with the current list * + * showing. == 0 if there is no list. == -1 if a new list has just * + * been put on the screen. == -2 if refresh() needs to put up a new * + * list. */ + +/**/ +int showinglist; + +/* Non-zero if ALWAYS_LAST_PROMPT has been used, meaning that the * + * screen below the buffer display should not be cleared by * + * refresh(), but should be by trashzle(). */ + +/**/ +int clearflag; + +#ifdef HAVE_SELECT +/* cost of last update */ +/**/ +int cost; + +# define SELECT_ADD_COST(X) cost += X +# define zputc(a, b) putc(a, b), cost++ +# define zwrite(a, b, c, d) fwrite(a, b, c, d), cost += (b * c) +#else +# define SELECT_ADD_COST(X) +# define zputc(a, b) putc(a, b) +# define zwrite(a, b, c, d) fwrite(a, b, c, d) +#endif + +/* Oct/Nov 94: some code savagely redesigned to fix several bugs - + refreshline() & tc_rightcurs() majorly rewritten; refresh() fixed - + I've put my fingers into just about every routine in here - + any queries about updates to mason@werple.net.au */ + +static char **nbuf = NULL, /* new video buffer line-by-line char array */ + **obuf = NULL; /* old video buffer line-by-line char array */ +static int more_start, /* more text before start of screen? */ + more_end, /* more stuff after end of screen? */ + lppth, /* lines taken up by the prompt */ + olnct, /* previous number of lines */ + ovln, /* previous video cursor position line */ + pptw, rpw, /* prompt widths on screen */ + rppth, /* right prompt height */ + vcs, vln, /* video cursor position column & line */ + vmaxln, /* video maximum number of lines */ + winw, winh, rwinh, /* window width & height */ + winpos; /* singlelinezle: line's position in window */ + +/**/ +void +resetvideo(void) +{ + int ln; + static int lwinw = -1, lwinh = -1; /* last window width & height */ + + genprompts(); + winw = columns; /* terminal width */ + if (termflags & TERM_SHORT) + winh = 1; + else + winh = (lines < 2) ? 24 : lines; + rwinh = lines; /* keep the real number of lines */ + winpos = vln = vmaxln = 0; + if (lwinw != winw || lwinh != winh) { + if (nbuf) { + for (ln = 0; ln != lwinh; ln++) { + zfree(nbuf[ln], lwinw + 2); + zfree(obuf[ln], lwinw + 2); + } + free(nbuf); + free(obuf); + } + nbuf = (char **)zcalloc((winh + 1) * sizeof(char *)); + obuf = (char **)zcalloc((winh + 1) * sizeof(char *)); + nbuf[0] = (char *)zalloc(winw + 2); + obuf[0] = (char *)zalloc(winw + 2); + + lwinw = winw; + lwinh = winh; + } + for (ln = 0; ln != winh + 1; ln++) { + if (nbuf[ln]) + *nbuf[ln] = '\0'; + if (obuf[ln]) + *obuf[ln] = '\0'; + } + + if (pptw) { + memset(nbuf[0], ' ', pptw); + memset(obuf[0], ' ', pptw); + nbuf[0][pptw] = obuf[0][pptw] = '\0'; + } + + vcs = pptw; + olnct = nlnct = 0; + if (showinglist > 0) + showinglist = -2; +} + +/* + * Nov 96: changed to single line scroll + */ + +/**/ +static void +scrollwindow(int tline) +{ + int t0; + char *s; + + s = nbuf[tline]; + for (t0 = tline; t0 < winh - 1; t0++) + nbuf[t0] = nbuf[t0 + 1]; + nbuf[winh - 1] = s; + if (!tline) + more_start = 1; + return; +} + +/* this is the messy part. */ +/* this define belongs where it's used!!! */ + +#define nextline \ +{ \ + *s = '\0'; \ + if (ln != winh - 1) \ + ln++; \ + else { \ + if (!canscroll) { \ + if (nvln != -1 && nvln != winh - 1 \ + && (numscrolls != onumscrolls - 1 \ + || nvln <= winh / 2)) \ + break; \ + numscrolls++; \ + canscroll = winh / 2; \ + } \ + canscroll--; \ + scrollwindow(0); \ + if (nvln != -1) \ + nvln--; \ + } \ + if (!nbuf[ln]) \ + nbuf[ln] = (char *)zalloc(winw + 2); \ + s = (unsigned char *)nbuf[ln]; \ + sen = s + winw; \ +} + +#define snextline \ +{ \ + *s = '\0'; \ + if (ln != winh - 1) \ + ln++; \ + else \ + if (tosln < 3) { \ + more_status = 1; \ + scrollwindow(tosln + 1); \ + } else if (tosln - 1 <= nvln) { \ + scrollwindow(0); \ + if (nvln) \ + nvln--, tosln--; \ + } else { \ + tosln--; \ + scrollwindow(tosln); \ + } \ + if (!nbuf[ln]) \ + nbuf[ln] = (char *)zalloc(winw + 2); \ + s = (unsigned char *)nbuf[ln]; \ + sen = s + winw; \ +} + +static int cleareol, /* clear to end-of-line (if can't cleareod) */ + clearf, /* alwayslastprompt used immediately before */ + put_rpmpt, /* whether we should display right-prompt */ + oput_rpmpt, /* whether displayed right-prompt last time */ + oxtabs, /* oxtabs - tabs expand to spaces if set */ + numscrolls, onumscrolls; + +/**/ +void +refresh(void) +{ + static int inlist; /* avoiding recursion */ + int canscroll = 0, /* number of lines we are allowed to scroll */ + ln = 0, /* current line we're working on */ + more_status = 0, /* more stuff in status line */ + nvcs = 0, nvln = -1, /* video cursor column and line */ + t0 = -1, /* tmp */ + tosln = 0; /* tmp in statusline stuff */ + unsigned char *s, /* pointer into the video buffer */ + *t, /* pointer into the real buffer */ + *sen, /* pointer to end of the video buffer (eol) */ + *scs; /* pointer to cursor position in real buffer */ + char **qbuf; /* tmp */ + + /* If this is called from listmatches() (indirectly via trashzle()), and * + * that was called from the end of refresh(), then we don't need to do * + * anything. All this `inlist' code is actually unnecessary, but it * + * improves speed a little in a common case. */ + if (inlist) + return; + +#ifdef HAVE_SELECT + cost = 0; /* reset */ +#endif + +/* Nov 96: I haven't checked how complete this is. sgtty stuff may + or may not work */ + oxtabs = ((SGTTYFLAG & SGTABTYPE) == SGTABTYPE); + + cleareol = 0; /* unset */ + more_start = more_end = 0; /* unset */ + if (isset(SINGLELINEZLE) || lines < 3 + || (termflags & (TERM_NOUP | TERM_BAD | TERM_UNKNOWN))) + termflags |= TERM_SHORT; + else + termflags &= ~TERM_SHORT; + if (resetneeded) { + onumscrolls = 0; + setterm(); +#ifdef TIOCGWINSZ + if (winchanged) { + moveto(0, 0); + t0 = olnct; /* this is to clear extra lines even when */ + winchanged = 0; /* the terminal cannot TCCLEAREOD */ + } +#endif + resetvideo(); + resetneeded = 0; /* unset */ + oput_rpmpt = 0; /* no right-prompt currently on screen */ + + /* we probably should only have explicitly set attributes */ + tsetcap(TCALLATTRSOFF, 0); + tsetcap(TCSTANDOUTEND, 0); + tsetcap(TCUNDERLINEEND, 0); + + if (!clearflag) + if (tccan(TCCLEAREOD)) + tcout(TCCLEAREOD); + else + cleareol = 1; /* request: clear to end of line */ + if (t0 > -1) + olnct = t0; + if (termflags & TERM_SHORT) + vcs = 0; + else if (!clearflag && lpptbuf[0]) + zputs(lpptbuf, shout); + if (clearflag) { + zputc('\r', shout); + vcs = 0; + moveto(0, pptw); + } + fflush(shout); + clearf = clearflag; + } else if (winw != columns || rwinh != lines) + resetvideo(); + +/* now winw equals columns and winh equals lines + width comparisons can be made with winw, height comparisons with winh */ + + if (termflags & TERM_SHORT) { + singlerefresh(); + return; + } + + if (cs < 0) { +#ifdef DEBUG + fprintf(stderr, "BUG: negative cursor position\n"); + fflush(stderr); +#endif + cs = 0; + } + scs = line + cs; + numscrolls = 0; + +/* first, we generate the video line buffers so we know what to put on + the screen - also determine final cursor position (nvln, nvcs) */ + + /* Deemed necessary by PWS 1995/05/15 due to kill-line problems */ + if (!*nbuf) + *nbuf = (char *)zalloc(winw + 2); + + s = (unsigned char *)(nbuf[ln = 0] + pptw); + t = line; + sen = (unsigned char *)(*nbuf + winw); + for (; t < line+ll; t++) { + if (t == scs) /* if cursor is here, remember it */ + nvcs = s - (unsigned char *)(nbuf[nvln = ln]); + + if (*t == '\n') { /* newline */ + nbuf[ln][winw + 1] = '\0'; /* text not wrapped */ + nextline + } else if (*t == '\t') { /* tab */ + t0 = (char *)s - nbuf[ln]; + if ((t0 | 7) + 1 >= winw) { + nbuf[ln][winw + 1] = '\n'; /* text wrapped */ + nextline + } else + do + *s++ = ' '; + while ((++t0) & 7); + } else if (icntrl(*t)) { /* other control character */ + *s++ = '^'; + if (s == sen) { + nbuf[ln][winw + 1] = '\n'; /* text wrapped */ + nextline + } + *s++ = (*t == 127) ? '?' : (*t | '@'); + } else /* normal character */ + *s++ = *t; + if (s == sen) { + nbuf[ln][winw + 1] = '\n'; /* text wrapped */ + nextline + } + } + +/* if we're really on the next line, don't fake it; do everything properly */ + if (t == scs && (nvcs = s - (unsigned char *)(nbuf[nvln = ln])) == winw) { + nbuf[ln][winw + 1] = '\n'; /* text wrapped */ + switch ('\0') { /* a sad hack to make the break */ + case '\0': /* in nextline work */ + nextline + } + *s = '\0'; + nvcs = 0; + nvln++; + } + + if (t != line + ll) + more_end = 1; + + if (statusline) { + tosln = ln + 1; + if (ln == winh - 1) { + if (nvln > 0) { + scrollwindow(0); + nvln--; + } + tosln--; + } + nbuf[ln][winw + 1] = '\0'; /* text not wrapped */ + snextline + t = (unsigned char *)statusline; + for (; t < (unsigned char *)statusline + statusll; t++) { + if (icntrl(*t)) { /* simplified processing in the status line */ + *s++ = '^'; + if (s == sen) { + nbuf[ln][winw + 1] = '\n'; /* text wrapped */ + snextline + } + *s++ = (*t == 127) ? '?' : (*t | '@'); + } else + *s++ = *t; + if (s == sen) { + nbuf[ln][winw + 1] = '\n'; /* text wrapped */ + snextline + } + } + } + +/* insert <.... at end of last line if there is more text past end of screen */ + if (more_end) { + if (!statusline) + tosln = winh; + strncpy(nbuf[tosln - 1] + winw - 7, " <.... ", 7); + nbuf[tosln - 1][winw] = nbuf[tosln - 1][winw + 1] = '\0'; + } + +/* insert <....> at end of first status line if status is too big */ + if (more_status) { + strncpy(nbuf[tosln] + winw - 8, " <....> ", 8); + nbuf[tosln][winw] = nbuf[tosln][winw + 1] = '\0'; + } + + *s = '\0'; + nlnct = ln + 1; + for (ln = nlnct; ln < winh; ln++) + zfree(nbuf[ln], winw + 2), nbuf[ln] = NULL; + +/* determine whether the right-prompt exists and can fit on the screen */ + if (!more_start) + put_rpmpt = rppth == 1 && rpptbuf[0] && !strchr(rpptbuf, '\t') && + (int)strlen(nbuf[0]) + rpw < winw - 1; + else { +/* insert >.... on first line if there is more text before start of screen */ + memset(nbuf[0], ' ', pptw); + t0 = winw - pptw; + t0 = t0 > 5 ? 5 : t0; + strncpy(nbuf[0] + pptw, ">....", t0); + memset(nbuf[0] + pptw + t0, ' ', winw - t0 - pptw); + nbuf[0][winw] = nbuf[0][winw + 1] = '\0'; + } + + for (ln = 0; !clearf && (ln < nlnct); ln++) { + /* if we have more lines than last time, clear the newly-used lines */ + if (ln >= olnct) + cleareol = 1; + + /* if old line and new line are different, + see if we can insert/delete a line to speed up update */ + + if (ln < olnct - 1 && !(hasam && vcs == winw) && + nbuf[ln] && obuf[ln] && + strncmp(nbuf[ln], obuf[ln], 16)) { + if (tccan(TCDELLINE) && obuf[ln + 1] && obuf[ln + 1][0] && + nbuf[ln] && !strncmp(nbuf[ln], obuf[ln + 1], 16)) { + moveto(ln, 0); + tcout(TCDELLINE); + zfree(obuf[ln], winw + 2); + for (t0 = ln; t0 != olnct; t0++) + obuf[t0] = obuf[t0 + 1]; + obuf[--olnct] = NULL; + } + /* don't try to insert a line if olnct = vmaxln (vmaxln is the number + of lines that have been displayed by this routine) so that we don't + go off the end of the screen. */ + + else if (tccan(TCINSLINE) && olnct < vmaxln && nbuf[ln + 1] && + obuf[ln] && !strncmp(nbuf[ln + 1], obuf[ln], 16)) { + moveto(ln, 0); + tcout(TCINSLINE); + for (t0 = olnct; t0 != ln; t0--) + obuf[t0] = obuf[t0 - 1]; + obuf[ln] = NULL; + olnct++; + } + } + + /* update the single line */ + refreshline(ln); + + /* output the right-prompt if appropriate */ + if (put_rpmpt && !ln && !oput_rpmpt) { + moveto(0, winw - 1 - rpw); + zputs(rpptbuf, shout); + vcs = winw - 1; + /* reset character attributes to that set by the main prompt */ + txtchange = pmpt_attr; + if (txtchangeisset(TXTNOBOLDFACE) && (rpmpt_attr & TXTBOLDFACE)) + tsetcap(TCALLATTRSOFF, 0); + if (txtchangeisset(TXTNOSTANDOUT) && (rpmpt_attr & TXTSTANDOUT)) + tsetcap(TCSTANDOUTEND, 0); + if (txtchangeisset(TXTNOUNDERLINE) && (rpmpt_attr & TXTUNDERLINE)) + tsetcap(TCUNDERLINEEND, 0); + if (txtchangeisset(TXTBOLDFACE) && (rpmpt_attr & TXTNOBOLDFACE)) + tsetcap(TCBOLDFACEBEG, 0); + if (txtchangeisset(TXTSTANDOUT) && (rpmpt_attr & TXTNOSTANDOUT)) + tsetcap(TCSTANDOUTBEG, 0); + if (txtchangeisset(TXTUNDERLINE) && (rpmpt_attr & TXTNOUNDERLINE)) + tsetcap(TCUNDERLINEBEG, 0); + } + } + +/* if old buffer had extra lines, set them to be cleared and refresh them +individually */ + + if (olnct > nlnct) { + cleareol = 1; + for (ln = nlnct; ln < olnct; ln++) + refreshline(ln); + } + +/* reset character attributes */ + if (clearf && postedit) { + if ((txtchange = pmpt_attr ? pmpt_attr : rpmpt_attr)) { + if (txtchangeisset(TXTNOBOLDFACE)) + tsetcap(TCALLATTRSOFF, 0); + if (txtchangeisset(TXTNOSTANDOUT)) + tsetcap(TCSTANDOUTEND, 0); + if (txtchangeisset(TXTNOUNDERLINE)) + tsetcap(TCUNDERLINEEND, 0); + if (txtchangeisset(TXTBOLDFACE)) + tsetcap(TCBOLDFACEBEG, 0); + if (txtchangeisset(TXTSTANDOUT)) + tsetcap(TCSTANDOUTBEG, 0); + if (txtchangeisset(TXTUNDERLINE)) + tsetcap(TCUNDERLINEBEG, 0); + } + } + clearf = 0; + +/* move to the new cursor position */ + moveto(nvln, nvcs); + +/* swap old and new buffers - better than freeing/allocating every time */ + qbuf = nbuf; + nbuf = obuf; + obuf = qbuf; +/* store current values so we can use them next time */ + ovln = nvln; + olnct = nlnct; + oput_rpmpt = put_rpmpt; + onumscrolls = numscrolls; + if (nlnct > vmaxln) + vmaxln = nlnct; + fflush(shout); /* make sure everything is written out */ + + /* if we have a new list showing, note it; if part of the list has been + overwritten, redisplay it. */ + if (showinglist == -2 || (showinglist > 0 && showinglist < nlnct)) { + inlist = 1; + listmatches(); + inlist = 0; + refresh(); + } + if (showinglist == -1) + showinglist = nlnct; +} + +#define tcinscost(X) (tccan(TCMULTINS) ? tclen[TCMULTINS] : (X)*tclen[TCINS]) +#define tcdelcost(X) (tccan(TCMULTDEL) ? tclen[TCMULTDEL] : (X)*tclen[TCDEL]) +#define tc_delchars(X) (void) tcmultout(TCDEL, TCMULTDEL, (X)) +#define tc_inschars(X) (void) tcmultout(TCINS, TCMULTINS, (X)) +#define tc_upcurs(X) (void) tcmultout(TCUP, TCMULTUP, (X)) +#define tc_leftcurs(X) (void) tcmultout(TCLEFT, TCMULTLEFT, (X)) + +/* refresh one line, using whatever speed-up tricks are provided by the tty */ + +/**/ +static void +refreshline(int ln) +{ + char *nl, *ol, *p1; /* line buffer pointers */ + int ccs = 0, /* temporary count for cursor position */ + char_ins = 0, /* number of characters inserted/deleted */ + col_cleareol, /* clear to end-of-line from this column */ + i, j, /* tmp */ + ins_last, /* insert pushed last character off line */ + nllen, ollen, /* new and old line buffer lengths */ + rnllen; /* real new line buffer length */ + +/* 0: setup */ + nl = nbuf[ln]; + rnllen = nllen = nl ? strlen(nl) : 0; + ol = obuf[ln] ? obuf[ln] : ""; + ollen = strlen(ol); + +/* optimisation: can easily happen for clearing old lines. If the terminal has + the capability, then this is the easiest way to skip unnecessary stuff */ + if (cleareol && !nllen && !(hasam && ln < nlnct - 1) + && tccan(TCCLEAREOL)) { + moveto(ln, 0); + tcout(TCCLEAREOL); + return; + } + +/* 1: pad out the new buffer with spaces to contain _all_ of the characters + which need to be written. do this now to allow some pre-processing */ + + if (cleareol /* request to clear to end of line */ + || !nllen /* no line buffer given */ + || (ln == 0 && (put_rpmpt != oput_rpmpt))) { /* prompt changed */ + p1 = halloc(winw + 2); + if (nllen) + strncpy(p1, nl, nllen); + memset(p1 + nllen, ' ', winw - nllen); + p1[winw] = '\0'; + p1[winw + 1] = (nllen < winw) ? '\0' : nl[winw + 1]; + if (ln && nbuf[ln]) + memcpy(nl, p1, winw + 2); /* next time obuf will be up-to-date */ + else + nl = p1; /* don't keep the padding for prompt line */ + nllen = winw; + } else if (ollen > nllen) { /* make new line at least as long as old */ + p1 = halloc(ollen + 1); + strncpy(p1, nl, nllen); + memset(p1 + nllen, ' ', ollen - nllen); + p1[ollen] = '\0'; + nl = p1; + nllen = ollen; + } + +/* 2: see if we can clear to end-of-line, and if it's faster, work out where + to do it from - we can normally only do so if there's no right-prompt. + With automatic margins, we shouldn't do it if there is another line, in + case it messes up cut and paste. */ + + if (hasam && ln < nlnct - 1 && rnllen == winw) + col_cleareol = -2; /* clearing eol would be evil so don't */ + else { + col_cleareol = -1; + if (tccan(TCCLEAREOL) && (nllen == winw || put_rpmpt != oput_rpmpt)) { + for (i = nllen; i && nl[i - 1] == ' '; i--); + for (j = ollen; j && ol[j - 1] == ' '; j--); + if ((j > i + tclen[TCCLEAREOL]) /* new buf has enough spaces */ + || (nllen == winw && nl[winw - 1] == ' ')) + col_cleareol = i; + } + } + +/* 2b: first a new trick for automargin niceness - good for cut and paste */ + + if (hasam && vcs == winw) { + if (nbuf[vln] && nbuf[vln][vcs + 1] == '\n') { + vln++, vcs = 1; + if (nbuf[vln] && *nbuf[vln]) + zputc(*nbuf[vln], shout); + else + zputc(' ', shout); /* I don't think this should happen */ + if (ln == vln) { /* better safe than sorry */ + nl++; + if (*ol) + ol++; + ccs = 1; + } /* else hmmm... I wonder what happened */ + } else { + vln++, vcs = 0; + zputc('\n', shout); + } + } + ins_last = 0; + +/* 2c: if we're on the first line, start checking at the end of the prompt; + we shouldn't be doing anything within the prompt */ + + if (ln == 0 && pptw) { + i = pptw - ccs; + j = strlen(ol); + nl += i; + ol += (i > j ? j : i); /* if ol is too short, point it to '\0' */ + ccs = pptw; + } + +/* 3: main display loop - write out the buffer using whatever tricks we can */ + + for (;;) { + if (*nl && *ol && nl[1] == ol[1]) /* skip only if second chars match */ + /* skip past all matching characters */ + for (; *nl && (*nl == *ol); nl++, ol++, ccs++) ; + + if (!*nl) { + if (ccs == winw && hasam && char_ins > 0 && ins_last + && vcs != winw) { + nl--; /* we can assume we can go back here */ + moveto(ln, winw - 1); + zputc(*nl, shout); + vcs++; + return; /* write last character in line */ + } + if ((char_ins <= 0) || (ccs >= winw)) /* written everything */ + return; + if (tccan(TCCLEAREOL) && (char_ins >= tclen[TCCLEAREOL]) + && col_cleareol != -2) + /* we've got junk on the right yet to clear */ + col_cleareol = 0; /* force a clear to end of line */ + } + + moveto(ln, ccs); /* move to where we do all output from */ + + /* if we can finish quickly, do so */ + if ((col_cleareol >= 0) && (ccs >= col_cleareol)) { + tcout(TCCLEAREOL); + return; + } + + /* we've written out the new but yet to clear rubbish due to inserts */ + if (!*nl) { + i = (winw - ccs < char_ins) ? (winw - ccs) : char_ins; + if (tccan(TCDEL) && (tcdelcost(i) <= i + 1)) + tc_delchars(i); + else { + vcs += i; + while (i-- > 0) + zputc(' ', shout); + } + return; + } + + /* if we've reached the end of the old buffer, then there are few tricks + we can do, so we just dump out what we must and clear if we can */ + if (!*ol) { + i = (col_cleareol >= 0) ? col_cleareol : nllen; + i -= vcs; + zwrite(nl, i, 1, shout); + vcs += i; + if (col_cleareol >= 0) + tcout(TCCLEAREOL); + return; + } + + /* inserting & deleting chars: we can if there's no right-prompt */ + if ((ln || !put_rpmpt || !oput_rpmpt) + && (nl[1] && ol[1] && nl[1] != ol[1])) { + + /* deleting characters - see if we can find a match series that + makes it cheaper to delete intermediate characters + eg. oldline: hifoobar \ hopefully cheaper here to delete two + newline: foobar / characters, then we have six matches */ + if (tccan(TCDEL)) { + for (i = 1; *(ol + i); i++) + if (tcdelcost(i) < pfxlen(ol + i, nl)) { + tc_delchars(i); + ol += i; + char_ins -= i; + i = 0; + break; + } + if (!i) + continue; + } + /* inserting characters - characters pushed off the right should be + annihilated, but we don't do this if we're on the last line lest + undesired scrolling occurs due to `illegal' characters on screen */ + + if (tccan(TCINS) && (vln != lines - 1)) { /* not on last line */ + for (i = 1; *(nl + i); i++) + if (tcinscost(i) < pfxlen(nl + i, ol)) { + tc_inschars(i); + zwrite(nl, i, 1, shout); + nl += i; + char_ins += i; + ccs = (vcs += i); + /* if we've pushed off the right, truncate oldline */ + for (i = 0; *(ol + i) && i < winw - ccs; i++); + if (i == winw - ccs) { + *(ol + i) = '\0'; + ins_last = 1; + } + i = 0; + break; + } + if (!i) + continue; + } + } + /* we can't do any fancy tricks, so just dump the single character + and keep on trying */ + zputc(*nl, shout); + nl++, ol++; + ccs++, vcs++; + } +} + +/* move the cursor to line ln (relative to the prompt line), + absolute column cl; update vln, vcs - video line and column */ + +/**/ +void +moveto(int ln, int cl) +{ + int c; + + if (vcs == winw) { + vln++, vcs = 0; + if (!hasam) { + zputc('\r', shout); + zputc('\n', shout); + } else { + if ((vln < nlnct) && nbuf[vln] && *nbuf[vln]) + c = *nbuf[vln]; + else + c = ' '; + zputc(c, shout); + zputc('\r', shout); + if ((vln < olnct) && obuf[vln] && *obuf[vln]) + *obuf[vln] = c; + } + } + + if (ln == vln && cl == vcs) + return; + +/* move up */ + if (ln < vln) { + tc_upcurs(vln - ln); + vln = ln; + } +/* move down; if we might go off the end of the screen, use newlines + instead of TCDOWN */ + + while (ln > vln) { + if (vln < vmaxln - 1) + if (ln > vmaxln - 1) { + if (tc_downcurs(vmaxln - 1 - vln)) + vcs = 0; + vln = vmaxln - 1; + } else { + if (tc_downcurs(ln - vln)) + vcs = 0; + vln = ln; + continue; + } + zputc('\r', shout), vcs = 0; /* safety precaution */ + while (ln > vln) { + zputc('\n', shout); + vln++; + } + } + + if (cl == vcs) + return; + +/* choose cheapest movements for ttys without multiple movement capabilities - + do this now because it's easier (to code) */ + if (cl <= vcs / 2) { + zputc('\r', shout); + vcs = 0; + } + if (vcs < cl) + tc_rightcurs(cl); + else if (vcs > cl) + tc_leftcurs(vcs - cl); + vcs = cl; +} + +/**/ +int +tcmultout(int cap, int multcap, int ct) +{ + if (tccan(multcap) && (!tccan(cap) || tclen[multcap] <= tclen[cap] * ct)) { + tcoutarg(multcap, ct); + return 1; + } else if (tccan(cap)) { + while (ct--) + tcout(cap); + return 1; + } + return 0; +} + +/**/ +static void +tc_rightcurs(int cl) +{ + int ct, /* number of characters to move across */ + i = vcs, /* cursor position after initial movements */ + j; + char *t; + + ct = cl - vcs; + +/* do a multright if we can - it's the most reliable */ + if (tccan(TCMULTRIGHT)) { + tcoutarg(TCMULTRIGHT, ct); + return; + } + +/* try tabs if tabs are non destructive and multright is not possible */ + if (!oxtabs && tccan(TCNEXTTAB) && ((vcs | 7) < cl)) { + i = (vcs | 7) + 1; + tcout(TCNEXTTAB); + for ( ; i + 8 <= cl; i += 8) + tcout(TCNEXTTAB); + if ((ct = cl - i) == 0) /* number of chars still to move across */ + return; + } + +/* otherwise _carefully_ write the contents of the video buffer. + if we're anywhere in the prompt, goto the left column and write the whole + prompt out unless ztrlen(lpptbuf) == pptw : we can cheat then */ + if (vln == 0 && i < pptw) { + if (strlen(lpptbuf) == pptw) + fputs(lpptbuf + i, shout); + else if (tccan(TCRIGHT) && (tclen[TCRIGHT] * ct <= ztrlen(lpptbuf))) + /* it is cheaper to send TCRIGHT than reprint the whole prompt */ + for (ct = pptw - i; ct--; ) + tcout(TCRIGHT); + else { + if (i != 0) + zputc('\r', shout); + tc_upcurs(lppth - 1); + zputs(lpptbuf, shout); + } + i = pptw; + ct = cl - i; + } + + if (nbuf[vln]) { + for (j = 0, t = nbuf[vln]; *t && (j < i); j++, t++); + if (j == i) + for ( ; *t && ct; ct--, t++) + zputc(*t, shout); + } + while (ct--) + zputc(' ', shout); /* not my fault your terminal can't go right */ +} + +/**/ +static int +tc_downcurs(int ct) +{ + int ret = 0; + + if (ct && !tcmultout(TCDOWN, TCMULTDOWN, ct)) { + while (ct--) + zputc('\n', shout); + zputc('\r', shout), ret = -1; + } + return ret; +} + +/**/ +void +tcout(int cap) +{ + tputs(tcstr[cap], 1, putshout); + SELECT_ADD_COST(tclen[cap]); +} + +/**/ +static void +tcoutarg(int cap, int arg) +{ + char *result; + + result = tgoto(tcstr[cap], arg, arg); + tputs(result, 1, putshout); + SELECT_ADD_COST(strlen(result)); +} + +/**/ +void +clearscreen(void) +{ + tcout(TCCLEARSCREEN); + resetneeded = 1; + clearflag = 0; +} + +/**/ +void +redisplay(void) +{ + moveto(0, 0); + zputc('\r', shout); /* extra care */ + tc_upcurs(lppth - 1); + resetneeded = 1; + clearflag = 0; +} + +/**/ +static void +singlerefresh(void) +{ + char *vbuf, *vp, /* video buffer and pointer */ + **qbuf, /* tmp */ + *refreshop = *obuf; /* pointer to old video buffer */ + int t0, /* tmp */ + vsiz, /* size of new video buffer */ + nvcs = 0; /* new video cursor column */ + + nlnct = 1; +/* generate the new line buffer completely */ + for (vsiz = 1 + pptw, t0 = 0; t0 != ll; t0++, vsiz++) + if (line[t0] == '\t') + vsiz = (vsiz | 7) + 1; + else if (icntrl(line[t0])) + vsiz++; + vbuf = (char *)zalloc(vsiz); + + if (cs < 0) { +#ifdef DEBUG + fprintf(stderr, "BUG: negative cursor position\n"); + fflush(stderr); +#endif + cs = 0; + } + + memcpy(vbuf, strchr(lpptbuf, 0) - pptw, pptw); /* only use last part of prompt */ + vbuf[pptw] = '\0'; + vp = vbuf + pptw; + + for (t0 = 0; t0 != ll; t0++) { + if (line[t0] == '\t') + for (*vp++ = ' '; (vp - vbuf) & 7; ) + *vp++ = ' '; + else if (line[t0] == '\n') { + *vp++ = '\\'; + *vp++ = 'n'; + } else if (line[t0] == 0x7f) { + *vp++ = '^'; + *vp++ = '?'; + } else if (icntrl(line[t0])) { + *vp++ = '^'; + *vp++ = line[t0] | '@'; + } else + *vp++ = line[t0]; + if (t0 == cs) + nvcs = vp - vbuf - 1; + } + if (t0 == cs) + nvcs = vp - vbuf; + *vp = '\0'; + +/* determine which part of the new line buffer we want for the display */ + if ((winpos && nvcs < winpos + 1) || (nvcs > winpos + winw - 2)) { + if ((winpos = nvcs - ((winw - hasam) / 2)) < 0) + winpos = 0; + } + if (winpos) + vbuf[winpos] = '<'; /* line continues to the left */ + if ((int)strlen(vbuf + winpos) > (winw - hasam)) { + vbuf[winpos + winw - hasam - 1] = '>'; /* line continues to right */ + vbuf[winpos + winw - hasam] = '\0'; + } + strcpy(nbuf[0], vbuf + winpos); + zfree(vbuf, vsiz); + nvcs -= winpos; + +/* display the `visable' portion of the line buffer */ + for (t0 = 0, vp = *nbuf;;) { + /* skip past all matching characters */ + for (; *vp && *vp == *refreshop; t0++, vp++, refreshop++) ; + + if (!*vp && !*refreshop) + break; + + singmoveto(t0); /* move to where we do all output from */ + + if (!*refreshop) { + if ((t0 = strlen(vp))) + zwrite(vp, t0, 1, shout); + vcs += t0; + break; + } + if (!*vp) { + if (tccan(TCCLEAREOL)) + tcout(TCCLEAREOL); + else + for (; *refreshop++; vcs++) + zputc(' ', shout); + break; + } + zputc(*vp, shout); + vcs++, t0++; + vp++, refreshop++; + } +/* move to the new cursor position */ + singmoveto(nvcs); + + qbuf = nbuf; + nbuf = obuf; + obuf = qbuf; + fflush(shout); /* make sure everything is written out */ +} + +/**/ +static void +singmoveto(int pos) +{ + if (pos == vcs) + return; + if (pos <= vcs / 2) { + zputc('\r', shout); + vcs = 0; + } + if (pos < vcs) { + tc_leftcurs(vcs - pos); + vcs = pos; + } + if (pos > vcs) { + if (tcmultout(TCRIGHT, TCMULTRIGHT, pos - vcs)) + vcs = pos; + else + while (pos > vcs) { + zputc(nbuf[0][vcs], shout); + vcs++; + } + } +} + +/* recheck size of prompts */ + +/**/ +static void +genprompts(void) +{ + countprompt(lpptbuf, &pptw, &lppth); + countprompt(rpptbuf, &rpw, &rppth); +} diff --git a/Src/Zle/zle_things.sed b/Src/Zle/zle_things.sed new file mode 100644 index 000000000..781d23704 --- /dev/null +++ b/Src/Zle/zle_things.sed @@ -0,0 +1,9 @@ +/^ *T("/{ + s/^[^"]*"/ z_/ + s/".*$/,/ + s/-//g + s/\./D/g + P + s/ z_\(.*\),/#define t_\1 (\&thingies[z_\1])/ + P +} diff --git a/Src/Zle/zle_thingy.c b/Src/Zle/zle_thingy.c new file mode 100644 index 000000000..c4f2e25e1 --- /dev/null +++ b/Src/Zle/zle_thingy.c @@ -0,0 +1,491 @@ +/* + * zle_thingy.c - thingies + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "zle.mdh" +#include "zle_thingy.pro" + +/* + * Thingies: + * + * From the user's point of view, a thingy is just a string. Internally, + * the thingy is a struct thingy; these structures are in a hash table + * indexed by the string the user sees. This hash table contains all + * thingies currently referenced anywhere; each has a reference count, + * and is deleted when it becomes unused. Being the name of a function + * counts as a reference. + * + * The DISABLED flag on a thingy indicates that it is not the name of a + * widget. This makes it easy to generate completion lists; + * looking only at the `enabled' nodes makes the thingy table look like + * a table of widgets. + */ + +/* Hashtable of thingies. Enabled nodes are those that refer to widgets. */ + +/**/ +HashTable thingytab; + +/**********************************/ +/* hashtable management functions */ +/**********************************/ + +/**/ +static void +createthingytab(void) +{ + thingytab = newhashtable(199, "thingytab", NULL); + + thingytab->hash = hasher; + thingytab->emptytable = emptythingytab; + thingytab->filltable = NULL; + thingytab->addnode = addhashnode; + thingytab->getnode = gethashnode; + thingytab->getnode2 = gethashnode2; + thingytab->removenode = removehashnode; + thingytab->disablenode = NULL; + thingytab->enablenode = NULL; + thingytab->freenode = freethingynode; + thingytab->printnode = NULL; +} + +/**/ +static void +emptythingytab(HashTable ht) +{ + /* This will only be called when deleting the thingy table, which * + * is only done to unload the zle module. A normal emptytable() * + * function would free all the thingies, but we don't want to do * + * that because some of them are the known thingies in the fixed * + * `thingies' table. As the module cleanup code deletes all the * + * keymaps and so on before deleting the thingy table, we can * + * just remove the user-defined widgets and then be sure that * + * *all* the thingies left are the fixed ones. This has the side * + * effect of freeing all resources used by user-defined widgets. */ + scanhashtable(thingytab, 0, 0, DISABLED, scanemptythingies, 0); +} + +/**/ +static void +scanemptythingies(HashNode hn, int flags) +{ + Thingy t = (Thingy) hn; + + /* Mustn't unbind internal widgets -- we wouldn't want to free the * + * memory they use. */ + if(!(t->widget->flags & WIDGET_INT)) + unbindwidget(t, 1); +} + +/**/ +static Thingy +makethingynode(void) +{ + Thingy t = (Thingy) zcalloc(sizeof(*t)); + + t->flags = DISABLED; + return t; +} + +/**/ +static void +freethingynode(HashNode hn) +{ + Thingy th = (Thingy) hn; + + zsfree(th->nam); + zfree(th, sizeof(*th)); +} + +/************************/ +/* referencing thingies */ +/************************/ + +/* It is important to maintain the reference counts on thingies. When * + * copying a reference to a thingy, wrap the copy in refthingy(), to * + * increase its reference count. When removing a reference, * + * unrefthingy() it. Both of these functions handle NULL arguments * + * correctly. */ + +/**/ +Thingy +refthingy(Thingy th) +{ + if(th) + th->rc++; + return th; +} + +/**/ +void +unrefthingy(Thingy th) +{ + if(th && !--th->rc) + thingytab->freenode(thingytab->removenode(thingytab, th->nam)); +} + +/* Use rthingy() to turn a string into a thingy. It increases the reference * + * count, after creating the thingy structure if necessary. */ + +/**/ +Thingy +rthingy(char *nam) +{ + Thingy t = (Thingy) thingytab->getnode2(thingytab, nam); + + if(!t) + thingytab->addnode(thingytab, ztrdup(nam), t = makethingynode()); + return refthingy(t); +} + +/***********/ +/* widgets */ +/***********/ + +/* + * Each widget is attached to one or more thingies. Each thingy + * names either zero or one widgets. Thingies that name a widget + * are treated as being referenced. The widget type, flags and pointer + * are stored in a separate structure pointed to by the thingies. Each + * thingy also has a pointer to the `next' thingy (in a circular list) + * that references the same widget. The DISABLED flag is unset in these + * thingies. + */ + +/* Bind a widget to a thingy. The thingy's reference count must already * + * have been incremented. The widget may already be bound to other * + * thingies; if it is not, then its `first' member must be NULL. Return * + * is 0 on success, or -1 if the thingy has the TH_IMMORTAL flag set. */ + +/**/ +static int +bindwidget(Widget w, Thingy t) +{ + if(t->flags & TH_IMMORTAL) { + unrefthingy(t); + return -1; + } + if(!(t->flags & DISABLED)) { + if(t->widget == w) + return 0; + unbindwidget(t, 1); + } + if(w->first) { + t->samew = w->first->samew; + w->first->samew = t; + } else { + w->first = t; + t->samew = t; + } + t->widget = w; + t->flags &= ~DISABLED; + return 0; +} + +/* Unbind a widget from a thingy. This decrements the thingy's reference * + * count. The widget will be destroyed if this is its last name. * + * TH_IMMORTAL thingies won't be touched, unless override is non-zero. * + * Returns 0 on success, or -1 if the thingy is protected. If the thingy * + * doesn't actually reference a widget, this is considered successful. */ + +/**/ +static int +unbindwidget(Thingy t, int override) +{ + Widget w; + + if(t->flags & DISABLED) + return 0; + if(!override && (t->flags & TH_IMMORTAL)) + return -1; + w = t->widget; + if(t->samew == t) + freewidget(w); + else { + Thingy p; + for(p = w->first; p->samew != t; p = p->samew) ; + w->first = p; /* optimised for deletezlefunction() */ + p->samew = t->samew; + } + t->flags &= ~TH_IMMORTAL; + t->flags |= DISABLED; + unrefthingy(t); + return 0; +} + +/* Free a widget. */ + +/**/ +static void +freewidget(Widget w) +{ + if(!(w->flags & WIDGET_INT)) + zsfree(w->u.fnnam); + zfree(w, sizeof(*w)); +} + +/* Add am internal widget provided by a module. The name given is the * + * canonical one, which must not begin with a dot. The widget is first * + * bound to the dotted canonical name; if that name is already taken by * + * an internal widget, failure is indicated. The same widget is then * + * bound to the canonical name, and a pointer to the widget structure * + * returned. */ + +/**/ +Widget +addzlefunction(char *name, ZleIntFunc ifunc, int flags) +{ + VARARR(char, dotn, strlen(name) + 2); + Widget w; + Thingy t; + + if(name[0] == '.') + return NULL; + dotn[0] = '.'; + strcpy(dotn + 1, name); + t = (Thingy) thingytab->getnode(thingytab, dotn); + if(t && (t->flags & TH_IMMORTAL)) + return NULL; + w = zalloc(sizeof(*w)); + w->flags = WIDGET_INT | flags; + w->first = NULL; + w->u.fn = ifunc; + t = rthingy(dotn); + bindwidget(w, t); + t->flags |= TH_IMMORTAL; + bindwidget(w, rthingy(name)); + return w; +} + +#ifdef DYNAMIC + +/* Delete an internal widget provided by a module. Don't try to delete * + * a widget from the fixed table -- it would be bad. (Thanks, Egon.) */ + +/**/ +void +deletezlefunction(Widget w) +{ + Thingy p, n; + + p = w->first; + while(1) { + n = p->samew; + if(n == p) { + unbindwidget(p, 1); + return; + } + unbindwidget(p, 1); + p = n; + } +} + +#endif /* DYNAMIC */ + +/***************/ +/* zle builtin */ +/***************/ + +/* + * The available operations are: + * + * -l list user-defined widgets (no arguments) + * -D delete widget names + * -A link the two named widgets (2 arguments) + * -N create new user-defined widget (1 or 2 arguments) + * invoke a widget (1 argument) + */ + +/**/ +int +bin_zle(char *name, char **args, char *ops, int func) +{ + static struct opn { + char o; + int (*func) _((char *, char **, char *, char)); + int min, max; + } const opns[] = { + { 'l', bin_zle_list, 0, 0 }, + { 'D', bin_zle_del, 1, -1 }, + { 'A', bin_zle_link, 2, 2 }, + { 'N', bin_zle_new, 1, 2 }, + { 0, bin_zle_call, 0, -1 }, + }; + struct opn const *op, *opp; + int n; + + /* select operation and ensure no clashing arguments */ + for(op = opns; op->o && !ops[op->o]; op++) ; + if(op->o) + for(opp = op; (++opp)->o; ) + if(ops[opp->o]) { + zerrnam(name, "incompatible operation selection options", + NULL, 0); + return 1; + } + + /* check number of arguments */ + for(n = 0; args[n]; n++) ; + if(!op->o && n != 1) { + zerrnam(name, "wrong number of arguments", NULL, 0); + return 1; + } + if(n < op->min) { + zerrnam(name, "not enough arguments for -%c", NULL, op->o); + return 1; + } else if(op->max != -1 && n > op->max) { + zerrnam(name, "too many arguments for -%c", NULL, op->o); + return 1; + } + + /* pass on the work to the operation function */ + return op->func(name, args, ops, op->o); +} + +/**/ +static int +bin_zle_list(char *name, char **args, char *ops, char func) +{ + scanhashtable(thingytab, 1, 0, DISABLED, scanlistwidgets, ops['L']); + return 0; +} + +/**/ +static void +scanlistwidgets(HashNode hn, int list) +{ + Thingy t = (Thingy) hn; + Widget w = t->widget; + + if(w->flags & WIDGET_INT) + return; + if(list) { + fputs("zle -N ", stdout); + if(t->nam[0] == '-') + fputs("-- ", stdout); + quotedzputs(t->nam, stdout); + if(strcmp(t->nam, w->u.fnnam)) { + fputc(' ', stdout); + quotedzputs(w->u.fnnam, stdout); + } + } else { + nicezputs(t->nam, stdout); + if(strcmp(t->nam, w->u.fnnam)) { + fputs(" (", stdout); + nicezputs(w->u.fnnam, stdout); + fputc(')', stdout); + } + } + putchar('\n'); +} + +/**/ +static int +bin_zle_del(char *name, char **args, char *ops, char func) +{ + int ret = 0; + + do { + Thingy t = (Thingy) thingytab->getnode(thingytab, *args); + if(!t) { + zwarnnam(name, "no such widget `%s'", *args, 0); + ret = 1; + } else if(unbindwidget(t, 0)) { + zwarnnam(name, "widget name `%s' is protected", *args, 0); + ret = 1; + } + } while(*++args); + return ret; +} + +/**/ +static int +bin_zle_link(char *name, char **args, char *ops, char func) +{ + Thingy t = (Thingy) thingytab->getnode(thingytab, args[0]); + + if(!t) { + zerrnam(name, "no such widget `%s'", args[0], 0); + return 1; + } else if(bindwidget(t->widget, rthingy(args[1]))) { + zerrnam(name, "widget name `%s' is protected", args[1], 0); + return 1; + } + return 0; + +} + +/**/ +static int +bin_zle_new(char *name, char **args, char *ops, char func) +{ + Widget w = zalloc(sizeof(*w)); + + w->flags = 0; + w->first = NULL; + w->u.fnnam = ztrdup(args[1] ? args[1] : args[0]); + if(!bindwidget(w, rthingy(args[0]))) + return 0; + freewidget(w); + zerrnam(name, "widget name `%s' is protected", args[0], 0); + return 1; +} + +/**/ +static int +bin_zle_call(char *name, char **args, char *ops, char func) +{ + Thingy t; + + if(!zleactive || incompctlfunc) { + zerrnam(name, "widgets can only be called when ZLE is active", + NULL, 0); + return 1; + } + t = rthingy(args[0]); + PERMALLOC { + execzlefunc(t); + } LASTALLOC; + unrefthingy(t); + return 0; +} + +/*******************/ +/* initialiasation */ +/*******************/ + +/**/ +void +init_thingies(void) +{ + Thingy t; + + createthingytab(); + for(t = thingies; t->nam; t++) + thingytab->addnode(thingytab, t->nam, t); +} diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c new file mode 100644 index 000000000..1aa1a008c --- /dev/null +++ b/Src/Zle/zle_tricky.c @@ -0,0 +1,4015 @@ +/* + * zle_tricky.c - expansion and completion + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "zle.mdh" +#include "zle_tricky.pro" + +/* The main part of ZLE maintains the line being edited as binary data, * + * but here, where we interface with the lexer and other bits of zsh, * + * we need the line metafied. The technique used is quite simple: on * + * entry to the expansion/completion system, we metafy the line in * + * place, adjusting ll and cs to match. All completion and expansion * + * is done on the metafied line. Immediately before returning, the * + * line is unmetafied again, changing ll and cs back. (ll and cs might * + * have changed during completion, so they can't be merely saved and * + * restored.) The various indexes into the line that are used in this * + * file only are not translated: they remain indexes into the metafied * + * line. */ + +#ifdef HAVE_NIS_PLUS +# include +#else +# ifdef HAVE_NIS +# include +# include +# include +# include + +/* This is used when getting usernames from the NIS. */ +typedef struct { + int len; + char *s; +} +dopestring; +# endif +#endif + +#define inststr(X) inststrlen((X),1,-1) + +/* wb and we hold the beginning/end position of the word we are completing. */ + +static int wb, we; + +/* offs is the cursor position within the tokenized * + * current word after removing nulargs. */ + +static int offs; + +/* These control the type of completion that will be done. They are * + * affected by the choice of ZLE command and by relevant shell options. */ + +static int usemenu, useglob; + +/* != 0 if we are in the middle of a menu completion */ + +static int menucmp; + +/* A pointer to the current position in the menu-completion array (the one * + * that was put in the command line last). */ + +static char **menucur; + +/* menupos is the point (in the command line) where the menu-completion * + * strings are inserted. menulen is the length of the string that was * + * inserted last. menuend is the end position of this string in the * + * command line. menuwe is non-zero if the cursor was at the end of the * + * word (meaning that suffixes should go before the cursor). menuinsc is * + * the length of any suffix that has been temporarily added. */ + +static int menupos, menulen, menuend, menuwe, menuinsc; + +/* This is used as a flag from get_comp_string() that we are doing * + * completion inside a brace expansion. */ + +static int complinbrace; + +/* The list of matches. fmatches contains the matches we first ignore * + * because of fignore. */ + +static LinkList matches, fmatches; + +/* The list of matches turned into an array. This is used to sort this * + * list and when menu-completion is used (directly or via automenu). */ + +static char **amatches; + +/* The number of matches. */ + +static int nmatches; + +/* A list of user-defined explanations for the completions to be shown * + * instead of amatches when listing completions. */ + +static char **aylist; + +/* !=0 if we have a valid completion list. */ + +static int validlist; + +/* This flag is non-zero if we are completing a pattern (with globcomplete) */ + +static int ispattern; + +/* Two patterns used when doing glob-completion. The first one is built * + * from the whole word we are completing and the second one from that * + * part of the word that was identified as a possible filename. */ + +static Comp patcomp, filecomp; + +/* We store the following prefixes/suffixes: * + * lpre/lsuf -- what's on the line * + * rpre/rsuf -- same as lpre/lsuf, but expanded * + * * + * ... and if we are completing files, too: * + * ppre/psuf -- the path prefix/suffix * + * fpre/fsuf -- prefix/suffix of the pathname component the cursor is in * + * prpre -- ppre in expanded form usable for opendir * + * * + * The integer variables hold the lengths of lpre, lsuf, rpre, rsuf, * + * fpre, and fsuf. noreal is non-zero if we have rpre/rsuf. */ + +static char *lpre, *lsuf; +static char *rpre, *rsuf; +static char *ppre, *psuf, *prpre; +static char *fpre, *fsuf; +static int lpl, lsl, rpl, rsl, fpl, fsl; +static int noreal; + +/* This is used when completing after `$' and holds the whole prefix, * + * used in do_single() to check whether the word expands to a directory * + * name (in that case and if autoparamslash is set, we add a `/'). * + * qparampre is the same but quoted. The length of it is in qparprelen. * + * parambr is != 0 if the parameter name is in braces. */ + +static char *parampre = NULL, *qparampre = NULL; +static int qparprelen, parambr; + +/* This is either zero or equal to the special character the word we are * + * trying to complete starts with (e.g. Tilde or Equals). */ + +static char ic; + +/* These hold the minimum common prefix/suffix lengths (normal and for * + * fignore ignored). */ + +static int ab, ae, fab, fae; + +/* This variable says what we are currently adding to the list of matches. */ + +static int addwhat; + +/* firstm hold the first match we found, shortest contains the shortest * + * one (normal and for fignore ignored). */ + +static char *firstm, *shortest, *ffirstm, *fshortest; + +/* This holds the word we are completing in quoted from. */ + +static char *qword; + +/* This is the length of the shortest match we found (normal and for * + * fignore ignored). */ + +static int shortl, fshortl; + +/* This is non-zero if we are doing a menu-completion and this is not the * + * first call (e.g. when automenu is set and menu-completion was entered * + * due to this). */ + +static int amenu; + +/* Find out if we have to insert a tab (instead of trying to complete). */ + +/**/ +static int +usetab(void) +{ + unsigned char *s = line + cs - 1; + + for (; s >= line && *s != '\n'; s--) + if (*s != '\t' && *s != ' ') + return 0; + return 1; +} + +#define COMP_COMPLETE 0 +#define COMP_LIST_COMPLETE 1 +#define COMP_SPELL 2 +#define COMP_EXPAND 3 +#define COMP_EXPAND_COMPLETE 4 +#define COMP_LIST_EXPAND 5 +#define COMP_ISEXPAND(X) ((X) >= COMP_EXPAND) + +/**/ +void +completeword(void) +{ + usemenu = isset(MENUCOMPLETE); + useglob = isset(GLOBCOMPLETE); + if (c == '\t' && usetab()) + selfinsert(); + else + docomplete(COMP_COMPLETE); +} + +/**/ +void +menucomplete(void) +{ + usemenu = 1; + useglob = isset(GLOBCOMPLETE); + if (c == '\t' && usetab()) + selfinsert(); + else + docomplete(COMP_COMPLETE); +} + +/**/ +void +listchoices(void) +{ + usemenu = isset(MENUCOMPLETE); + useglob = isset(GLOBCOMPLETE); + docomplete(COMP_LIST_COMPLETE); +} + +/**/ +void +spellword(void) +{ + usemenu = useglob = 0; + docomplete(COMP_SPELL); +} + +/**/ +void +deletecharorlist(void) +{ + char **mc = menucur; + + usemenu = isset(MENUCOMPLETE); + useglob = isset(GLOBCOMPLETE); + if (cs != ll) + deletechar(); + else + docomplete(COMP_LIST_COMPLETE); + + menucur = mc; +} + +/**/ +void +expandword(void) +{ + usemenu = useglob = 0; + if (c == '\t' && usetab()) + selfinsert(); + else + docomplete(COMP_EXPAND); +} + +/**/ +void +expandorcomplete(void) +{ + usemenu = isset(MENUCOMPLETE); + useglob = isset(GLOBCOMPLETE); + if (c == '\t' && usetab()) + selfinsert(); + else + docomplete(COMP_EXPAND_COMPLETE); +} + +/**/ +void +menuexpandorcomplete(void) +{ + usemenu = 1; + useglob = isset(GLOBCOMPLETE); + if (c == '\t' && usetab()) + selfinsert(); + else + docomplete(COMP_EXPAND_COMPLETE); +} + +/**/ +void +listexpand(void) +{ + usemenu = isset(MENUCOMPLETE); + useglob = isset(GLOBCOMPLETE); + docomplete(COMP_LIST_EXPAND); +} + +/**/ +void +reversemenucomplete(void) +{ + if (!menucmp) { + menucomplete(); + return; + } + HEAPALLOC { + if (menucur == amatches) + menucur = amatches + nmatches - 1; + else + menucur--; + metafy_line(); + do_single(*menucur); + unmetafy_line(); + } LASTALLOC; +} + +/* Accepts the current completion and starts a new arg, * + * with the next completions. This gives you a way to * + * accept several selections from the list of matches. */ + +/**/ +void +acceptandmenucomplete(void) +{ + if (!menucmp) { + feep(); + return; + } + cs = menuend + menuinsc; + inststrlen(" ", 1, 1); + if (qparampre) + inststrlen(qparampre, 1, qparprelen); + if (lpre && !ispattern) + inststrlen(lpre, 1, -1); + if (lsuf && !ispattern) + inststrlen(lsuf, 0, -1); + menupos = cs; + menuend = cs + (lsuf ? strlen(lsuf) : 0); + menulen = 0; + menuinsc = 0; + menuwe = 1; + menucomplete(); +} + +/* These are flags saying if we are completing in the command * + * position or in a redirection. */ + +static int lincmd, linredir; + +/* Non-zero if the last completion done was ambiguous (used to find * + * out if AUTOMENU should start). More precisely, it's nonzero after * + * successfully doing any completion, unless the completion was * + * unambiguous and did not cause the display of a completion list. * + * From the other point of view, it's nonzero iff AUTOMENU (if set) * + * should kick in on another completion. */ + +static int lastambig; + +/* This describes some important things collected during the last * + * completion. Its value is zero or the inclusive OR of some of * + * the HAS_* things below. */ + +static int haswhat; + +/* We have a suffix to add (given with compctl -S). */ + +#define HAS_SUFFIX 1 + +/* We have filenames in the completion list. */ + +#define HAS_FILES 2 + +/* We have other things than files in the completion list. If this is * + * not set but HAS_FILES is, we probably put the file type characters * + * in the completion list (if listtypes is set) and we attempt to add * + * a slash to completed directories. */ + +#define HAS_MISC 4 + +/* This is set if we have filenames in the completion list that were * + * generated by a globcompletion pattern. */ + +#define HAS_PATHPAT 8 + + +/* This holds the naem of the current command (used to find the right * + * compctl). */ + +static char *cmdstr; + + +/* Check if the given string is the name of a parameter and if this * + * parameter is one worth expanding. */ + +/**/ +static int +checkparams(char *p) +{ + int t0, n, l = strlen(p), e = 0; + struct hashnode *hn; + + for (t0 = paramtab->hsize - 1, n = 0; n < 2 && t0 >= 0; t0--) + for (hn = paramtab->nodes[t0]; n < 2 && hn; hn = hn->next) + if (pfxlen(p, hn->nam) == l) { + n++; + if (strlen(hn->nam) == l) + e = 1; + } + return (n == 1) ? (getsparam(p) != NULL) : + (!menucmp && e && isset(RECEXACT)); +} + +/* Check if the given string has wildcards. The difficulty is that we * + * have to treat things like job specifications (%...) and parameter * + * expressions correctly. */ + +/**/ +static int +cmphaswilds(char *str) +{ + if ((*str == Inbrack || *str == Outbrack) && !str[1]) + return 0; + + /* If a leading % is immediately followed by ?, then don't * + * treat that ? as a wildcard. This is so you don't have * + * to escape job references such as %?foo. */ + if (str[0] == '%' && str[1] ==Quest) + str += 2; + + for (; *str;) { + if (*str == String || *str == Qstring) { + /* A parameter expression. */ + + if (*++str == Inbrace) + skipparens(Inbrace, Outbrace, &str); + else if (*str == String || *str == Qstring) + str++; + else { + /* Skip all the things a parameter expression might start * + * with (before we come to the parameter name). */ + for (; *str; str++) + if (*str != '^' && *str != Hat && + *str != '=' && *str != Equals && + *str != '~' && *str != Tilde) + break; + if (*str == '#' || *str == Pound) + str++; + /* Star and Quest are parameter names here, not wildcards */ + if (*str == Star || *str == Quest) + str++; + } + } else { + /* Not a parameter expression so we check for wildcards */ + if (((*str == Pound || *str == Hat) && isset(EXTENDEDGLOB)) || + *str == Star || *str == Bar || *str == Quest || + !skipparens(Inbrack, Outbrack, &str) || + !skipparens(Inang, Outang, &str) || + (unset(IGNOREBRACES) && + !skipparens(Inbrace, Outbrace, &str)) || + (*str == Inpar && str[1] == ':' && + !skipparens(Inpar, Outpar, &str))) + return 1; + if (*str) + str++; + } + } + return 0; +} + +/* The main entry point for completion. */ + +/**/ +static void +docomplete(int lst) +{ + char *s, *ol; + int olst = lst, chl = 0, ne = noerrs, ocs; + + /* If we are doing a menu-completion... */ + + if (menucmp && lst != COMP_LIST_EXPAND) { + do_menucmp(lst); + return; + } + + /* Check if we have to start a menu-completion (via automenu). */ + + if ((amenu = (isset(AUTOMENU) && lastambig))) + usemenu = 1; + + /* Expand history references before starting completion. If anything * + * changed, do no more. */ + + if (doexpandhist()) + return; + + metafy_line(); + + ocs = cs; + if (!isfirstln && chline != NULL) { + /* If we are completing in a multi-line buffer (which was not * + * taken from the history), we have to prepend the stuff saved * + * in chline to the contents of line. */ + + ol = dupstring((char *)line); + /* Make sure that chline is zero-terminated. */ + *hptr = '\0'; + cs = 0; + inststr(chline); + chl = cs; + cs += ocs; + } else + ol = NULL; + inwhat = IN_NOTHING; + qword = NULL; + /* Get the word to complete. */ + noerrs = 1; + s = get_comp_string(); + DPUTS(wb < 0 || cs < wb || cs > we, + "BUG: 0 <= wb <= cs <= we is not true!"); + 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 * + * would never change the pointer in the middle of an insertion, but * + * then vi doesn't have completion. More to the point, this is only * + * an emulation. */ + if (viinsbegin > ztrsub((char *) line + wb, (char *) line)) + viinsbegin = ztrsub((char *) line + wb, (char *) line); + /* If we added chline to the line buffer, reset the original contents. */ + if (ol) { + cs -= chl; + wb -= chl; + we -= chl; + if (wb < 0) { + strcpy((char *) line, ol); + ll = strlen((char *) line); + cs = ocs; + unmetafy_line(); + feep(); + return; + } + ocs = cs; + cs = 0; + foredel(chl); + cs = ocs; + } + freeheap(); + /* Save the lexer state, in case the completion code uses the lexer * + * somewhere (e.g. when processing a compctl -s flag). */ + lexsave(); + if (inwhat == IN_ENV) + lincmd = 0; + if (s) { + if (lst == COMP_EXPAND_COMPLETE) { + /* Check if we have to do expansion or completion. */ + char *q = s; + + if (*q == Equals) { + /* The word starts with `=', see if we can expand it. */ + q = s + 1; + if (cmdnamtab->getnode(cmdnamtab, q) || hashcmd(q, pathchecked)) + if (isset(RECEXACT)) + lst = COMP_EXPAND; + else { + int t0, n = 0; + char *fc; + struct hashnode *hn; + + for (t0 = cmdnamtab->hsize - 1; t0 >= 0; t0--) + for (hn = cmdnamtab->nodes[t0]; hn; + hn = hn->next) { + if (strpfx(q, hn->nam) && (fc = findcmd(hn->nam))) { + zsfree(fc); + n++; + } + if (n == 2) + break; + } + + if (n == 1) + lst = COMP_EXPAND; + } + } + if (lst == COMP_EXPAND_COMPLETE) + do { + /* check if there is a parameter expresiion. */ + for (; *q && *q != String; q++); + if (*q == String && q[1] != Inpar && q[1] != Inbrack) { + if (*++q == Inbrace) { + if (! skipparens(Inbrace, Outbrace, &q) && + q == s + cs - wb) + lst = COMP_EXPAND; + } else { + char *t, sav, sav2; + + /* Skip the things parameter expressions might * + * start with (the things before the parameter * + * name). */ + for (; *q; q++) + if (*q != '^' && *q != Hat && + *q != '=' && *q != Equals && + *q != '~' && *q != Tilde) + break; + if ((*q == '#' || *q == Pound || *q == '+') && + q[1] != String) + q++; + + sav2 = *(t = q); + if (*q == Quest || *q == Star || *q == String || + *q == Qstring) + *q = ztokens[*q - Pound], ++q; + else if (*q == '?' || *q == '*' || *q == '$' || + *q == '-' || *q == '!' || *q == '@') + q++; + else if (idigit(*q)) + do q++; while (idigit(*q)); + else + while (iident(*q)) + q++; + sav = *q; + *q = '\0'; + if (cs - wb == q - s && + (idigit(sav2) || checkparams(t))) + lst = COMP_EXPAND; + *q = sav; + *t = sav2; + } + if (lst != COMP_EXPAND) + lst = COMP_COMPLETE; + } else + break; + } while (q < s + cs - wb); + if (lst == COMP_EXPAND_COMPLETE) { + /* If it is still not clear if we should use expansion or * + * completion and there is a `$' or a backtick in the word, * + * than do expansion. */ + for (q = s; *q; q++) + if (*q == Tick || *q == Qtick || + *q == String || *q == Qstring) + break; + lst = *q ? COMP_EXPAND : COMP_COMPLETE; + } + /* And do expansion if there are wildcards and globcomplete is * + * not used. */ + if (unset(GLOBCOMPLETE) && cmphaswilds(s)) + lst = COMP_EXPAND; + } + if (lincmd && (inwhat == IN_NOTHING)) + inwhat = IN_CMD; + + if (lst == COMP_SPELL) { + char *x, *q; + + for (q = s; *q; q++) + if (INULL(*q)) + *q = Nularg; + cs = wb; + foredel(we - wb); + HEAPALLOC { + untokenize(x = dupstring(s)); + if (*s == Tilde || *s == Equals || *s == String) + *x = *s; + spckword(&x, 0, lincmd, 0); + } LASTALLOC; + untokenize(x); + inststr(x); + } else if (COMP_ISEXPAND(lst)) { + /* Do expansion. */ + char *ol = (olst == COMP_EXPAND_COMPLETE) ? + dupstring((char *)line) : (char *)line; + int ocs = cs, ne = noerrs; + + noerrs = 1; + doexpansion(s, lst, olst, lincmd); + lastambig = 0; + noerrs = ne; + + /* If expandorcomplete was invoked and the expansion didn't * + * change the command line, do completion. */ + if (olst == COMP_EXPAND_COMPLETE && + !strcmp(ol, (char *)line)) { + char *p; + + cs = ocs; + errflag = 0; + + p = s; + if (*p == Tilde || *p == Equals) + p++; + for (; *p; p++) + if (itok(*p)) + if (*p != String && *p != Qstring) + *p = ztokens[*p - Pound]; + else if (p[1] == Inbrace) + p++, skipparens(Inbrace, Outbrace, &p); + docompletion(s, lst, lincmd, 1); + } + } else + /* Just do completion. */ + docompletion(s, lst, lincmd, 0); + zsfree(s); + } + /* Reset the lexer state, pop the heap. */ + lexrestore(); + popheap(); + zsfree(qword); + unmetafy_line(); +} + +/* Do completion, given that we are in the middle of a menu completion. We * + * don't need to generate a list of matches, because that's already been * + * done by previous commands. We will either list the completions, or * + * insert the next completion. */ + +/**/ +static void +do_menucmp(int lst) +{ + /* Just list the matches if the list was requested. */ + if (lst == COMP_LIST_COMPLETE) { + showinglist = -2; + return; + } + /* Otherwise go to the next match in the array... */ + HEAPALLOC { + if (!*++menucur) + menucur = amatches; + /* ... and insert it into the command line. */ + metafy_line(); + do_single(*menucur); + unmetafy_line(); + } LASTALLOC; +} + +/* 1 if we are completing in a string */ +static int instring; + +/* 1 if we are completing the prefix */ +static int comppref; + +/* This function inserts an `x' in the command line at the cursor position. * + * * + * Oh, you want to know why? Well, if completion is tried somewhere on an * + * empty part of the command line, the lexer code would normally not be * + * able to give us the `word' we want to complete, since there is no word. * + * But we need to call the lexer to find out where we are (and for which * + * command we are completing and such things). So we temporarily add a `x' * + * (any character without special meaning would do the job) at the cursor * + * position, than the lexer gives us the word `x' and its beginning and end * + * positions and we can remove the `x'. * + * * + * If we are just completing the prefix (comppref set), we also insert a * + * space after the x to end the word. We never need to remove the space: * + * anywhere we are able to retrieve a word for completion it will be * + * discarded as whitespace. It has the effect of making any suffix * + * referrable to as the next word on the command line when indexing * + * from a completion function. */ + +/**/ +static void +addx(char **ptmp) +{ + int addspace = 0; + + if (!line[cs] || line[cs] == '\n' || + (iblank(line[cs]) && (!cs || line[cs-1] != '\\')) || + line[cs] == ')' || line[cs] == '`' || + (instring && (line[cs] == '"' || line[cs] == '\'')) || + (addspace = (comppref && !iblank(line[cs])))) { + *ptmp = (char *)line; + line = (unsigned char *)halloc(strlen((char *)line) + 3 + addspace); + memcpy(line, *ptmp, cs); + line[cs] = 'x'; + if (addspace) + line[cs+1] = ' '; + strcpy((char *)line + cs + 1 + addspace, (*ptmp) + cs); + addedx = 1 + addspace; + } else { + addedx = 0; + *ptmp = NULL; + } +} + +/* Like dupstring, but add an extra space at the end of the string. */ + +/**/ +static char * +dupstrspace(const char *str) +{ + int len = strlen((char *)str); + char *t = (char *)ncalloc(len + 2); + strcpy(t, str); + strcpy(t+len, " "); + return t; +} + +/* These functions metafy and unmetafy the ZLE buffer, as described at the * + * top of this file. Note that ll and cs are translated. They *must* be * + * called in matching pairs, around all the expansion/completion code. * + * Currently, there are four pairs: in history expansion, in the main * + * completion function, and one in each of the middle-of-menu-completion * + * functions (there's one for each direction). */ + +/**/ +static void +metafy_line(void) +{ + int len = ll; + char *s; + + for (s = (char *) line; s < (char *) line + ll;) + if (imeta(*s++)) + len++; + sizeline(len); + (void) metafy((char *) line, ll, META_NOALLOC); + ll = len; + cs = metalen((char *) line, cs); +} + +/**/ +static void +unmetafy_line(void) +{ + cs = ztrsub((char *) line + cs, (char *) line); + (void) unmetafy((char *) line, &ll); +} + +/* Lasciate ogni speranza. * + * This function is a nightmare. It works, but I'm sure that nobody really * + * understands why. The problem is: to make it cleaner we would need * + * changes in the lexer code (and then in the parser, and then...). */ + +/**/ +static char * +get_comp_string(void) +{ + int t0, tt0, i, j, k, cp, rd, sl, ocs; + char *s = NULL, *linptr, *tmp, *p, *tt = NULL; + + complinbrace = 0; + /* This global flag is used to signal the lexer code if it should * + * expand aliases or not. */ + noaliases = isset(COMPLETEALIASES); + + /* Find out if we are somewhere in a `string', i.e. inside '...', * + * "...", `...`, or ((...)). */ + + for (i = j = k = 0, p = (char *)line; p < (char *)line + cs; p++) + if (*p == '`' && !(k & 1)) + i++; + else if (*p == '\"' && !(k & 1) && !(i & 1)) + j++; + else if (*p == '\'' && !(j & 1)) + k++; + else if (*p == '\\' && p[1] && !(k & 1)) + p++; + instring = (j & 1) ? 2 : (k & 1); + addx(&tmp); + if (instring) { + /* Yes, we are in a string. */ + if (!tmp) { + tmp = (char *)line; + line = (unsigned char *) dupstring((char *) line); + } + /* Now remove the quotes. * + * What?? Why that?? Well, we want to be able to complete * + * inside strings. The lexer code gives us no help here, * + * so we have to cheat. We remove the quotes, the lexer * + * will than treat the words in the strings normally and we * + * can complete them. * + * This is completely the wrong thing to do, but it's * + * occasionally useful, and we can't handle quotes properly * + * yet anyway. */ + for (p = (char *)line; *p; p++) + if (*p == '"' || *p == '\'') + *p = ' '; + } + linptr = (char *)line; + pushheap(); + HEAPALLOC { + start: + inwhat = IN_NOTHING; + /* Now set up the lexer and start it. */ + parbegin = parend = -1; + lincmd = incmdpos; + linredir = inredir; + zsfree(cmdstr); + cmdstr = NULL; + zleparse = 1; + clwpos = -1; + lexsave(); + inpush(dupstrspace((char *) linptr), 0, NULL); + strinbeg(); + stophist = 2; + i = tt0 = cp = rd = 0; + + /* This loop is possibly the wrong way to do this. It goes through * + * the previously massaged command line using the lexer. It stores * + * each token in each command (commands being regarded, roughly, as * + * being separated by tokens | & &! |& || &&). The loop stops when * + * the end of the command containing the cursor is reached. It's a * + * simple way to do things, but suffers from an inability to * + * distinguish actual command arguments from, for example, * + * filenames in redirections. (But note that code elsewhere checks * + * if we are completing *in* a redirection.) The only way to fix * + * this would be to pass the command line through the parser too, * + * and get the arguments that way. Maybe in 3.1... */ + do { + lincmd = incmdpos; + linredir = inredir; + /* Get the next token. */ + ctxtlex(); + if (tok == DINPAR) + tokstr = NULL; + + /* We reached the end. */ + if (tok == ENDINPUT) + break; + if (tok == BAR || tok == AMPER || + tok == BARAMP || tok == AMPERBANG || + ((tok == DBAR || tok == DAMPER) && !incond)) { + /* This is one of the things that separate commands. If we * + * already have the things we need (e.g. the token strings), * + * leave the loop. */ + if (tt) + break; + /* Otherwise reset the variables we are collecting data in. */ + i = tt0 = cp = rd = 0; + } + if (lincmd && tok == STRING) { + /* The lexer says, this token is in command position, so * + * store the token string (to find the right compctl). */ + zsfree(cmdstr); + cmdstr = ztrdup(tokstr); + i = 0; + } + if (!zleparse && !tt0) { + /* This is done when the lexer reached the word the cursor is on. */ + tt = tokstr ? dupstring(tokstr) : NULL; + /* If we added a `x', remove it. */ + if (addedx && tt) + chuck(tt + cs - wb); + tt0 = tok; + /* Store the number of this word. */ + clwpos = i; + cp = lincmd; + rd = linredir; + if (inwhat == IN_NOTHING && incond) + inwhat = IN_COND; + } + if (!tokstr) + continue; + /* We need to store the token strings of all words (for some of * + * the more complicated compctl -x things). They are stored in * + * the clwords array. Make this array big enough. */ + if (i + 1 == clwsize) { + int n; + clwords = (char **)realloc(clwords, + (clwsize *= 2) * sizeof(char *)); + for(n = clwsize; --n > i; ) + clwords[n] = NULL; + } + zsfree(clwords[i]); + /* And store the current token string. */ + clwords[i] = ztrdup(tokstr); + sl = strlen(tokstr); + /* Sometimes the lexer gives us token strings ending with * + * spaces we delete the spaces. */ + while (sl && clwords[i][sl - 1] == ' ' && + (sl < 2 || (clwords[i][sl - 2] != Bnull && + clwords[i][sl - 2] != Meta))) + clwords[i][--sl] = '\0'; + /* If this is the word the cursor is in and we added a `x', * + * remove it. */ + if (clwpos == i++ && addedx) + chuck(&clwords[i - 1][((cs - wb) >= sl) ? + (sl - 1) : (cs - wb)]); + } while (tok != LEXERR && tok != ENDINPUT && + (tok != SEPER || (zleparse && !tt0))); + /* Calculate the number of words stored in the clwords array. */ + clwnum = (tt || !i) ? i : i - 1; + zsfree(clwords[clwnum]); + clwords[clwnum] = NULL; + t0 = tt0; + lincmd = cp; + linredir = rd; + strinend(); + inpop(); + errflag = zleparse = 0; + if (parbegin != -1) { + /* We are in command or process substitution */ + if (parend >= 0 && !tmp) + line = (unsigned char *) dupstring(tmp = (char *)line); + linptr = (char *) line + ll + addedx - parbegin + 1; + if (parend >= 0) { + ll -= parend; + line[ll + addedx] = '\0'; + } + lexrestore(); + goto start; + } + + if (inwhat == IN_MATH) + s = NULL; + else if (!t0 || t0 == ENDINPUT) { + /* There was no word (empty line). */ + s = ztrdup(""); + we = wb = cs; + clwpos = clwnum; + t0 = STRING; + } else if (t0 == STRING) { + /* We found a simple string. */ + s = ztrdup(clwords[clwpos]); + } else if (t0 == ENVSTRING) { + /* The cursor was inside a parameter assignment. */ + for (s = tt; iident(*s); s++); + if (skipparens(Inbrack, Outbrack, &s) > 0 || s > tt + cs - wb) + s = NULL, inwhat = IN_MATH; + else if (*s == '=') { + s++; + wb += s - tt; + t0 = STRING; + s = ztrdup(s); + inwhat = IN_ENV; + } + lincmd = 1; + } + if (we > ll) + we = ll; + tt = (char *)line; + if (tmp) { + line = (unsigned char *)tmp; + ll = strlen((char *)line); + } + if (t0 != STRING && inwhat != IN_MATH) { + if (tmp) { + tmp = NULL; + linptr = (char *)line; + lexrestore(); + goto start; + } + feep(); + noaliases = 0; + lexrestore(); + LASTALLOC_RETURN NULL; + } + + noaliases = 0; + + /* Check if we are in an array subscript. We simply assume that * + * we are in a subscript if we are in brackets. Correct solution * + * is very difficult. This is quite close, but gets things like * + * foo[_ wrong (note no $). If we are in a subscript, treat it * + * as being in math. */ + if (inwhat != IN_MATH) { + int i = 0; + for (tt = s; ++tt < s + cs - wb;) + if (*tt == Inbrack) + i++; + else if (i && *tt == Outbrack) + i--; + if (i) + inwhat = IN_MATH; + } + if (inwhat == IN_MATH) { + /* In mathematical expression, we complete parameter names (even * + * if they don't have a `$' in front of them). So we have to * + * find that name. */ + for (we = cs; iident(line[we]); we++); + for (wb = cs; --wb >= 0 && iident(line[wb]);); + wb++; + zsfree(s); + s = zalloc(we - wb + 1); + strncpy(s, (char *) line + wb, we - wb); + s[we - wb] = '\0'; + } + /* This variable will hold the current word in quoted form. */ + qword = ztrdup(s); + /* While building the quoted form, we also clean up the command line. */ + offs = cs - wb; + for (p = s, tt = qword, i = wb; *p; p++, tt++, i++) + if (INULL(*p)) { + if (i < cs) + offs--; + if (p[1] || *p != Bnull) { + if (*p == Bnull) { + *tt = '\\'; + if (cs == i + 1) + cs++, offs++; + } else { + ocs = cs; + cs = i; + foredel(1); + chuck(tt--); + if ((cs = ocs) > i--) + cs--; + we--; + } + } else { + ocs = cs; + *tt = '\0'; + cs = we; + backdel(1); + if (ocs == we) + cs = we - 1; + else + cs = ocs; + we--; + } + chuck(p--); + } + + if (!isset(IGNOREBRACES)) { + /* Try and deal with foo{xxx etc.; only simple cases + * (only one inbrace, completion after inbrace and before outbrace + * if present). + */ + int myoffs = isset(COMPLETEINWORD) ? offs : strlen(s); + tt = NULL; + /* First check the conditions mentioned above + * and locate opening brace + */ + for (i = 0, p = s; *p; p++, i++) { + /* careful, ${... is not a brace expansion... + * in fact, if it's got a substitution in it's too + * hard for us anyway. sorry. + */ + if (*p == String || *p == Qstring) { + tt = NULL; + break; + } else if (*p == Inbrace) { + if (tt) { + /* too many inbraces */ + tt = NULL; + break; + } + tt = p; + } else if (*p == Outbrace && i < myoffs) { + /* outbrace is before cursor pos, so nothing to complete */ + tt = NULL; + break; + } + } + + if (tt && tt < s + myoffs) { + /* Braces are go: delete opening brace */ + char *com = NULL; + chuck(tt); + offs--; + myoffs--; + + /* Look for text up to comma before cursor and delete it */ + for (i = tt - s, p = tt; *p && i < myoffs; p++, i++) + if (*p == Comma) + com = p; + if (com) { + i = com - tt + 1; + while (i--) + chuck(tt), offs--, myoffs--; + } + + /* Look for text between subsequent comma + * and closing brace or end of string and delete it + */ + for (p = s + myoffs; *p && *p != Outbrace; p++) + if (*p == Comma) { + while (*p && *p != Outbrace) + chuck(p); + break; + } + if (*p == Outbrace) + chuck(p); + else { + /* we are still waiting for an outbrace and maybe commas */ + complinbrace = 1; + } + } + } + + } LASTALLOC; + lexrestore(); + + return (char *)s; +} + +/* Expand the current word. */ + +/**/ +static void +doexpansion(char *s, int lst, int olst, int explincmd) +{ + LinkList vl; + char *ss; + + DPUTS(useheap, "BUG: useheap in doexpansion()"); + HEAPALLOC { + pushheap(); + vl = newlinklist(); + ss = dupstring(s); + addlinknode(vl, ss); + prefork(vl, 0); + if (errflag) + goto end; + if ((lst == COMP_LIST_EXPAND) || (lst == COMP_EXPAND)) { + int ng = opts[NULLGLOB]; + + opts[NULLGLOB] = 1; + globlist(vl); + opts[NULLGLOB] = ng; + } + if (errflag) + goto end; + if (empty(vl) || !*(char *)peekfirst(vl)) { + if (!noerrs) + feep(); + goto end; + } + if (peekfirst(vl) == (void *) ss || + (olst == COMP_EXPAND_COMPLETE && + !nextnode(firstnode(vl)) && *s == Tilde && + (ss = dupstring(s), filesubstr(&ss, 0)) && + !strcmp(ss, (char *)peekfirst(vl)))) { + /* If expansion didn't change the word, try completion if * + * expandorcomplete was called, otherwise, just beep. */ + if (lst == COMP_EXPAND_COMPLETE) + docompletion(s, COMP_COMPLETE, explincmd, 0); + else + feep(); + goto end; + } + if (lst == COMP_LIST_EXPAND) { + /* Only the list of expansions was requested. */ + listlist(vl); + goto end; + } + /* Remove the current word and put the expansions there. */ + cs = wb; + foredel(we - wb); + while ((ss = (char *)ugetnode(vl))) { + untokenize(ss); + ss = quotename(ss, NULL, NULL, NULL); + inststr(ss); +#if 0 + if (nonempty(vl)) { + spaceinline(1); + line[cs++] = ' '; + } +#endif + if (olst != COMP_EXPAND_COMPLETE || nonempty(vl) || + (cs && line[cs-1] != '/')) { + spaceinline(1); + line[cs++] = ' '; + } + } + end: + popheap(); + } LASTALLOC; +} + +/* This is called from the lexer to give us word positions. */ + +/**/ +void +gotword(void) +{ + we = ll + 1 - inbufct + (addedx == 2 ? 1 : 0); + if (cs <= we) { + wb = ll - wordbeg + addedx; + zleparse = 0; + } +} + +/* Insert the given string into the command line. If move is non-zero, * + * the cursor position is changed and len is the length of the string * + * to insert (if it is -1, the length is calculated here). */ + +/**/ +static void +inststrlen(char *str, int move, int len) +{ + if (!len) + return; + if (len == -1) + len = strlen(str); + spaceinline(len); + strncpy((char *)(line + cs), str, len); + if (move) + cs += len; +} + +/* Quote the string s and return the result. If e is non-zero, it the * + * pointer it points to may point to aposition in s and in e the position * + * of the corresponding character in the quoted string is returned. Like * + * e, te may point to a position in the string and pl is used to return * + * the position of the character pointed to by te in the quoted string. * + * The string is metafied and may contain tokens. */ + +/**/ +static char * +quotename(const char *s, char **e, char *te, int *pl) +{ + const char *u, *tt; + char *v, buf[PATH_MAX * 2]; + int sf = 0; + + tt = v = buf; + u = s; + for (; *u; u++) { + if (e && *e == u) + *e = v, sf |= 1; + if (te == u) + *pl = v - tt, sf |= 2; + if (ispecial(*u) && + (!instring || (isset(BANGHIST) && + *u == (char)bangchar) || + (instring == 2 && + (*u == '$' || *u == '`' || *u == '\"')) || + (instring == 1 && *u == '\''))) + if (*u == '\n' || (instring == 1 && *u == '\'')) { + if (unset(RCQUOTES)) { + *v++ = '\''; + if (*u == '\'') + *v++ = '\\'; + *v++ = *u; + *v++ = '\''; + } else if (*u == '\n') + *v++ = '"', *v++ = '\n', *v++ = '"'; + else + *v++ = '\'', *v++ = '\''; + continue; + } else + *v++ = '\\'; + if(*u == Meta) + *v++ = *u++; + *v++ = *u; + } + *v = '\0'; + if (strcmp(buf, s)) + tt = dupstring(buf); + else + tt = s; + v += tt - buf; + if (e && (sf & 1)) + *e += tt - buf; + + if (e && *e == u) + *e = v; + if (te == u) + *pl = v - tt; + + return (char *) tt; +} + +/* This adds a match to the list of matches. The string to add is given * + * in s, the type of match is given in the global variable addwhat and * + * the parameter t (if not NULL) is a pointer to a hash node node which * + * may be used to give other information to this function. * + * * + * addwhat contains either one of the special values (negative, see below) * + * or the inclusive OR of some of the CC_* flags used for compctls. */ + +/**/ +static void +addmatch(char *s, char *t) +{ + int test = 0, sl = strlen(s), pl = rpl, cc = 0, *bp, *ep, *sp; + char *e = NULL, *tt, *te, *fc, **fm; + Comp cp = patcomp; + HashNode hn; + Param pm; + LinkList l = matches; + +/* + * addwhat: -5 is for files, + * -6 is for glob expansions, + * -8 is for executable files (e.g. command paths), + * -9 is for parameters + * -7 is for command names (from cmdnamtab) + * -4 is for a cdable parameter + * -3 is for executable command names. + * -2 is for anything unquoted + * -1 is for other file specifications + * (things with `~' of `=' at the beginning, ...). + */ + + /* Just to make the code cleaner */ + hn = (HashNode) t; + pm = (Param) t; + + if (!addwhat) { + test = 1; + } else if (addwhat == -1 || addwhat == -5 || addwhat == -6 || + addwhat == CC_FILES || addwhat == -7 || addwhat == -8) { + if (sl < fpl + fsl) + return; + + if ((addwhat == CC_FILES || + addwhat == -5) && !*psuf && !*fsuf) { + /* If this is a filename, do the fignore check. */ + char **pt = fignore; + int filell; + + for (test = 1; test && *pt; pt++) + if ((filell = strlen(*pt)) < sl + && !strcmp(*pt, s + sl - filell)) + test = 0; + + if (!test) + l = fmatches; + } + pl = fpl; + if (addwhat == -5 || addwhat == -8) { + test = 1; + cp = filecomp; + cc = cp || ispattern; + e = s + sl - fsl; + } else { + if ((cp = filecomp)) { + if ((test = domatch(s, filecomp, 0))) + cc = 1; + } else { + e = s + sl - fsl; + if ((test = !strncmp(s, fpre, fpl))) + test = !strcmp(e, fsuf); + if (ispattern) + cc = 1; + } + } + if (test) { + fc = NULL; + if (addwhat == -7 && !(fc = findcmd(s))) + return; + if (fc) + zsfree(fc); + haswhat |= HAS_FILES; + + if (addwhat == CC_FILES || addwhat == -6 || + addwhat == -5 || addwhat == -8) { + te = s + pl; + s = quotename(s, &e, te, &pl); + sl = strlen(s); + } else if (!cc) { + s = dupstring(t = s); + e += s - t; + } + if (cc) { + tt = (char *)halloc(strlen(ppre) + strlen(psuf) + sl + 1); + strcpy(tt, ppre); + strcat(tt, s); + strcat(tt, psuf); + untokenize(s = tt); + } + } + } else if (addwhat == CC_QUOTEFLAG || addwhat == -2 || + (addwhat == -3 && !(hn->flags & DISABLED)) || + (addwhat == -4 && (PM_TYPE(pm->flags) == PM_SCALAR) && + (tt = pm->gets.cfn(pm)) && *tt == '/') || + (addwhat == -9 && !(hn->flags & PM_UNSET)) || + (addwhat > 0 && + ((!(hn->flags & PM_UNSET) && + (((addwhat & CC_ARRAYS) && (hn->flags & PM_ARRAY)) || + ((addwhat & CC_INTVARS) && (hn->flags & PM_INTEGER)) || + ((addwhat & CC_ENVVARS) && (hn->flags & PM_EXPORTED)) || + ((addwhat & CC_SCALARS) && (hn->flags & PM_SCALAR)) || + ((addwhat & CC_READONLYS) && (hn->flags & PM_READONLY)) || + ((addwhat & CC_SPECIALS) && (hn->flags & PM_SPECIAL)) || + ((addwhat & CC_PARAMS) && !(hn->flags & PM_EXPORTED)))) || + ((( addwhat & CC_SHFUNCS) || + ( addwhat & CC_BUILTINS) || + ( addwhat & CC_EXTCMDS) || + ( addwhat & CC_RESWDS) || + ((addwhat & CC_ALREG) && !(hn->flags & ALIAS_GLOBAL)) || + ((addwhat & CC_ALGLOB) && (hn->flags & ALIAS_GLOBAL))) && + (((addwhat & CC_DISCMDS) && (hn->flags & DISABLED)) || + ((addwhat & CC_EXCMDS) && !(hn->flags & DISABLED)))) || + ((addwhat & CC_BINDINGS) && !(hn->flags & DISABLED))))) { + if (sl >= rpl + rsl) { + if (cp) + test = domatch(s, patcomp, 0); + else { + e = s + sl - rsl; + if ((test = !strncmp(s, rpre, rpl))) + test = !strcmp(e, rsuf); + } + } + if (!test && sl < lpl + lsl) + return; + if (!test && lpre && lsuf && sl >= lpl + lsl) { + e = s + sl - lsl; + if ((test = !strncmp(s, lpre, lpl))) + test = !strcmp(e, lsuf); + pl = lpl; + } + if (addwhat == CC_QUOTEFLAG) { + te = s + pl; + s = quotename(s, &e, te, &pl); + sl = strlen(s); + } + if (test) + haswhat |= HAS_MISC; + } + if (!test) + return; + + if (ispattern) { + t = s; + } else { + t = s += pl; + if (*e) + t = s = dupstrpfx(t, e - t); + } + + if (l == fmatches) { + bp = &fab; + ep = &fae; + sp = &fshortl; + fm = &ffirstm; + } else { + bp = &ab; + ep = &ae; + sp = &shortl; + fm = &firstm; + } + + if (!ispattern && *fm) { + if ((test = pfxlen(*fm, s)) < *bp) + *bp = test; + if ((test = sfxlen(*fm, s)) < *ep) + *ep = test; + if (*ep > *sp - *bp) + *ep = *sp - *bp; + } + + /* If we are doing a glob completion we store the whole string in * + * the list. Otherwise only the part that fits between the prefix * + * and the suffix is stored. */ + addlinknode(l, t); + if (!*fm) { + *bp = *ep = 10000; + *fm = t; + *sp = 100000; + } + if (!ispattern && (sl = strlen(t)) < *sp) { + *sp = sl; + if (l == fmatches) + fshortest = t; + else + shortest = t; + } +} + +#ifdef HAVE_NIS_PLUS +static int +match_username(nis_name table, nis_object *object, void *userdata) +{ + if (errflag) + return 1; + else { + static char buf[40]; + register entry_col *ec = + object->zo_data.objdata_u.en_data.en_cols.en_cols_val; + register int l = minimum(ec->ec_value.ec_value_len, 39); + + memcpy(buf, ec->ec_value.ec_value_val, l); + buf[l] = '\0'; + + addmatch(dupstring(buf), NULL); + } + return 0; +} +#else +# ifdef HAVE_NIS +static int +match_username(int status, char *key, int keylen, char *val, int vallen, dopestring *data) +{ + if (errflag || status != YP_TRUE) + return 1; + + if (vallen > keylen && val[keylen] == ':') { + val[keylen] = '\0'; + addmatch(dupstring(val), NULL); + } + return 0; +} +# endif /* HAVE_NIS */ +#endif /* HAVE_NIS_PLUS */ + +/**/ +static void +maketildelist(void) +{ +#if defined(HAVE_NIS) || defined(HAVE_NIS_PLUS) + FILE *pwf; + char buf[BUFSIZ], *p; + int skipping; + +# ifndef HAVE_NIS_PLUS + char domain[YPMAXDOMAIN]; + struct ypall_callback cb; + dopestring data; + + data.s = fpre; + data.len = fpl; + /* Get potential matches from NIS and cull those without local accounts */ + if (getdomainname(domain, YPMAXDOMAIN) == 0) { + cb.foreach = (int (*)()) match_username; + cb.data = (char *)&data; + yp_all(domain, PASSWD_MAP, &cb); + } +# else /* HAVE_NIS_PLUS */ + /* Maybe we should turn this string into a #define'd constant...? */ + + nis_list("passwd.org_dir", EXPAND_NAME|ALL_RESULTS|FOLLOW_LINKS|FOLLOW_PATH, + match_username, 0); +# endif + /* Don't forget the non-NIS matches from the flat passwd file */ + if ((pwf = fopen(PASSWD_FILE, "r")) != NULL) { + skipping = 0; + while (fgets(buf, BUFSIZ, pwf) != NULL) { + if (strchr(buf, '\n') != NULL) { + if (!skipping) { + if ((p = strchr(buf, ':')) != NULL) { + *p = '\0'; + addmatch(dupstring(buf), NULL); + } + } else + skipping = 0; + } else + skipping = 1; + } + fclose(pwf); + } +#else /* no NIS or NIS_PLUS */ + /* add all the usernames to the named directory table */ + nameddirtab->filltable(nameddirtab); +#endif + + scanhashtable(nameddirtab, 0, (addwhat==-1) ? 0 : ND_USERNAME, 0, + addhnmatch, 0); +} + +/* Copy the given string and remove backslashes from the copy and return it. */ + +/**/ +static char * +rembslash(char *s) +{ + char *t = s = dupstring(s); + + while (*s) + if (*s == '\\') { + chuck(s); + if (*s) + s++; + } else + s++; + + return t; +} + +/* This does the check for compctl -x `n' and `N' patterns. */ + +/**/ +static int +getcpat(char *wrd, int cpatindex, char *cpat, int class) +{ + char *str, *s, *t, *p; + int d = 0; + + if (!wrd || !*wrd) + return -1; + + cpat = rembslash(cpat); + + str = ztrdup(wrd); + untokenize(str); + if (!cpatindex) + cpatindex++, d = 0; + else if ((d = (cpatindex < 0))) + cpatindex = -cpatindex; + + for (s = d ? str + strlen(str) - 1 : str; + d ? (s >= str) : *s; + d ? s-- : s++) { + for (t = s, p = cpat; *t && *p; p++) { + if (class) { + if (*p == *s && !--cpatindex) { + zsfree(str); + return (int)(s - str + 1); + } + } else if (*t++ != *p) + break; + } + if (!class && !*p && !--cpatindex) { + zsfree(str); + t += wrd - str; + for (d = 0; --t >= wrd;) + if (! INULL(*t)) + d++; + return d; + } + } + zsfree(str); + return -1; +} + +/* This holds a pointer to the compctl we are using. */ + +static Compctl ccmain; + + +/* Find the compctl to use and return it. The first argument gives a * + * compctl to start searching with (if it is zero, the hash table is * + * searched). compadd is used to return a number of characters that * + * should be ignored at the beginning of the word and incmd is * + * non-zero if we are in command position. */ + +/**/ +static Compctl +get_ccompctl(Compctl occ, int *compadd, int incmd) +{ + Compctl compc, ret; + Compctlp ccp; + int t, i, a, b, tt, ra, rb, j, isf = 1; + Compcond or, cc; + char *s, *ss, *sc, *cmd = dupstring(cmdstr); + Comp comp; + + first_rec: + *compadd = 0; + ra = 0; + rb = clwnum - 1; + sc = NULL; + + if (!(ret = compc = occ)) { + if (isf) { + isf = 0; + ret = &cc_first; + } + else if (inwhat == IN_ENV) + /* Default completion for parameter values. */ + ret = &cc_default; + else if (inwhat == IN_MATH) { + /* Parameter names inside mathematical expression. */ + cc_dummy.mask = CC_PARAMS; + ret = &cc_dummy; + cc_dummy.refc = 10000; + } else if (inwhat == IN_COND) { + /* We try to be clever here: in conditions we complete option * + * names after a `-o', file names after `-nt', `-ot', and `-ef' * + * and file names and parameter names elsewhere. */ + s = clwpos ? clwords[clwpos - 1] : ""; + cc_dummy.mask = !strcmp("-o", s) ? CC_OPTIONS : + ((*s == '-' && s[1] && !s[2]) || + !strcmp("-nt", s) || + !strcmp("-ot", s) || + !strcmp("-ef", s)) ? CC_FILES : + (CC_FILES | CC_PARAMS); + ret = &cc_dummy; + cc_dummy.refc = 10000; + } else if (incmd) + ret = &cc_compos; + /* And in redirections or if there is no command name (and we are * + * not in command position) or if no special compctl was given * + * for the command: use default completion. Note that we first * + * search the complete command name and than the trailing * + * pathname component. */ + else if (linredir || + !(cmd && + (((ccp = (Compctlp) compctltab->getnode(compctltab, cmd)) && + (compc = ret = ccp->cc)) || + ((s = dupstring(cmd)) && remlpaths(&s) && + (ccp = (Compctlp) compctltab->getnode(compctltab, s)) && + (compc = ret = ccp->cc))))) + ret = &cc_default; + + ccmain = compc = ret; + ccmain->refc++; + } + /* The compctl we found has extended completion patterns, check them. */ + if (compc && compc->ext) { + compc = compc->ext; + /* This loops over the patterns separated by `--'. */ + for (t = 0; compc && !t; compc = compc->next) { + /* This loops over OR'ed patterns. */ + for (cc = compc->cond; cc && !t; cc = or) { + or = cc->or; + /* This loops over AND'ed patterns. */ + for (t = 1; cc && t; cc = cc->and) { + /* And this loops of [...] pairs. */ + for (t = i = 0; i < cc->n && !t; i++) { + s = NULL; + ra = 0; + rb = clwnum - 1; + switch (cc->type) { + case CCT_POS: + tt = clwpos; + goto cct_num; + case CCT_NUMWORDS: + tt = clwnum; + cct_num: + if ((a = cc->u.r.a[i]) < 0) + a += clwnum; + if ((b = cc->u.r.b[i]) < 0) + b += clwnum; + if (cc->type == CCT_POS) + ra = a, rb = b; + t = (tt >= a && tt <= b); + break; + case CCT_CURSUF: + case CCT_CURPRE: + s = ztrdup(clwpos < clwnum ? clwords[clwpos] : ""); + untokenize(s); + sc = rembslash(cc->u.s.s[i]); + a = strlen(sc); + if (!strncmp(s, sc, a)) { + *compadd = (cc->type == CCT_CURSUF ? a : 0); + t = 1; + } + break; + case CCT_CURSUB: + case CCT_CURSUBC: + if (clwpos < 0 || clwpos > clwnum) + t = 0; + else { + a = getcpat(clwords[clwpos], + cc->u.s.p[i], + cc->u.s.s[i], + cc->type == CCT_CURSUBC); + if (a != -1) + *compadd = a, t = 1; + } + break; + + case CCT_CURPAT: + case CCT_CURSTR: + tt = clwpos; + goto cct_str; + case CCT_WORDPAT: + case CCT_WORDSTR: + tt = 0; + cct_str: + if ((a = tt + cc->u.s.p[i]) < 0) + a += clwnum; + s = ztrdup((a < 0 || a >= clwnum) ? "" : + clwords[a]); + untokenize(s); + + if (cc->type == CCT_CURPAT || + cc->type == CCT_WORDPAT) { + tokenize(ss = dupstring(cc->u.s.s[i])); + t = ((comp = parsereg(ss)) && + domatch(s, comp, 0)); + } else + t = (!strcmp(s, rembslash(cc->u.s.s[i]))); + break; + case CCT_RANGESTR: + case CCT_RANGEPAT: + if (cc->type == CCT_RANGEPAT) + tokenize(sc = dupstring(cc->u.l.a[i])); + for (j = clwpos; j; j--) { + untokenize(s = ztrdup(clwords[j])); + if (cc->type == CCT_RANGESTR) + sc = rembslash(cc->u.l.a[i]); + if (cc->type == CCT_RANGESTR ? + !strncmp(s, sc, strlen(sc)) : + ((comp = parsereg(sc)) && + domatch(s, comp, 0))) { + zsfree(s); + ra = j + 1; + t = 1; + break; + } + zsfree(s); + } + if (t) { + if (cc->type == CCT_RANGEPAT) + tokenize(sc = dupstring(cc->u.l.b[i])); + for (j++; j < clwnum; j++) { + untokenize(s = ztrdup(clwords[j])); + if (cc->type == CCT_RANGESTR) + sc = rembslash(cc->u.l.b[i]); + if (cc->type == CCT_RANGESTR ? + !strncmp(s, sc, strlen(sc)) : + ((comp = parsereg(sc)) && + domatch(s, comp, 0))) { + zsfree(s); + rb = j - 1; + t = clwpos <= rb; + break; + } + zsfree(s); + } + } + s = NULL; + } + zsfree(s); + } + } + } + if (t) + break; + } + if (compc) + /* We found a matching pattern, we may return it. */ + ret = compc; + } + if (ret->subcmd) { + /* The thing we want to return has a subcmd flag (-l). */ + char **ow = clwords, *os = cmdstr, *ops = NULL; + int oldn = clwnum, oldp = clwpos; + + /* So we restrict the words-array. */ + if (ra >= clwnum) + ra = clwnum - 1; + if (ra < 1) + ra = 1; + if (rb >= clwnum) + rb = clwnum - 1; + if (rb < 1) + rb = 1; + clwnum = rb - ra + 1; + clwpos = clwpos - ra; + + if (ret->subcmd[0]) { + /* And probably put the command name given to the flag * + * in the array. */ + clwpos++; + clwnum++; + incmd = 0; + ops = clwords[ra - 1]; + clwords[ra - 1] = cmdstr = ret->subcmd; + clwords += ra - 1; + } else { + cmdstr = clwords[ra]; + incmd = !clwpos; + clwords += ra; + } + *compadd = 0; + if (ccmain != &cc_dummy) + freecompctl(ccmain); + /* Then we call this function recursively. */ + + ret = get_ccompctl(NULL, compadd, incmd); + /* And restore the things we changed. */ + clwords = ow; + cmdstr = os; + clwnum = oldn; + clwpos = oldp; + if (ops) + clwords[ra - 1] = ops; + } + if (ret == &cc_first) + goto first_rec; + return ret; +} + +/* Dump a hash table (without sorting). For each element the addmatch * + * function is called and at the beginning the addwhat variable is set. * + * This could be done using scanhashtable(), but this is easy and much * + * more efficient. */ + +/**/ +static void +dumphashtable(HashTable ht, int what) +{ + HashNode hn; + int i; + + addwhat = what; + + for (i = 0; i < ht->hsize; i++) + for (hn = ht->nodes[i]; hn; hn = hn->next) + addmatch(hn->nam, (char *) hn); + +} + +/* ScanFunc used by maketildelist() et al. */ + +/**/ +static void +addhnmatch(HashNode hn, int flags) +{ + addmatch(hn->nam, NULL); +} + +/* Perform expansion on the given string and return the result. * + * During this errors are not reported. */ + +/**/ +static char * +getreal(char *str) +{ + LinkList l = newlinklist(); + int ne = noerrs; + + noerrs = 1; + addlinknode(l, dupstring(str)); + prefork(l, 0); + noerrs = ne; + if (!errflag && nonempty(l)) + return ztrdup(peekfirst(l)); + errflag = 0; + + return ztrdup(str); +} + +/* This reads a directory and adds the files to the list of * + * matches. The parameters say which files should be added. */ + +/**/ +static void +gen_matches_files(int dirs, int execs, int all) +{ + DIR *d; + struct stat buf; + char *n, p[PATH_MAX], *q = NULL, *e; + LinkList l = NULL; + int ns = 0, ng = opts[NULLGLOB], test, aw = addwhat; + + addwhat = execs ? -8 : -5; + opts[NULLGLOB] = 1; + + if (*psuf) { + /* If there is a path suffix, check if it doesn't have a `*' or * + * `)' at the end (this is used to determine if we should use * + * globbing). */ + q = psuf + strlen(psuf) - 1; + ns = !(*q == Star || *q == Outpar); + l = newlinklist(); + /* And generate only directory names. */ + dirs = 1; + all = execs = 0; + } + /* Open directory. */ + if ((d = opendir((prpre && *prpre) ? prpre : "."))) { + /* If we search only special files, prepare a path buffer for stat. */ + if (!all && prpre) { + strcpy(p, prpre); + q = p + strlen(prpre); + } + /* Fine, now read the directory. */ + while ((n = zreaddir(d, 1)) && !errflag) { + /* Ignore files beginning with `.' unless the thing we found on * + * the command line also starts with a dot or GLOBDOTS is set. */ + if (*n != '.' || *fpre == '.' || isset(GLOBDOTS)) { + if (filecomp) + /* If we have a pattern for the filename check, use it. */ + test = domatch(n, filecomp, 0); + else { + /* Otherwise use the prefix and suffix strings directly. */ + e = n + strlen(n) - fsl; + if ((test = !strncmp(n, fpre, fpl))) + test = !strcmp(e, fsuf); + } + /* Filename didn't match? */ + if (!test) + continue; + if (!all) { + /* We still have to check the file type, so prepare * + * the path buffer by appending the filename. */ + strcpy(q, n); + /* And do the stat. */ + if (stat(p, &buf) < 0) + continue; + } + if (all || + (dirs && S_ISDIR(buf.st_mode)) || + (execs && S_ISREG(buf.st_mode) && (buf.st_mode&S_IXUGO))) { + /* If we want all files or the file has the right type... */ + if (*psuf) { + /* We have to test for a path suffix. */ + int o = strlen(p), tt; + + /* Append it to the path buffer. */ + strcpy(p + o, psuf); + + /* Do we have to use globbing? */ + if (ispattern || (ns && isset(GLOBCOMPLETE))) { + /* Yes, so append a `*' if needed. */ + if (ns) { + int tl = strlen(p); + + p[tl] = Star; + p[tl + 1] = '\0'; + } + /* Do the globbing... */ + remnulargs(p); + addlinknode(l, p); + globlist(l); + /* And see if that produced a filename. */ + tt = nonempty(l); + while (ugetnode(l)); + } else + /* Otherwise just check, if we have access * + * to the file. */ + tt = !access(p, F_OK); + + p[o] = '\0'; + if (tt) + /* Ok, we can add the filename to the * + * list of matches. */ + addmatch(dupstring(n), NULL); + } else + /* We want all files, so just add the name * + * to the matches. */ + addmatch(dupstring(n), NULL); + } + } + } + closedir(d); + } + opts[NULLGLOB] = ng; + addwhat = aw; +} + +/* This holds the explanation string we have to print. */ + +static char *expl; + +/* This holds the suffix to add (given with compctl -S). */ + +static char *ccsuffix; + +/* This s non-zero if the compctl -q flag was given (the suffix should * + * be removed when a space or something like that is typed next). */ + +static int remsuffix; + +/**/ +static void +quotepresuf(char **ps) +{ + if (*ps) { + char *p = quotename(*ps, NULL, NULL, NULL); + + if (p != *ps) { + zsfree(*ps); + *ps = ztrdup(p); + } + } +} + +/**/ +static void +docompletion(char *s, int lst, int incmd, int untokenized) +{ + static int delit, compadd; + + fixsuffix(); + HEAPALLOC { + pushheap(); + + /* Make sure we have the completion list and compctl. */ + if(makecomplist(s, incmd, &delit, &compadd, untokenized)) { + /* Error condition: feeeeeeeeeeeeep(). */ + feep(); + goto compend; + } + + if (lst == COMP_LIST_COMPLETE) + /* All this and the guy only wants to see the list, sigh. */ + showinglist = -2; + else { + /* We have matches. */ + if (delit) { + /* If we have to delete the word from the command line, * + * do it now. */ + wb -= compadd; + strcpy((char *)line + wb, (char *)line + we); + we = cs = wb; + } + if (nmatches > 1) + /* There are more than one match. */ + do_ambiguous(); + else if (nmatches == 1) { + /* Only one match. */ + do_single(amatches[0]); + invalidatelist(); + } + } + + /* Print the explanation string if needed. */ + if (!showinglist && expl && nmatches != 1) { + int up; + + if (!nmatches) + feep(); + trashzle(); + + clearflag = (isset(USEZLE) && !termflags && + (isset(ALWAYSLASTPROMPT) && zmult == 1)) || + (unset(ALWAYSLASTPROMPT) && zmult != 1); + + up = printfmt(expl, nmatches, 1); + + if (clearflag) + tcmultout(TCUP, TCMULTUP, up + nlnct); + else + putc('\n', shout); + fflush(shout); + } + compend: + ll = strlen((char *)line); + if (cs > ll) + cs = ll; + popheap(); + } LASTALLOC; +} + +/* Create the completion list. This is called whenever some bit of * + * completion code needs the list. If the list is already available * + * (validlist!=0), this function doesn't do anything. Along with * + * the list is maintained the prefixes/suffixes etc. When any of * + * this becomes invalid -- e.g. if some text is changed on the * + * command line -- invalidatelist() should be called, to set * + * validlist to zero and free up the memory used. This function * + * returns non-zero on error. delit and compadd return information * + * about bits of the command line that need to be deleted. */ + +/**/ +static int +makecomplist(char *s, int incmd, int *delit, int *compadd, int untokenized) +{ + Compctl cc = NULL; + int oloffs = offs, owe = we, owb = wb, ocs = cs, oll = ll, isf = 1; + int t, sf1, sf2, ooffs; + char *p, *sd = NULL, *tt, *s1, *s2, *os = NULL; + unsigned char *ol = NULL; + + /* If we already have a list from a previous execution of this * + * function, skip the list building code. */ + if (validlist) + return !nmatches; + + os = dupstring(s); + ol = (unsigned char *)dupstring((char *)line); + + xorrec: + + DPUTS(ll != strlen((char *) line), "BUG: xorrec: ll != strlen(line)"); + + /* Go to the end of the word if complete_in_word is not set. */ + if (unset(COMPLETEINWORD) && cs != we) + cs = we, offs = strlen(s); + + ispattern = haswhat = lastambig = 0; + patcomp = filecomp = NULL; + menucur = NULL; + shortest = NULL; + fshortest = NULL; + rpre = rsuf = lpre = lsuf = ppre = psuf = prpre = + fpre = fsuf = firstm = ffirstm = parampre = qparampre = NULL; + + /* Blank out the lists. */ + matches = newlinklist(); + fmatches = newlinklist(); + + /* If we don't have a compctl definition yet or we have a compctl * + * with extended completion, get it (or the next one, resp.). */ + if (!cc || cc->ext) + cc = get_ccompctl(cc, compadd, incmd); + + /* *compadd is the number of characters we have to ignore at the * + * beginning of the word. */ + wb += *compadd; + s += *compadd; + if ((offs -= *compadd) < 0) + /* It's bigger than our word prefix, so we can't help here... */ + return 1; + + /* Insert the prefix (compctl -P), if any. */ + if (cc->prefix) { + int pl = 0, sl = strlen(cc->prefix); + + if (*s) { + /* First find out how much of the prefix is already on the line. */ + sd = dupstring(s); + untokenize(sd); + pl = pfxlen(cc->prefix, sd); + s += pl; + } + if (pl < sl) { + int savecs = cs; + + /* Then insert the prefix. */ + cs = wb + pl; + inststrlen(cc->prefix + pl, 0, sl - pl); + cs = savecs + sl - pl; + } + /* And adjust the word beginning/end variables. */ + wb += sl; + we += sl - pl; + offs -= pl; + } + /* Does this compctl have a suffix (compctl -S)? */ + if ((ccsuffix = cc->suffix) && *ccsuffix) { + char *sdup = dupstring(ccsuffix); + int sl = strlen(sdup), suffixll; + + /* Ignore trailing spaces. */ + for (p = sdup + sl - 1; p >= sdup && *p == ' '; p--, sl--); + p[1] = '\0'; + + if (!sd) { + sd = dupstring(s); + untokenize(sd); + } + /* If the suffix is already there, ignore it (and don't add * + * it again). */ + if (*sd && (suffixll = strlen(sd)) >= sl && + offs <= suffixll - sl && !strcmp(sdup, sd + suffixll - sl)) { + ccsuffix = NULL; + haswhat |= HAS_SUFFIX; + s[suffixll - sl] = '\0'; + } + } + /* Do we have one of the special characters `~' and `=' at the beginning? */ + if ((ic = *s) != Tilde && ic != Equals) + ic = 0; + + /* Check if we have to complete a parameter name... */ + + /* Try to find a `$'. */ + for (p = s + offs; p > s && *p != String; p--); + if (*p == String) { + /* Handle $$'s */ + while (p > s && p[-1] == String) + p--; + while (p[1] == String && p[2] == String) + p += 2; + } + if (*p == String && p[1] != Inpar && p[1] != Inbrack) { + /* This is really a parameter expression (not $(...) or $[...]). */ + char *b = p + 1, *e = b; + int n = 0, br = 1; + + if (*b == Inbrace) { + /* If this is a ${...}, ignore the possible (...) flags. */ + b++, br++; + n = skipparens(Inpar, Outpar, &b); + } + + /* Ignore the stuff before the parameter name. */ + for (; *b; b++) + if (*b != '^' && *b != Hat && + *b != '=' && *b != Equals && + *b != '~' && *b != Tilde) + break; + if (*b == '#' || *b == Pound || *b == '+') + b++; + + e = b; + /* Find the end of the name. */ + if (*e == Quest || *e == Star || *e == String || *e == Qstring || + *e == '?' || *e == '*' || *e == '$' || + *e == '-' || *e == '!' || *e == '@') + e++; + else if (idigit(*e)) + while (idigit(*e)) + e++; + else if (iident(*e)) + while (iident(*e) || + (useglob && (*e == Star || *e == Quest))) + e++; + + /* Now make sure that the cursor is inside the name. */ + if (offs <= e - s && offs >= b - s && n <= 0) { + /* It is. */ + parambr = br - 1; + /* Get the prefix (anything up to the character before the name). */ + *e = '\0'; + parampre = ztrduppfx(s, b - s); + qparampre = ztrdup(quotename(parampre, NULL, NULL, NULL)); + untokenize(qparampre); + qparprelen = strlen(qparampre); + /* And adjust wb, we, and offs again. */ + offs -= b - s; + wb = cs - offs; + we = wb + e - b; + s = b; + /* And now make sure that we complete parameter names. */ + cc = ccmain = &cc_dummy; + cc_dummy.refc = 10000; + cc_dummy.mask = CC_PARAMS | CC_ENVVARS; + } + } + ooffs = offs; + /* If we have to ignore the word, do that. */ + if (cc->mask & CC_DELETE) { + *delit = 1; + *s = '\0'; + offs = 0; + } else + *delit = 0; + + /* Compute line prefix/suffix. */ + + lpl = offs; + lpre = zalloc(lpl + 1); + memcpy(lpre, s, lpl); + lpre[lpl] = '\0'; + p = quotename(lpre, NULL, NULL, NULL); + if (strcmp(p, lpre) && !strpfx(p, qword)) { + int l1, l2; + + backdel(l1 = cs - wb); + untokenize(p); + inststrlen(p, 1, l2 = strlen(p)); + we += l2 - l1; + } + lsuf = ztrdup(s + offs); + lsl = strlen(lsuf); + if (lsl && (p = quotename(lsuf, NULL, NULL, NULL)) && + (strcmp(p, lsuf) && !strsfx(p, qword))) { + int l1, l2; + + foredel(l1 = strlen(s + offs)); + untokenize(p); + inststrlen(p, 0, l2 = strlen(p)); + we += l2 - l1; + } + + /* First check for ~.../... */ + if (ic == Tilde) { + for (p = lpre + lpl; p > lpre; p--) + if (*p == '/') + break; + + if (*p == '/') + ic = 0; + } + /* Compute real prefix/suffix. */ + + noreal = !*delit; + for (p = lpre; *p && *p != String && *p != Tick; p++); + tt = ic && !parampre ? lpre + 1 : lpre; + rpre = (*p || *lpre == Tilde || *lpre == Equals) ? + (noreal = 0, getreal(tt)) : + ztrdup(tt); + + for (p = lsuf; *p && *p != String && *p != Tick; p++); + rsuf = *p ? (noreal = 0, getreal(lsuf)) : ztrdup(lsuf); + + /* Check if word is a pattern. */ + + for (s1 = NULL, sf1 = 0, p = rpre + (rpl = strlen(rpre)) - 1; + p >= rpre && (ispattern != 3 || !sf1); + p--) + if (itok(*p) && (p > rpre || (*p != Equals && *p != Tilde))) + ispattern |= sf1 ? 1 : 2; + else if (*p == '/') { + sf1++; + if (!s1) + s1 = p; + } + for (s2 = NULL, sf2 = t = 0, p = rsuf; *p && (!t || !sf2); p++) + if (itok(*p)) + t |= sf2 ? 4 : 2; + else if (*p == '/') { + sf2++; + if (!s2) + s2 = p; + } + ispattern = ispattern | t; + + /* But if we were asked not to do glob completion, we never treat the * + * thing as a pattern. */ + if (!useglob) + ispattern = 0; + + if (ispattern) { + /* The word should be treated as a pattern, so compute the matcher. */ + p = (char *)ncalloc(rpl + rsl + 2); + strcpy(p, rpre); + if (rpl && p[rpl - 1] != Star) { + p[rpl] = Star; + strcpy(p + rpl + 1, rsuf); + } else + strcpy(p + rpl, rsuf); + patcomp = parsereg(p); + } + if (!patcomp) { + untokenize(rpre); + untokenize(rsuf); + + rpl = strlen(rpre); + rsl = strlen(rsuf); + } + untokenize(lpre); + untokenize(lsuf); + + /* Handle completion of files specially (of course). */ + + if ((cc->mask & (CC_FILES | CC_DIRS | CC_COMMPATH)) || cc->glob) { + /* s1 and s2 point to the last/first slash in the prefix/suffix. */ + if (!s1) + s1 = rpre; + if (!s2) + s2 = rsuf + rsl; + + /* Compute the path prefix/suffix. */ + if (*s1 != '/') + ppre = ztrdup(""); + else + ppre = ztrduppfx(rpre, s1 - rpre + 1); + psuf = ztrdup(s2); + + /* And get the file prefix. */ + fpre = ztrdup(((s1 == s || s1 == rpre || ic) && + (*s != '/' || cs == wb)) ? s1 : s1 + 1); + /* And the suffix. */ + fsuf = ztrduppfx(rsuf, s2 - rsuf); + + if (useglob && (ispattern & 2)) { + int t2; + + /* We have to use globbing, so compute the pattern from * + * the file prefix and suffix with a `*' between them. */ + p = (char *)ncalloc((t2 = strlen(fpre)) + strlen(fsuf) + 2); + strcpy(p, fpre); + if ((!t2 || p[t2 - 1] != Star) && *fsuf != Star) + p[t2++] = Star; + strcpy(p + t2, fsuf); + filecomp = parsereg(p); + } + if (!filecomp) { + untokenize(fpre); + untokenize(fsuf); + + fpl = strlen(fpre); + fsl = strlen(fsuf); + } + addwhat = -1; + + /* Completion after `~', maketildelist adds the usernames * + * and named directories. */ + if (ic == Tilde) + maketildelist(); + else if (ic == Equals) { + /* Completion after `=', get the command names from * + * the cmdnamtab and aliases from aliastab. */ + if (isset(HASHLISTALL)) + cmdnamtab->filltable(cmdnamtab); + dumphashtable(cmdnamtab, -7); + dumphashtable(aliastab, -2); + } else { + /* Normal file completion... */ + if (ispattern & 1) { + /* But with pattern matching. */ + LinkList l = newlinklist(); + LinkNode n; + int ng = opts[NULLGLOB]; + + opts[NULLGLOB] = 1; + + addwhat = 0; + p = (char *)ncalloc(lpl + lsl + 3); + strcpy(p, lpre); + if (*lsuf != '*' && *lpre && lpre[lpl - 1] != '*') + strcat(p, "*"); + strcat(p, lsuf); + if (*lsuf && lsuf[lsl - 1] != '*' && lsuf[lsl - 1] != ')') + strcat(p, "*"); + + /* Do the globbing. */ + tokenize(p); + remnulargs(p); + addlinknode(l, p); + globlist(l); + + if (nonempty(l)) { + /* And add the resulting words. */ + haswhat |= HAS_PATHPAT; + for (n = firstnode(l); n; incnode(n)) + addmatch(getdata(n), NULL); + } + opts[NULLGLOB] = ng; + } else { + /* No pattern matching. */ + addwhat = CC_FILES; + if (cc->withd) { + prpre = tricat(cc->withd, "/", ppre); + } else + prpre = ztrdup(ppre); + + if (sf2) + /* We are in the path, so add only directories. */ + gen_matches_files(1, 0, 0); + else { + if (cc->mask & CC_FILES) + /* Add all files. */ + gen_matches_files(0, 0, 1); + else if (cc->mask & CC_COMMPATH) { + /* Completion of command paths. */ + if (sf1 || cc->withd) + /* There is a path prefix, so add * + * directories and executables. */ + gen_matches_files(1, 1, 0); + else { + /* No path prefix, so add the things * + * reachable via the PATH variable. */ + char **pc = path, *pp = prpre; + + for (; *pc; pc++) + if (!**pc || (pc[0][0] == '.' && !pc[0][1])) + break; + if (*pc) { + prpre = "./"; + gen_matches_files(1, 1, 0); + prpre = pp; + } + } + } else if (cc->mask & CC_DIRS) + gen_matches_files(1, 0, 0); + /* The compctl has a glob pattern (compctl -g). */ + if (cc->glob) { + int ns, pl = strlen(prpre), o; + char *g = dupstring(cc->glob), pa[PATH_MAX]; + char *p2, *p3; + int ne = noerrs, md = opts[MARKDIRS]; + + /* These are used in the globbing code to make * + * things a bit faster. */ + glob_pre = fpre; + glob_suf = fsuf; + + noerrs = 1; + addwhat = -6; + strcpy(pa, prpre); + o = strlen(pa); + opts[MARKDIRS] = 0; + + /* The compctl -g string may contain more than * + * one pattern, so we need a loop. */ + while (*g) { + LinkList l = newlinklist(); + int ng; + + /* Find the blank terminating the pattern. */ + while (*g && inblank(*g)) + g++; + /* Oops, we already reached the end of the + string. */ + if (!*g) + break; + for (p = g + 1; *p && !inblank(*p); p++) + if (*p == '\\' && p[1]) + p++; + /* Get the pattern string. */ + tokenize(g = dupstrpfx(g, p - g)); + if (*g == '=') + *g = Equals; + if (*g == '~') + *g = Tilde; + remnulargs(g); + if ((*g == Equals || *g == Tilde) && !cc->withd) { + /* The pattern has a `~' or `=' at the * + * beginning, so we expand this and use * + * the result. */ + filesub(&g, 0); + addlinknode(l, dupstring(g)); + } else if (*g == '/' && !cc->withd) + /* The pattern is a full path (starting * + * with '/'), so add it unchanged. */ + addlinknode(l, dupstring(g)); + else { + /* It's a simple pattern, so append it to * + * the path we have on the command line. */ + strcpy(pa + o, g); + addlinknode(l, dupstring(pa)); + } + /* Do the globbing. */ + ng = opts[NULLGLOB]; + opts[NULLGLOB] = 1; + globlist(l); + opts[NULLGLOB] = ng; + /* Get the results. */ + if (nonempty(l) && peekfirst(l)) { + for (p2 = (char *)peekfirst(l); *p2; p2++) + if (itok(*p2)) + break; + if (!*p2) { + if ((*g == Equals || *g == Tilde || + *g == '/') || cc->withd) { + /* IF the pattern started with `~', * + * `=', or `/', add the result only, * + * if it really matches what we have * + * on the line. * + * Do this if an initial directory * + * was specified, too. */ + while ((p2 = (char *)ugetnode(l))) + if (strpfx(prpre, p2)) + addmatch(p2 + pl, NULL); + } else { + /* Otherwise ignore the path we * + * prepended to the pattern. */ + while ((p2 = p3 = + (char *)ugetnode(l))) { + for (ns = sf1; *p3 && ns; p3++) + if (*p3 == '/') + ns--; + + addmatch(p3, NULL); + } + } + } + } + pa[o] = '\0'; + g = p; + } + glob_pre = glob_suf = NULL; + noerrs = ne; + opts[MARKDIRS] = md; + } + } + } + } + } + /* Use tricat() instead of dyncat() to get zalloc()'d memory. */ + if (ic) { + /* Now change the `~' and `=' tokens to the real characters so * + * that things starting with these characters will be added. */ + char *orpre = rpre; + + rpre = tricat("", (ic == Tilde) ? "~" : "=", rpre); + rpl++; + zsfree(orpre); + } + if (!ic && (cc->mask & CC_COMMPATH) && !*ppre && !*psuf) { + /* If we have to complete commands, add alias names, * + * shell functions and builtins too. */ + dumphashtable(aliastab, -3); + dumphashtable(reswdtab, -3); + dumphashtable(shfunctab, -3); + dumphashtable(builtintab, -3); + if (isset(HASHLISTALL)) + cmdnamtab->filltable(cmdnamtab); + dumphashtable(cmdnamtab, -3); + /* And parameter names if autocd and cdablevars are set. */ + if (isset(AUTOCD) && isset(CDABLEVARS)) + dumphashtable(paramtab, -4); + } + addwhat = (cc->mask & CC_QUOTEFLAG) ? -2 : CC_QUOTEFLAG; + + if (cc->mask & CC_NAMED) + /* Add named directories. */ + dumphashtable(nameddirtab, addwhat); + if (cc->mask & CC_OPTIONS) + /* Add option names. */ + dumphashtable(optiontab, addwhat); + if (cc->mask & CC_VARS) + /* And parameter names. */ + dumphashtable(paramtab, -9); + if (cc->mask & CC_BINDINGS) + /* And zle function names... */ + dumphashtable(thingytab, CC_BINDINGS); + if (cc->keyvar) { + /* This adds things given to the compctl -k flag * + * (from a parameter or a list of words). */ + char **usr = get_user_var(cc->keyvar); + + if (usr) + while (*usr) + addmatch(*usr++, NULL); + } + if (cc->mask & CC_USERS) + /* Add user names. */ + maketildelist(); + if (cc->func) { + /* This handles the compctl -K flag. */ + List list; + char **r; + int lv = lastval; + + /* Get the function. */ + if ((list = getshfunc(cc->func)) != &dummy_list) { + /* We have it, so build a argument list. */ + LinkList args = newlinklist(); + + addlinknode(args, cc->func); + + if (*delit) { + p = dupstrpfx(os, ooffs); + untokenize(p); + addlinknode(args, p); + p = dupstring(os + ooffs); + untokenize(p); + addlinknode(args, p); + } else { + addlinknode(args, lpre); + addlinknode(args, lsuf); + } + + /* This flag allows us to use read -l and -c. */ + incompctlfunc = 1; + /* Call the function. */ + doshfunc(list, args, 0, 1); + incompctlfunc = 0; + /* And get the result from the reply parameter. */ + if ((r = get_user_var("reply"))) + while (*r) + addmatch(*r++, NULL); + } + lastval = lv; + } + if (cc->mask & (CC_JOBS | CC_RUNNING | CC_STOPPED)) { + /* Get job names. */ + int i; + char *j, *jj; + + for (i = 0; i < MAXJOB; i++) + if (jobtab[i].stat & STAT_INUSE) { + int stopped = jobtab[i].stat & STAT_STOPPED; + + j = jj = dupstring(jobtab[i].procs->text); + /* Find the first word. */ + for (; *jj; jj++) + if (*jj == ' ') { + *jj = '\0'; + break; + } + if ((cc->mask & CC_JOBS) || + (stopped && (cc->mask & CC_STOPPED)) || + (!stopped && (cc->mask & CC_RUNNING))) + addmatch(j, NULL); + } + } + if (cc->str) { + /* Get the stuff from a compctl -s. */ + LinkList foo = newlinklist(); + LinkNode n; + int first = 1, ng = opts[NULLGLOB], oowe = we, oowb = wb; + char *tmpbuf; + + opts[NULLGLOB] = 1; + + /* Put the strin in the lexer buffer and call the lexer to * + * get the words we have to expand. */ + zleparse = 1; + lexsave(); + tmpbuf = (char *)halloc(strlen(cc->str) + 5); + sprintf(tmpbuf, "foo %s", cc->str); /* KLUDGE! */ + inpush(tmpbuf, 0, NULL); + strinbeg(); + noaliases = 1; + do { + ctxtlex(); + if (tok == ENDINPUT || tok == LEXERR) + break; + if (!first && tokstr && *tokstr) + addlinknode(foo, ztrdup(tokstr)); + first = 0; + } while (tok != ENDINPUT && tok != LEXERR); + noaliases = 0; + strinend(); + inpop(); + errflag = zleparse = 0; + lexrestore(); + /* Fine, now do full expansion. */ + prefork(foo, 0); + if (!errflag) { + globlist(foo); + if (!errflag) + /* And add the resulting words as matches. */ + for (n = firstnode(foo); n; incnode(n)) + addmatch((char *)n->dat, NULL); + } + opts[NULLGLOB] = ng; + we = oowe; + wb = oowb; + } + if (cc->hpat) { + /* We have a pattern to take things from the history. */ + Comp compc = NULL; + char *e, *h, hpatsav; + Histent he; + int i = curhist - 1, n = cc->hnum; + + /* Parse the pattern, if it isn't the null string. */ + if (*(cc->hpat)) { + char *thpat = dupstring(cc->hpat); + + tokenize(thpat); + compc = parsereg(thpat); + } + /* n holds the number of history line we have to search. */ + if (!n) + n = -1; + + /* Now search the history. */ + while (n-- && (he = quietgethist(i--))) { + int iwords; + for (iwords = 0; iwords < he->nwords; iwords++) { + h = he->text + he->words[iwords*2]; + e = he->text + he->words[iwords*2+1]; + hpatsav = *e; + *e = '\0'; + /* We now have a word from the history, ignore it * + * if it begins with a quote or `$'. */ + if (*h != '\'' && *h != '"' && *h != '`' && *h != '$' && + (!compc || domatch(h, compc, 0))) + /* Otherwise add it if it was matched. */ + addmatch(dupstring(h), NULL); + if (hpatsav) + *e = hpatsav; + } + } + } + if ((t = cc->mask & (CC_ARRAYS | CC_INTVARS | CC_ENVVARS | CC_SCALARS | + CC_READONLYS | CC_SPECIALS | CC_PARAMS))) + /* Add various flavours of parameters. */ + dumphashtable(paramtab, t); + if ((t = cc->mask & CC_SHFUNCS)) + /* Add shell functions. */ + dumphashtable(shfunctab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS))); + if ((t = cc->mask & CC_BUILTINS)) + /* Add builtins. */ + dumphashtable(builtintab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS))); + if ((t = cc->mask & CC_EXTCMDS)) + /* Add external commands */ + dumphashtable(cmdnamtab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS))); + if ((t = cc->mask & CC_RESWDS)) + /* Add reserved words */ + dumphashtable(reswdtab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS))); + if ((t = cc->mask & (CC_ALREG | CC_ALGLOB))) + /* Add the two types of aliases. */ + dumphashtable(aliastab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS))); + + /* If we have no matches, ignore fignore. */ + if (empty(matches)) { + matches = fmatches; + firstm = ffirstm; + shortest = fshortest; + ab = fab; + ae = fae; + shortl = fshortl; + } + + /* Make an array from the list of matches. */ + makearray(matches); + PERMALLOC { + amatches = arrdup(amatches); + if (firstm) + firstm = ztrdup(firstm); + /* And quote the prefixes/suffixes. */ + if (hasspecial(s)) { + zfree(lpre, lpl); + zfree(lsuf, lsl); + lpre = zalloc(lpl + 1); + memcpy(lpre, s, lpl); + lpre[lpl] = '\0'; + lsuf = ztrdup(s + offs); + quotepresuf(&lpre); + quotepresuf(&lsuf); + untokenize(lpre); + untokenize(lsuf); + } + quotepresuf(&fpre); + quotepresuf(&fsuf); + quotepresuf(&ppre); + quotepresuf(&psuf); + } LASTALLOC; + + if (!errflag && cc->ylist) { + /* generate the user-defined display list: if anything fails, * + * we silently allow the normal completion list to be used. */ + char **yaptr, *uv = NULL; + List list; + + if (cc->ylist[0] == '$' || cc->ylist[0] == '(') { + /* from variable */ + uv = cc->ylist + (cc->ylist[0] == '$'); + } else if ((list = getshfunc(cc->ylist)) != &dummy_list) { + /* from function: pass completions as arg list */ + LinkList args = newlinklist(); + int addlen = strlen(rpre) + strlen(rsuf) + 1; + + addlinknode(args, cc->ylist); + for (yaptr = amatches; *yaptr; yaptr++) { + /* can't use tricat(). rats. */ + char *ptr = (char *)halloc(addlen + strlen(*yaptr)); + sprintf(ptr, "%s%s%s", rpre, *yaptr, rsuf); + addlinknode(args, ptr); + } + + /* No harm in allowing read -l and -c here, too */ + incompctlfunc = 1; + doshfunc(list, args, 0, 1); + incompctlfunc = 0; + uv = "reply"; + } + if (uv && (yaptr = get_user_var(uv))) { + PERMALLOC { + aylist = arrdup(yaptr); + } LASTALLOC; + } + } + + /* Get the explanation string we will have to print: * + * do this here in case a -y function alters the messge */ + if ((expl = cc->explain)) { + if (cc->mask & CC_EXPANDEXPL && !parsestr(expl = dupstring(expl))) { + singsub(&expl); + untokenize(expl); + } + expl = ztrdup(expl); + } + + remsuffix = (cc->mask & CC_REMOVE); + ccsuffix = cc->suffix; + + validlist = 1; + if (nmatches && !errflag) + return 0; + + if ((isf || cc->xor) && !parampre) { + /* We found no matches, but there is a xor'ed completion: * + * fine, so go back and continue with that compctl. */ + errflag = 0; + cc = cc->xor; + isf = 0; + wb = owb; + we = owe; + cs = ocs; + ll = oll; + strcpy((char *)line, (char *)ol); + offs = oloffs; + s = dupstring(os); + free(amatches); + zsfree(rpre); + zsfree(rsuf); + zsfree(lpre); + zsfree(lsuf); + zsfree(ppre); + zsfree(psuf); + zsfree(fpre); + zsfree(fsuf); + zsfree(prpre); + zsfree(parampre); + zsfree(qparampre); + zsfree(firstm); + if (expl) + zsfree(expl); + expl = NULL; + if (aylist) + freearray(aylist); + aylist = NULL; + goto xorrec; + } + + /* No matches and xor'ed completion: restore the command line if * + * it was alredy quoted, which is the case when s is untokenized. */ + if (untokenized) + strcpy((char *)line, (char *)ol); + return 1; +} + +/* Invalidate the completion list. */ + +/**/ +void +invalidatelist(void) +{ + if(showinglist == -2) + listmatches(); + if(validlist) { + freearray(amatches); + if (aylist) + freearray(aylist); + aylist = NULL; + if (expl) + zsfree(expl); + expl = 0; + zsfree(rpre); + zsfree(rsuf); + zsfree(lpre); + zsfree(lsuf); + zsfree(ppre); + zsfree(psuf); + zsfree(fpre); + zsfree(fsuf); + zsfree(prpre); + zsfree(parampre); + zsfree(qparampre); + zsfree(firstm); + if (ccmain != &cc_dummy) + freecompctl(ccmain); + } + lastambig = menucmp = showinglist = validlist = 0; + menucur = NULL; +} + +/* Get the words from a variable or a compctl -k list. */ + +/**/ +static char ** +get_user_var(char *nam) +{ + if (!nam) + return NULL; + else if (*nam == '(') { + /* It's a (...) list, not a parameter name. */ + char *ptr, *s, **uarr, **aptr; + int count = 0, notempty = 0, brk = 0; + LinkList arrlist = newlinklist(); + + ptr = dupstring(nam); + s = ptr + 1; + while (*++ptr) { + if (*ptr == '\\' && ptr[1]) + chuck(ptr), notempty = 1; + else if (*ptr == ',' || inblank(*ptr) || *ptr == ')') { + if (*ptr == ')') + brk++; + if (notempty) { + *ptr = '\0'; + count++; + if (*s == '\n') + s++; + addlinknode(arrlist, s); + } + s = ptr + 1; + notempty = 0; + } else { + notempty = 1; + if(*ptr == Meta) + ptr++; + } + if (brk) + break; + } + if (!brk || !count) + return NULL; + *ptr = '\0'; + aptr = uarr = (char **)ncalloc(sizeof(char *) * (count + 1)); + + while ((*aptr++ = (char *)ugetnode(arrlist))); + uarr[count] = NULL; + return uarr; + } else { + /* Otherwise it should be a parameter name. */ + char **arr = NULL, *val; + if (!(arr = getaparam(nam)) && (val = getsparam(nam))) { + arr = (char **)ncalloc(2*sizeof(char *)); + arr[0] = val; + arr[1] = NULL; + } + return arr; + } + +} + +/* This is strcmp with ignoring backslashes. */ + +/**/ +static int +strbpcmp(const void *a, const void *b) +{ + char *aa = *((char **)a), *bb = *((char **)b); + + while (*aa && *bb) { + if (*aa == '\\') + aa++; + if (*bb == '\\') + bb++; + if (*aa != *bb) + return (int)(*aa - *bb); + if (*aa) + aa++; + if (*bb) + bb++; + } + return (int)(*aa - *bb); +} + +/* Make an array from a linked list */ + +/**/ +static void +makearray(LinkList l) +{ + char **ap, **bp, **cp; + LinkNode nod; + + /* Build an array for the matches. */ + ap = amatches = (char **)ncalloc(((nmatches = countlinknodes(l)) + 1) * + sizeof(char *)); + + /* And copy them into it. */ + for (nod = firstnode(l); nod; incnode(nod)) + *ap++ = (char *)getdata(nod); + *ap = NULL; + + /* Now sort the array. */ + qsort((void *) amatches, nmatches, sizeof(char *), + (int (*) _((const void *, const void *)))strbpcmp); + + /* And delete the ones that occur more than once. */ + for (ap = cp = amatches; *ap; ap++) { + *cp++ = *ap; + for (bp = ap; bp[1] && !strcmp(*ap, bp[1]); bp++); + ap = bp; + } + *cp = NULL; + nmatches = arrlen(amatches); +} + +/* Handle the case were we found more than one match. */ + +/**/ +static void +do_ambiguous(void) +{ + int p = (usemenu || ispattern), atend = (cs == we); + int inv = 0; + + menucmp = 0; + + /* If we have to insert the first match, call do_single(). This is * + * how REC_EXACT takes effect. We effectively turn the ambiguous * + * completion into an unambiguous one. */ + if (shortest && shortl == 0 && isset(RECEXACT) && + (usemenu == 0 || unset(AUTOMENU))) { + do_single(shortest); + invalidatelist(); + return; + } + /* Setting lastambig here means that the completion is ambiguous and * + * AUTO_MENU might want to start a menu completion next time round, * + * but this might be overridden below if we can complete an * + * unambiguous prefix. */ + lastambig = 1; + if(p) { + /* p is set if we are in a position to start using menu completion * + * due to one of the menu completion options, or due to the * + * menu-complete-word command, or due to using GLOB_COMPLETE which * + * does menu-style completion regardless of the setting of the * + * normal menu completion options. */ + do_ambig_menu(); + } else { + /* Sort-of general case: we have an ambiguous completion, and aren't * + * starting menu completion or doing anything really weird. We need * + * to insert any unambiguous prefix and suffix, if possible. */ + if(ab) + inststrlen(firstm, 1, ab); + if(ae && !atend) + inststrlen(firstm + strlen(firstm) - ae, 0, ae); + if(ab || (ae && !atend)) + inv = 1; + /* If the LIST_AMBIGUOUS option (meaning roughly `show a list only * + * if the completion is completely ambiguous') is set, and some * + * prefix was inserted, return now, bypassing the list-displaying * + * code. On the way, invalidate the list and note that we don't * + * want to enter an AUTO_MENU imediately. */ + if(isset(LISTAMBIGUOUS) && inv) { + invalidatelist(); + lastambig = 0; + return; + } + } + /* At this point, we might want a completion listing. Show the listing * + * if it is needed. */ + if (isset(LISTBEEP)) + feep(); + if (isset(AUTOLIST) && !amenu && !showinglist) + showinglist = -2; + if(inv) + invalidatelist(); +} + +/* This is a stat that ignores backslashes in the filename. The `ls' * + * parameter says if we have to do lstat() or stat(). I think this * + * should instead be done by use of a general function to expand a * + * filename (stripping backslashes), combined with the actual * + * (l)stat(). */ + +/**/ +static int +ztat(char *nam, struct stat *buf, int ls) +{ + char b[PATH_MAX], *p; + + for (p = b; p < b + sizeof(b) - 1 && *nam; nam++) + if (*nam == '\\' && nam[1]) + *p++ = *++nam; + else + *p++ = *nam; + *p = '\0'; + + return ls ? lstat(b, buf) : stat(b, buf); +} + +/* Insert a single match in the command line. */ + +/**/ +static void +do_single(char *str) +{ + int l; + int havesuff = 0; + + fixsuffix(); + + if (!menucur) { + /* We are currently not in a menu-completion, * + * so set the position variables. */ + if (ispattern) { + cs = we; + menupos = wb; + } else + menupos = cs; + menuwe = (cs == we) || isset(ALWAYSTOEND); + menuend = we; + } + /* If we are already in a menu-completion or if we have done a * + * glob completion, we have to delete some of the stuff on the * + * command line. */ + if (menucur) { + if (menuinsc) { + cs = menuend + lsl; + foredel(menuinsc); + } + l = menulen; + } else if (ispattern) + l = we - wb; + else + l = 0; + + menuinsc = 0; + cs = menupos; + foredel(l); + + /* And than we insert the new string. */ + inststrlen(str, 1, menulen = strlen(str)); + menuend = cs; + + cs += lsl; + + if (ccsuffix) { + /* There is a compctl -S suffix. Add it. */ + if (!(haswhat & HAS_SUFFIX) && *ccsuffix) { + havesuff = 1; + inststr(ccsuffix); + menuinsc = ztrlen(ccsuffix); + if (remsuffix && menuwe) + makesuffix(menuinsc); + } + havesuff = 1; + } else { + /* There is no user-specified suffix, * + * so generate one automagically. */ + if(parampre && parambr) { + /*{{*/ + /* Completing a parameter in braces. Add a removable `}' suffix. */ + inststrlen("}", 1, 1); + menuinsc++; + } + if(!(haswhat & HAS_MISC) || + (parampre && isset(AUTOPARAMSLASH))) { + /* If we have only filenames or we completed a parameter name * + * and AUTO_PARAM_SLASH is set, lets see if it is a directory. * + * If it is, we append a slash. */ + char *p; + struct stat buf; + + /* Build the path name. */ + if (ispattern || ic || parampre) { + int ne = noerrs; + + noerrs = 1; + + if (parampre) { + int pl = strlen(parampre); + p = (char *) ncalloc(pl + strlen(lpre) + strlen(str) + + strlen(lsuf) + 1); + sprintf(p, "%s%s%s%s", parampre, lpre, str, lsuf); + if (pl && p[pl-1] == Inbrace) + strcpy(p+pl-1, p+pl); + } + else if (ic) { + p = (char *) ncalloc(strlen(ppre) + strlen(fpre) + strlen(str) + + strlen(fsuf) + strlen(psuf) + 2); + sprintf(p, "%c%s%s%s%s%s", ic, + ppre, fpre, str, fsuf, psuf); + } + else + p = dupstring(str); + parsestr(p); + if (ic) + *p = ic; + singsub(&p); + + noerrs = ne; + } else { + p = (char *) ncalloc((prpre ? strlen(prpre) : 0) + strlen(fpre) + + strlen(str) + strlen(fsuf) + strlen(psuf) + 3); + sprintf(p, "%s%s%s%s%s", + (prpre && *prpre) ? prpre : "./", fpre, str, + fsuf, psuf); + } + /* And do the stat. */ + if (!ztat(p, &buf, 0) && S_ISDIR(buf.st_mode)) { + /* It is a directory, so add the slash. */ + havesuff = 1; + inststrlen("/", 1, 1); + menuinsc++; + if(menuwe && isset(AUTOREMOVESLASH)) { + makesuffix(1); + suffixlen['/'] = 1; + } + } + } + } + /* If completing in a brace expansion... */ + if(complinbrace) { + if(havesuff) { + /*{{*/ + /* If a suffix was added, and is removable, let * + * `,' and `}' remove it. */ + if(isset(AUTOPARAMKEYS)) + suffixlen[','] = suffixlen['}'] = suffixlen[256]; + } else { + /*{{*/ + /* Otherwise, add a `,' suffix, and let `}' remove it. */ + havesuff = 1; + inststrlen(",", 1, 1); + menuinsc++; + if(menuwe && isset(AUTOPARAMKEYS)) + suffixlen[','] = suffixlen['}'] = 1; + } + } else if(!menucmp && !havesuff) { + /* If we didn't add a suffix, add a space, unless we are * + * doing menu completion. */ + inststrlen(" ", 1, 1); + menuinsc++; + if(menuwe) + makesuffix(1); + } + if(menuwe && parampre && isset(AUTOPARAMKEYS)) + makeparamsuffix(parambr, menuinsc); + + if (!menuwe) + cs = menuend; +} + +/* This handles the beginning of menu-completion. */ + +/**/ +static void +do_ambig_menu(void) +{ + menucmp = 1; + menucur = NULL; + do_single(amatches[0]); + menucur = amatches; +} + +/* Return the length of the common prefix of s and t. */ + +/**/ +int +pfxlen(char *s, char *t) +{ + int i = 0; + + while (*s && *s == *t) + s++, t++, i++; + return i; +} + +/* Return the length of the common suffix of s and t. */ + +/**/ +static int +sfxlen(char *s, char *t) +{ + if (*s && *t) { + int i = 0; + char *s2 = s + strlen(s) - 1, *t2 = t + strlen(t) - 1; + + while (s2 >= s && t2 >= t && *s2 == *t2) + s2--, t2--, i++; + + return i; + } else + return 0; +} + +/* This is used to print the explanation string. * + * It returns the number of lines printed. */ + +/**/ +static int +printfmt(char *fmt, int n, int dopr) +{ + char *p = fmt, nc[DIGBUFSIZE]; + int l = 0, cc = 0; + + for (; *p; p++) { + /* Handle the `%' stuff (%% == %, %n == ). */ + if (*p == '%') { + if (*++p) { + switch (*p) { + case '%': + if (dopr) + putc('%', shout); + cc++; + break; + case 'n': + sprintf(nc, "%d", n); + if (dopr) + fprintf(shout, nc); + cc += strlen(nc); + break; + } + } else + break; + } else { + cc++; + if (*p == '\n') { + l += 1 + (cc / columns); + cc = 0; + } + if (dopr) + putc(*p, shout); + } + } + + return l + (cc / columns); +} + +/* List the matches. Note that the list entries are metafied. */ + +/**/ +void +listmatches(void) +{ + int longest = 1, fct, fw, colsz, t0, t1, ct, up, cl, xup = 0; + int off = 0, boff = 0, nboff = 0; + int of = (!aylist && isset(LISTTYPES) && !(haswhat & HAS_MISC)); + char **arr, **ap, sav; + int nfpl, nfsl, nlpl, nlsl; + int listmax = getiparam("LISTMAX"), litnl = 0; + size_t (*strlenfn) _((char const *)); + +#ifdef DEBUG + /* Sanity check */ + if(!validlist) { + showmsg("BUG: listmatches called with bogus list"); + return; + } +#endif + + /* Calculate lengths of prefixes/suffixes to be added */ + nfpl = fpre ? niceztrlen(fpre) : 0; + nfsl = fsuf ? niceztrlen(fsuf) : 0; + nlpl = lpre ? niceztrlen(lpre) : 0; + nlsl = lsuf ? niceztrlen(lsuf) : 0; + + /* Calculate the lengths of the prefixes/suffixes we have to ignore + during printing. */ + if (ispattern && !aylist && !(haswhat & (HAS_MISC | HAS_PATHPAT))) { + if (ppre && *ppre) + off = strlen(ppre); + if (psuf && *psuf) { + boff = strlen(psuf); + nboff = niceztrlen(psuf); + } + } + + /* Set the cursor below the prompt. */ + trashzle(); + showinglist = 0; + + clearflag = (isset(USEZLE) && !termflags && + (isset(ALWAYSLASTPROMPT) && zmult == 1)) || + (unset(ALWAYSLASTPROMPT) && zmult != 1); + + /* just to keep gcc happy */ + fw = colsz = up = 0; + if (aylist) { + arr = aylist; + /* If no literal newlines, the remaining code should use strlen() */ + strlenfn = (size_t (*) _((char const *)))strlen; + + /* The hard bit here is that we are handling newlines literally. * + * In fact, we are in principle handling all characters literally, * + * but it's quite enough work with just newlines. * + * If there are such, we give up trying to print the list as * + * columns and print as rows, counting the extra newlines. */ + ct = 0; + for (ap = arr; *ap; ap++) { + ct++; + if (strchr(*ap, '\n')) + litnl++; + } + if (litnl) { + colsz = ct; + up = colsz + nlnct - clearflag; + /* Count real newlines, as well as overflowing lines. */ + for (ap = arr; *ap; ap++) { + char *nlptr, *sptr = *ap; + while (sptr && *sptr) { + up += (nlptr = strchr(sptr, '\n')) + ? 1 + (nlptr-sptr)/columns + : strlen(sptr)/columns; + sptr = nlptr ? nlptr+1 : NULL; + } + } + } + } else { + arr = amatches; + ct = nmatches; + strlenfn = niceztrlen; + } + + + if (!litnl) { + /* Calculate the column width, the number of columns and the + number of lines. */ + for (ap = arr; *ap; ap++) + if ((cl = strlenfn(*ap + off) - nboff + + ((ispattern || aylist) ? 0 : + (!(haswhat & HAS_MISC) ? + nfpl + nfsl : nlpl + nlsl))) > longest) + longest = cl; + if (of) + longest++; + + fw = longest + 2; + fct = (columns + 1) / fw; + if (fct == 0) { + fct = 1; + colsz = ct; + up = colsz + nlnct - clearflag; + for (ap = arr; *ap; ap++) + up += (strlenfn(*ap + off) - nboff + of + + ((ispattern || aylist) ? 0 : + (!(haswhat & HAS_MISC) ? + nfpl + nfsl : nlpl + nlsl))) / columns; + } else { + colsz = (ct + fct - 1) / fct; + up = colsz + nlnct - clearflag + (ct == 0); + } + } + + /* Print the explanation string, if any. */ + if (expl) { + xup = printfmt(expl, ct, 1) + 1; + putc('\n', shout); + up += xup; + } + + /* Maybe we have to ask if the user wants to see the list. */ + if ((listmax && ct > listmax) || (!listmax && up >= lines)) { + int qup; + setterm(); + qup = printfmt("zsh: do you wish to see all %n possibilities? ", ct, 1); + fflush(shout); + if (getzlequery() != 'y') { + if (clearflag) { + putc('\r', shout); + tcmultout(TCUP, TCMULTUP, qup); + if (tccan(TCCLEAREOD)) + tcout(TCCLEAREOD); + tcmultout(TCUP, TCMULTUP, nlnct + xup); + } else + putc('\n', shout); + return; + } + if (clearflag) { + putc('\r', shout); + tcmultout(TCUP, TCMULTUP, qup); + if (tccan(TCCLEAREOD)) + tcout(TCCLEAREOD); + } else + putc('\n', shout); + settyinfo(&shttyinfo); + } + + /* Now print the matches. */ + for (t1 = 0; t1 != colsz; t1++) { + ap = arr + t1; + if (of) { + /* We have to print the file types. */ + while (*ap) { + int t2; + char *pb; + struct stat buf; + + /* Build the path name for the stat. */ + if (ispattern) { + int cut = strlen(*ap) - boff; + + sav = ap[0][cut]; + ap[0][cut] = '\0'; + nicezputs(*ap + off, shout); + t2 = niceztrlen(*ap + off); + ap[0][cut] = sav; + pb = *ap; + } else { + nicezputs(fpre, shout); + nicezputs(*ap, shout); + nicezputs(fsuf, shout); + t2 = nfpl + niceztrlen(*ap) + nfsl; + pb = (char *) halloc((prpre ? strlen(prpre) : 0) + 3 + + strlen(fpre) + strlen(*ap) + strlen(fsuf)); + sprintf(pb, "%s%s%s%s", + (prpre && *prpre) ? prpre : "./", fpre, *ap, fsuf); + } + if (ztat(pb, &buf, 1)) + putc(' ', shout); + else + /* Print the file type character. */ + putc(file_type(buf.st_mode), shout); + for (t0 = colsz; t0 && *ap; t0--, ap++); + if (*ap) + /* And add spaces to make the columns aligned. */ + for (++t2; t2 < fw; t2++) + putc(' ', shout); + } + } else + while (*ap) { + int t2; + + if (aylist) { + zputs(*ap, shout); + t2 = strlen(*ap); + } else if (ispattern) { + int cut = strlen(*ap) - boff; + + sav = ap[0][cut]; + ap[0][cut] = '\0'; + nicezputs(*ap + off, shout); + t2 = niceztrlen(*ap + off); + ap[0][cut] = sav; + } else if (!(haswhat & HAS_MISC)) { + nicezputs(fpre, shout); + nicezputs(*ap, shout); + nicezputs(fsuf, shout); + t2 = nfpl + niceztrlen(*ap) + nfsl; + } else { + nicezputs(lpre, shout); + nicezputs(*ap, shout); + nicezputs(lsuf, shout); + t2 = nlpl + niceztrlen(*ap) + nlsl; + } + for (t0 = colsz; t0 && *ap; t0--, ap++); + if (*ap) + for (; t2 < fw; t2++) + putc(' ', shout); + } + if (t1 != colsz - 1 || !clearflag) + putc('\n', shout); + } + if (clearflag) + /* Move the cursor up to the prompt, if always_last_prompt * + * is set and all that... */ + if (up < lines) { + tcmultout(TCUP, TCMULTUP, up); + showinglist = -1; + } else + clearflag = 0, putc('\n', shout); +} + +/* This is used to print expansions. */ + +/**/ +void +listlist(LinkList l) +{ + int hw = haswhat, ip = ispattern; + char *lp = lpre, *ls = lsuf; + int nm = nmatches, vl = validlist; + char **am = amatches, **ay = aylist; + char *ex = expl; + + haswhat = HAS_MISC; + ispattern = 0; + validlist = 1; + lpre = lsuf = ""; + aylist = NULL; + expl = NULL; + + makearray(l); + listmatches(); + showinglist = 0; + + expl = ex; + amatches = am; + aylist = ay; + nmatches = nm; + validlist = vl; + lpre = lp; + lsuf = ls; + ispattern = ip; + haswhat = hw; +} + +/* Expand the history references. */ + +/**/ +int +doexpandhist(void) +{ + unsigned char *ol; + int oll, ocs, ne = noerrs, err; + + DPUTS(useheap, "BUG: useheap in doexpandhist()"); + HEAPALLOC { + pushheap(); + metafy_line(); + oll = ll; + ocs = cs; + ol = (unsigned char *)dupstring((char *)line); + expanding = 1; + excs = cs; + ll = cs = 0; + lexsave(); + /* We push ol as it will remain unchanged */ + inpush((char *) ol, 0, NULL); + strinbeg(); + noaliases = 1; + noerrs = 1; + exlast = inbufct; + do { + ctxtlex(); + } while (tok != ENDINPUT && tok != LEXERR); + stophist = 2; + while (!lexstop) + hgetc(); + /* We have to save errflags because it's reset in lexrestore. Since * + * noerrs was set to 1 errflag is true if there was a habort() which * + * means that the expanded string is unusable. */ + err = errflag; + noerrs = ne; + noaliases = 0; + strinend(); + inpop(); + zleparse = 0; + lexrestore(); + expanding = 0; + + if (!err) { + cs = excs; + if (strcmp((char *)line, (char *)ol)) { + unmetafy_line(); + /* For vi mode -- reset the beginning-of-insertion pointer * + * to the beginning of the line. This seems a little silly, * + * if we are, for example, expanding "exec !!". */ + if (viinsbegin > findbol()) + viinsbegin = findbol(); + popheap(); + LASTALLOC_RETURN 1; + } + } + + strcpy((char *)line, (char *)ol); + ll = oll; + cs = ocs; + unmetafy_line(); + + popheap(); + } LASTALLOC; + return 0; +} + +/**/ +void +magicspace(void) +{ + c = ' '; + selfinsert(); + doexpandhist(); +} + +/**/ +void +expandhistory(void) +{ + if (!doexpandhist()) + feep(); +} + +static int cmdwb, cmdwe; + +/**/ +static char * +getcurcmd(void) +{ + int curlincmd; + char *s = NULL; + + DPUTS(useheap, "BUG: useheap in getcurcmd()"); + HEAPALLOC { + zleparse = 2; + lexsave(); + metafy_line(); + inpush(dupstrspace((char *) line), 0, NULL); + unmetafy_line(); + strinbeg(); + pushheap(); + do { + curlincmd = incmdpos; + ctxtlex(); + if (tok == ENDINPUT || tok == LEXERR) + break; + if (tok == STRING && curlincmd) { + zsfree(s); + s = ztrdup(tokstr); + cmdwb = ll - wordbeg; + cmdwe = ll + 1 - inbufct; + } + } + while (tok != ENDINPUT && tok != LEXERR && zleparse); + popheap(); + strinend(); + inpop(); + errflag = zleparse = 0; + lexrestore(); + } LASTALLOC; + return s; +} + +/**/ +void +processcmd(void) +{ + char *s; + int m = zmult; + + s = getcurcmd(); + if (!s) { + feep(); + return; + } + zmult = 1; + pushline(); + zmult = m; + inststr(bindk->nam); + inststr(" "); + untokenize(s); + HEAPALLOC { + inststr(quotename(s, NULL, NULL, NULL)); + } LASTALLOC; + zsfree(s); + done = 1; +} + +/**/ +void +expandcmdpath(void) +{ + int oldcs = cs, na = noaliases; + char *s, *str; + + noaliases = 1; + s = getcurcmd(); + noaliases = na; + if (!s || cmdwb < 0 || cmdwe < cmdwb) { + feep(); + return; + } + str = findcmd(s); + zsfree(s); + if (!str) { + feep(); + return; + } + cs = cmdwb; + foredel(cmdwe - cmdwb); + spaceinline(strlen(str)); + strncpy((char *)line + cs, str, strlen(str)); + cs = oldcs; + if (cs >= cmdwe - 1) + cs += cmdwe - cmdwb + strlen(str); + if (cs > ll) + cs = ll; + zsfree(str); +} + +/* Extra function added by AR Iano-Fletcher. */ +/* This is a expand/complete in the vein of wash. */ + +/**/ +void +expandorcompleteprefix(void) +{ + comppref = 1; + expandorcomplete(); + comppref = 0; +} diff --git a/Src/Zle/zle_utils.c b/Src/Zle/zle_utils.c new file mode 100644 index 000000000..8fe3e7f0b --- /dev/null +++ b/Src/Zle/zle_utils.c @@ -0,0 +1,650 @@ +/* + * zle_utils.c - miscellaneous line editor utilities + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "zle.mdh" +#include "zle_utils.pro" + +/* Primary cut buffer */ + +/**/ +struct cutbuffer cutbuf; + +/* Emacs-style kill buffer ring */ + +/**/ +struct cutbuffer kring[KRINGCT]; +/**/ +int 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. */ + +/**/ +struct cutbuffer vibuf[35]; + +/* the line before last mod (for undo purposes) */ + +/**/ +char *lastline; +/**/ +int lastlinesz, lastll; + +/* size of line buffer */ + +/**/ +int linesz; + +/* make sure that the line buffer has at least sz chars */ + +/**/ +void +sizeline(int sz) +{ + while (sz > linesz) + line = (unsigned char *)realloc(line, (linesz *= 4) + 2); +} + +/* insert space for ct chars at cursor position */ + +/**/ +void +spaceinline(int ct) +{ + int i; + + sizeline(ct + ll); + for (i = ll; --i >= cs;) + line[i + ct] = line[i]; + ll += ct; + line[ll] = '\0'; + + if (mark > cs) + mark += ct; +} + +/**/ +static void +shiftchars(int to, int cnt) +{ + if (mark >= to + cnt) + mark -= cnt; + else if (mark > to) + mark = to; + + while (to + cnt < ll) { + line[to] = line[to + cnt]; + to++; + } + line[ll = to] = '\0'; +} + +/**/ +void +backkill(int ct, int dir) +{ + int i = (cs -= ct); + + cut(i, ct, dir); + shiftchars(i, ct); +} + +/**/ +void +forekill(int ct, int dir) +{ + int i = cs; + + cut(i, ct, dir); + shiftchars(i, ct); +} + +/**/ +void +cut(int i, int ct, int dir) +{ + if (zmod.flags & MOD_VIBUF) { + struct cutbuffer *b = &vibuf[zmod.vibuf]; + + if (!(zmod.flags & MOD_VIAPP) || !b->buf) { + zfree(b->buf, b->len); + b->buf = (char *)zalloc(ct); + memcpy(b->buf, (char *) line + i, ct); + b->len = ct; + b->flags = vilinerange ? CUTBUFFER_LINE : 0; + } else { + int len = b->len; + + if(vilinerange) + b->flags |= CUTBUFFER_LINE; + b->buf = realloc(b->buf, ct + len + !!(b->flags & CUTBUFFER_LINE)); + if (b->flags & CUTBUFFER_LINE) + b->buf[len++] = '\n'; + memcpy(b->buf + len, (char *) line + i, ct); + b->len = len + ct; + } + return; + } else { + /* Save in "1, shifting "1-"8 along to "2-"9 */ + int n; + zfree(vibuf[34].buf, vibuf[34].len); + for(n=34; n>26; n--) + vibuf[n] = vibuf[n-1]; + vibuf[26].buf = (char *)zalloc(ct); + memcpy(vibuf[26].buf, (char *) line + i, ct); + vibuf[26].len = ct; + vibuf[26].flags = vilinerange ? CUTBUFFER_LINE : 0; + } + if (!cutbuf.buf) { + cutbuf.buf = ztrdup(""); + cutbuf.len = cutbuf.flags = 0; + } else if (!(lastcmd & ZLE_KILL)) { + kringnum = (kringnum + 1) % KRINGCT; + if (kring[kringnum].buf) + free(kring[kringnum].buf); + kring[kringnum] = cutbuf; + cutbuf.buf = ztrdup(""); + cutbuf.len = cutbuf.flags = 0; + } + if (dir) { + char *s = (char *)zalloc(cutbuf.len + ct); + + memcpy(s, (char *) line + i, ct); + memcpy(s + ct, cutbuf.buf, cutbuf.len); + free(cutbuf.buf); + cutbuf.buf = s; + cutbuf.len += ct; + } else { + cutbuf.buf = realloc(cutbuf.buf, cutbuf.len + ct); + memcpy(cutbuf.buf + cutbuf.len, (char *) line + i, ct); + cutbuf.len += ct; + } + if(vilinerange) + cutbuf.flags |= CUTBUFFER_LINE; + else + cutbuf.flags &= ~CUTBUFFER_LINE; +} + +/**/ +void +backdel(int ct) +{ + shiftchars(cs -= ct, ct); +} + +/**/ +void +foredel(int ct) +{ + shiftchars(cs, ct); +} + +/**/ +void +setline(char const *s) +{ + sizeline(strlen(s)); + strcpy((char *) line, s); + unmetafy((char *) line, &ll); + if ((cs = ll) && invicmdmode()) + cs--; +} + +/**/ +int +findbol(void) +{ + int x = cs; + + while (x > 0 && line[x - 1] != '\n') + x--; + return x; +} + +/**/ +int +findeol(void) +{ + int x = cs; + + while (x != ll && line[x] != '\n') + x++; + return x; +} + +/**/ +void +findline(int *a, int *b) +{ + *a = findbol(); + *b = findeol(); +} + +/* Search for needle in haystack. Haystack is a metafied string while * + * needle is unmetafied and len-long. Start the search at position * + * pos. Search forward if dir > 0 otherwise search backward. */ + +/**/ +char * +hstrnstr(char *haystack, int pos, char *needle, int len, int dir, int sens) +{ + char *s = haystack + pos; + + if (dir > 0) { + while (*s) { + if (metadiffer(s, needle, len) < sens) + return s; + s += 1 + (*s == Meta); + } + } else { + for (;;) { + if (metadiffer(s, needle, len) < sens) + return s; + if (s == haystack) + break; + s -= 1 + (s != haystack+1 && s[-2] == Meta); + } + } + return NULL; +} + +/* Query the user, and return a single character response. The * + * question is assumed to have been printed already, and the * + * cursor is left immediately after the response echoed. * + * (Might cause a problem if this takes it onto the next line.) * + * is interpreted as 'y'; any other control character is * + * interpreted as 'n'. If there are any characters in the * + * buffer, this is taken as a negative response, and no * + * characters are read. Case is folded. */ + +/**/ +int +getzlequery(void) +{ + int c; +#ifdef FIONREAD + int val; + + /* check for typeahead, which is treated as a negative response */ + ioctl(SHTTY, FIONREAD, (char *)&val); + if (val) { + putc('n', shout); + return 'n'; + } +#endif + + /* get a character from the tty and interpret it */ + c = getkey(0); + if (c == '\t') + c = 'y'; + else if (icntrl(c) || c == EOF) + c = 'n'; + else + c = tulower(c); + + /* echo response and return */ + putc(c, shout); + return c; +} + +/* Format a string, keybinding style. */ + +/**/ +char * +bindztrdup(char *str) +{ + int c, len = 1; + char *buf, *ptr, *ret; + + for(ptr = str; *ptr; ptr++) { + c = *ptr == Meta ? STOUC(*++ptr) ^ 32 : STOUC(*ptr); + if(c & 0x80) { + len += 3; + c &= 0x7f; + } + if(c < 32 || c == 0x7f) { + len++; + c ^= 64; + } + len += c == '\\' || c == '^'; + len++; + } + ptr = buf = zalloc(len); + for(; *str; str++) { + c = *str == Meta ? STOUC(*++str) ^ 32 : STOUC(*str); + if(c & 0x80) { + *ptr++ = '\\'; + *ptr++ = 'M'; + *ptr++ = '-'; + c &= 0x7f; + } + if(c < 32 || c == 0x7f) { + *ptr++ = '^'; + c ^= 64; + } + if(c == '\\' || c == '^') + *ptr++ = '\\'; + *ptr++ = c; + } + *ptr = 0; + ret = dquotedztrdup(buf); + zsfree(buf); + return ret; +} + +/* Display a metafied string, keybinding-style. */ + +/**/ +int +printbind(char *str, FILE *stream) +{ + char *b = bindztrdup(str); + int ret = zputs(b, stream); + + zsfree(b); + return ret; +} + +/* Display a message where the completion list normally goes. * + * The message must be metafied. */ + +/**/ +void +showmsg(char const *msg) +{ + char const *p; + int up = 0, cc = 0, c; + + trashzle(); + clearflag = isset(USEZLE) && !termflags && isset(ALWAYSLASTPROMPT); + + for(p = msg; (c = *p); p++) { + if(c == Meta) + c = *++p ^ 32; + if(c == '\n') { + putc('\n', shout); + up += 1 + cc / columns; + cc = 0; + } else { + char const *n = nicechar(c); + fputs(n, shout); + cc += strlen(n); + } + } + up += cc / columns; + + if (clearflag) { + putc('\r', shout); + tcmultout(TCUP, TCMULTUP, up + nlnct); + } else + putc('\n', shout); + showinglist = 0; +} + +/* handle the error flag */ + +/**/ +void +feep(void) +{ + feepflag = 1; +} + +/**/ +void +handlefeep(void) +{ + if(feepflag) + beep(); + feepflag = 0; +} + +/***************/ +/* undo system */ +/***************/ + +/* head of the undo list, and the current position */ + +static struct change *changes, *curchange; + +/* list of pending changes, not yet in the undo system */ + +static struct change *nextchanges, *endnextchanges; + +/**/ +void +initundo(void) +{ + nextchanges = NULL; + changes = curchange = zalloc(sizeof(*curchange)); + curchange->prev = curchange->next = NULL; + curchange->del = curchange->ins = NULL; + lastline = zalloc(lastlinesz = linesz); + memcpy(lastline, line, lastll = ll); +} + +/**/ +void +freeundo(void) +{ + freechanges(changes); + freechanges(nextchanges); + zfree(lastline, lastlinesz); +} + +/**/ +static void +freechanges(struct change *p) +{ + struct change *n; + + for(; p; p = n) { + n = p->next; + zsfree(p->del); + zsfree(p->ins); + zfree(p, sizeof(*p)); + } +} + +/* register pending changes in the undo system */ + +/**/ +void +handleundo(void) +{ + mkundoent(); + if(!nextchanges) + return; + setlastline(); + if(curchange->next) { + freechanges(curchange->next); + curchange->next = NULL; + zsfree(curchange->del); + zsfree(curchange->ins); + curchange->del = curchange->ins = NULL; + } + nextchanges->prev = curchange->prev; + if(curchange->prev) + curchange->prev->next = nextchanges; + else + changes = nextchanges; + curchange->prev = endnextchanges; + endnextchanges->next = curchange; + nextchanges = endnextchanges = NULL; +} + +/* add an entry to the undo system, if anything has changed */ + +/**/ +void +mkundoent(void) +{ + int pre, suf; + int sh = ll < lastll ? ll : lastll; + struct change *ch; + + if(lastll == ll && !memcmp(lastline, line, ll)) + return; + for(pre = 0; pre < sh && line[pre] == lastline[pre]; ) + pre++; + for(suf = 0; suf < sh - pre && + line[ll - 1 - suf] == lastline[lastll - 1 - suf]; ) + suf++; + ch = zalloc(sizeof(*ch)); + ch->next = NULL; + ch->hist = histline; + ch->off = pre; + if(suf + pre == lastll) + ch->del = NULL; + else + ch->del = metafy(lastline + pre, lastll - pre - suf, META_DUP); + if(suf + pre == ll) + ch->ins = NULL; + else + ch->ins = metafy((char *)line + pre, ll - pre - suf, META_DUP); + if(nextchanges) { + ch->flags = CH_PREV; + ch->prev = endnextchanges; + endnextchanges->flags |= CH_NEXT; + endnextchanges->next = ch; + } else { + nextchanges = ch; + ch->flags = 0; + ch->prev = NULL; + } + endnextchanges = ch; +} + +/* set lastline to match line */ + +/**/ +void +setlastline(void) +{ + if(lastlinesz != linesz) + lastline = realloc(lastline, lastlinesz = linesz); + memcpy(lastline, line, lastll = ll); +} + +/* move backwards through the change list */ + +/**/ +void +undo(void) +{ + handleundo(); + do { + if(!curchange->prev) { + feep(); + return; + } + unapplychange(curchange = curchange->prev); + } while(curchange->flags & CH_PREV); + setlastline(); +} + +/**/ +static void +unapplychange(struct change *ch) +{ + if(ch->hist != histline) { + remember_edits(); + setline(zle_get_event(histline = ch->hist)); + } + cs = ch->off; + if(ch->ins) + foredel(ztrlen(ch->ins)); + if(ch->del) { + char *c = ch->del; + + spaceinline(ztrlen(c)); + for(; *c; c++) + if(*c == Meta) + line[cs++] = STOUC(*++c) ^ 32; + else + line[cs++] = STOUC(*c); + } +} + +/* move forwards through the change list */ + +/**/ +void +redo(void) +{ + handleundo(); + do { + if(!curchange->next) { + feep(); + return; + } + applychange(curchange); + curchange = curchange->next; + } while(curchange->prev->flags & CH_NEXT); + setlastline(); +} + +/**/ +static void +applychange(struct change *ch) +{ + if(ch->hist != histline) { + remember_edits(); + setline(zle_get_event(histline = ch->hist)); + } + cs = ch->off; + if(ch->del) + foredel(ztrlen(ch->del)); + if(ch->ins) { + char *c = ch->ins; + + spaceinline(ztrlen(c)); + for(; *c; c++) + if(*c == Meta) + line[cs++] = STOUC(*++c) ^ 32; + else + line[cs++] = STOUC(*c); + } +} + +/* vi undo: toggle between the end of the undo list and the preceding point */ + +/**/ +void +viundochange(void) +{ + handleundo(); + if(curchange->next) { + do { + applychange(curchange); + curchange = curchange->next; + } while(curchange->next); + setlastline(); + } else + undo(); +} diff --git a/Src/Zle/zle_vi.c b/Src/Zle/zle_vi.c new file mode 100644 index 000000000..a599d8091 --- /dev/null +++ b/Src/Zle/zle_vi.c @@ -0,0 +1,925 @@ +/* + * zle_vi.c - vi-specific functions + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "zle.mdh" +#include "zle_vi.pro" + +/* != 0 if we're getting a vi range */ + +/**/ +int virangeflag; + +/* kludge to get cw and dw to work right */ + +/**/ +int wordflag; + +/* != 0 if we're killing lines into a buffer, vi-style */ + +/**/ +int vilinerange; + +/* last vi change buffer, for vi change repetition */ + +/**/ +int vichgbufsz, vichgbufptr, vichgflag; + +/**/ +char *vichgbuf; + +/* point where vi insert mode was last entered */ + +/**/ +int viinsbegin; + +static struct modifier lastmod; +static int inrepeat, vichgrepeat; + +/**/ +static void +startvichange(int im) +{ + if (im != -1) { + insmode = im; + vichgflag = 1; + } + if (inrepeat) { + zmod = lastmod; + inrepeat = vichgflag = 0; + vichgrepeat = 1; + } else { + lastmod = zmod; + if (vichgbuf) + free(vichgbuf); + vichgbuf = (char *)zalloc(vichgbufsz = 16); + vichgbuf[0] = c; + vichgbufptr = 1; + vichgrepeat = 0; + } +} + +/**/ +static void +startvitext(int im) +{ + startvichange(im); + selectkeymap("main", 1); + undoing = 0; + viinsbegin = cs; +} + +/**/ +int +vigetkey(void) +{ + Keymap mn = openkeymap("main"); + char m[3], *str; + Thingy cmd; + + if((c = getkey(0)) == EOF) { + feep(); + return -1; + } + + m[0] = c; + metafy(m, 1, META_NOALLOC); + if(mn) + cmd = keybind(mn, m, &str); + else + cmd = t_undefinedkey; + + if (!cmd || cmd == Th(z_sendbreak)) { + feep(); + return -1; + } else if (cmd == Th(z_quotedinsert)) { + if ((c = getkey(0)) == EOF) { + feep(); + return -1; + } + } else if(cmd == Th(z_viquotedinsert)) { + char sav = line[cs]; + + line[cs] = '^'; + refresh(); + c = getkey(0); + line[cs] = sav; + if(c == EOF) { + feep(); + return -1; + } + } else if (cmd == Th(z_vicmdmode)) + return -1; + return c; +} + +/**/ +static int +getvirange(int wf) +{ + int pos = cs; + 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; + feep(); + return -1; + } + if(k2 == bindk) + /* The command key is repeated: a number of lines is used. */ + dovilinerange(); + else + execzlefunc(k2); + if(vichgrepeat) + zmult = mult1; + else + zmult = mult1 * zmod.tmult; + } while(prefixflag); + 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 || ll != lastll || memcmp(line, lastline, ll)) { + histline = hist1; + memcpy(line, lastline, ll = lastll); + cs = pos; + feep(); + return -1; + } + + /* Can't handle an empty file. Also, if the movement command * + * failed, or didn't move, it is an error. */ + if (!ll || (cs == pos && virangeflag != 2)) { + feep(); + return -1; + } + + /* 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) + pos++; + + /* Get the range the right way round. cs is placed at the * + * start of the range, and pos (the return value of this * + * function) is the end. */ + if (cs > pos) { + int tmp = cs; + cs = pos; + pos = tmp; + } + + /* 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 cs. 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) { + int newcs = findbol(); + cs = pos; + pos = findeol(); + cs = newcs; + } + return pos; +} + +/**/ +static void +dovilinerange(void) +{ + int pos = cs, n = zmult; + + /* A number of lines is taken as the range. The current line * + * is included. If the repeat count is positive the lines go * + * downward, otherwise upward. The repeat count gives the * + * number of lines. */ + vilinerange = 1; + if (!n) { + feep(); + return; + } + if (n > 0) { + while(n-- && cs <= ll) + cs = findeol() + 1; + if (n != -1) { + cs = pos; + feep(); + return; + } + cs--; + } else { + while(n++ && cs >= 0) + cs = findbol() - 1; + if (n != 1) { + cs = pos; + feep(); + return; + } + cs++; + } + virangeflag = 2; +} + +/**/ +void +viaddnext(void) +{ + if (cs != findeol()) + cs++; + startvitext(1); +} + +/**/ +void +viaddeol(void) +{ + cs = findeol(); + startvitext(1); +} + +/**/ +void +viinsert(void) +{ + startvitext(1); +} + +/**/ +void +viinsertbol(void) +{ + vifirstnonblank(); + startvitext(1); +} + +/**/ +void +videlete(void) +{ + int c2; + + startvichange(1); + if ((c2 = getvirange(0)) != -1) { + forekill(c2 - cs, 0); + if (vilinerange && ll) { + if (cs == ll) + cs--; + foredel(1); + vifirstnonblank(); + } + } + vichgflag = 0; +} + +/**/ +void +videletechar(void) +{ + int n = zmult; + + startvichange(-1); + /* handle negative argument */ + if (n < 0) { + zmult = -n; + vibackwarddeletechar(); + zmult = n; + return; + } + /* it is an error to be on the end of line */ + if (cs == ll || line[cs] == '\n') { + feep(); + return; + } + /* 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() - cs) + n = findeol() - cs; + /* do the deletion */ + forekill(n, 0); +} + +/**/ +void +vichange(void) +{ + int c2; + + startvichange(1); + if ((c2 = getvirange(1)) != -1) { + forekill(c2 - cs, 0); + selectkeymap("main", 1); + viinsbegin = cs; + undoing = 0; + } +} + +/**/ +void +visubstitute(void) +{ + int n = zmult; + + startvichange(1); + if (n < 0) { + feep(); + return; + } + /* it is an error to be on the end of line */ + if (cs == ll || line[cs] == '\n') { + feep(); + return; + } + /* 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() - cs) + n = findeol() - cs; + /* do the substitution */ + forekill(n, 0); + startvitext(1); +} + +/**/ +void +vichangeeol(void) +{ + forekill(findeol() - cs, 0); + startvitext(1); +} + +/**/ +void +vichangewholeline(void) +{ + vifirstnonblank(); + vichangeeol(); +} + +/**/ +void +viyank(void) +{ + int oldcs = cs, c2; + + startvichange(1); + if ((c2 = getvirange(0)) != -1) + cut(cs, c2 - cs, 0); + vichgflag = 0; + cs = oldcs; +} + +/**/ +void +viyankeol(void) +{ + int x = findeol(); + + startvichange(-1); + if (x == cs) { + feep(); + return; + } + cut(cs, x - cs, 0); +} + +/**/ +void +viyankwholeline(void) +{ + int bol = findbol(), oldcs = cs; + int n = zmult; + + startvichange(-1); + if (n < 1) + return; + while(n--) { + if (cs > ll) { + feep(); + cs = oldcs; + return; + } + cs = findeol() + 1; + } + vilinerange = 1; + cut(bol, cs - bol - 1, 0); + cs = oldcs; +} + +/**/ +void +vireplace(void) +{ + startvitext(0); +} + +/* vi-replace-chars has some oddities relating to vi-repeat-change. In * + * the real vi, if one does 3r at the end of a line, it feeps without * + * reading the argument, and won't repeat the action. A successful rx * + * followed by 3. at the end of a line (or 3rx followed by . at the end * + * of a line) will obviously feep after the ., even though it has the * + * argument available. Here repeating is tied very closely to argument * + * reading, so some trickery is needed to emulate this. When repeating * + * a change, we always read the argument normally, even if the count * + * was bad. When recording a change for repeating, and a bad count is * + * given, we squash the repeat buffer to avoid repeating the partial * + * command; we've lost the previous change, but that can't be avoided * + * without a rewrite of the repeat code. */ + +/**/ +void +vireplacechars(void) +{ + int ch, n = zmult; + + startvichange(1); + /* check argument range */ + if (n < 1 || n + cs > findeol()) { + if(vichgrepeat) { + int ofeep = feepflag; + vigetkey(); + feepflag = ofeep; + } + if(vichgflag) { + free(vichgbuf); + vichgbuf = NULL; + vichgflag = 0; + } + feep(); + return; + } + /* get key */ + if((ch = vigetkey()) == -1) { + vichgflag = 0; + feep(); + return; + } + /* do change */ + if (ch == '\r' || ch == '\n') { + /* handled specially */ + cs += n - 1; + backkill(n - 1, 0); + line[cs++] = '\n'; + } else { + while (n--) + line[cs++] = ch; + cs--; + } + vichgflag = 0; +} + +/**/ +void +vicmdmode(void) +{ + if (invicmdmode() || selectkeymap("vicmd", 0)) + feep(); + undoing = 1; + vichgflag = 0; + if (cs != findbol()) + cs--; +} + +/**/ +void +viopenlinebelow(void) +{ + cs = findeol(); + spaceinline(1); + line[cs++] = '\n'; + startvitext(1); +} + +/**/ +void +viopenlineabove(void) +{ + cs = findbol(); + spaceinline(1); + line[cs] = '\n'; + startvitext(1); +} + +/**/ +void +vioperswapcase(void) +{ + int oldcs, c2; + + /* get the range */ + startvichange(1); + if ((c2 = getvirange(0)) != -1) { + oldcs = cs; + /* swap the case of all letters within range */ + while (cs < c2) { + if (islower(line[cs])) + line[cs] = tuupper(line[cs]); + else if (isupper(line[cs])) + line[cs] = tulower(line[cs]); + cs++; + } + /* go back to the first line of the range */ + cs = oldcs; + vifirstnonblank(); + } + vichgflag = 0; +} + +/**/ +void +virepeatchange(void) +{ + /* make sure we have a change to repeat */ + if (!vichgbuf || vichgflag) { + feep(); + return; + } + /* restore or update the saved count and buffer */ + if (zmod.flags & MOD_MULT) { + lastmod.mult = zmod.mult; + lastmod.flags |= MOD_MULT; + } + if (zmod.flags & MOD_VIBUF) { + lastmod.vibuf = zmod.vibuf; + lastmod.flags = (lastmod.flags & ~MOD_VIAPP) | + MOD_VIBUF | (zmod.flags & MOD_VIAPP); + } + /* repeat the command */ + inrepeat = 1; + ungetkeys(vichgbuf, vichgbufptr); +} + +/**/ +void +viindent(void) +{ + int oldcs = cs, c2; + + /* get the range */ + startvichange(1); + if ((c2 = getvirange(0)) == -1) { + vichgflag = 0; + return; + } + vichgflag = 0; + /* must be a line range */ + if (!vilinerange) { + feep(); + cs = oldcs; + return; + } + oldcs = cs; + /* add a tab to the beginning of each line within range */ + while (cs < c2) { + spaceinline(1); + line[cs] = '\t'; + cs = findeol() + 1; + } + /* go back to the first line of the range */ + cs = oldcs; + vifirstnonblank(); +} + +/**/ +void +viunindent(void) +{ + int oldcs = cs, c2; + + /* get the range */ + startvichange(1); + if ((c2 = getvirange(0)) == -1) { + vichgflag = 0; + return; + } + vichgflag = 0; + /* must be a line range */ + if (!vilinerange) { + feep(); + cs = oldcs; + return; + } + oldcs = cs; + /* remove a tab from the beginning of each line within range */ + while (cs < c2) { + if (line[cs] == '\t') + foredel(1); + cs = findeol() + 1; + } + /* go back to the first line of the range */ + cs = oldcs; + vifirstnonblank(); +} + +/**/ +void +vibackwarddeletechar(void) +{ + int n = zmult; + + if (invicmdmode()) + startvichange(-1); + /* handle negative argument */ + if (n < 0) { + zmult = -n; + videletechar(); + zmult = n; + return; + } + /* It is an error to be at the beginning of the line, or (in * + * insert mode) to delete past the beginning of insertion. */ + if ((!invicmdmode() && cs - n < viinsbegin) || cs == findbol()) { + feep(); + return; + } + /* Put argument into the acceptable range -- it is not an error to * + * specify a greater count than the number of available characters. */ + if (n > cs - findbol()) + n = cs - findbol(); + /* do the deletion */ + backkill(n, 1); +} + +/**/ +void +vikillline(void) +{ + if (viinsbegin > cs) { + feep(); + return; + } + backdel(cs - viinsbegin); +} + +/**/ +void +viputbefore(void) +{ + Cutbuffer buf = &cutbuf; + int n = zmult; + + startvichange(-1); + if (n < 0) + return; + if (zmod.flags & MOD_VIBUF) + buf = &vibuf[zmod.vibuf]; + if (!buf->buf) { + feep(); + return; + } + if(buf->flags & CUTBUFFER_LINE) { + cs = findbol(); + spaceinline(buf->len + 1); + memcpy((char *)line + cs, buf->buf, buf->len); + line[cs + buf->len] = '\n'; + vifirstnonblank(); + } else { + while (n--) { + spaceinline(buf->len); + memcpy((char *)line + cs, buf->buf, buf->len); + cs += buf->len; + } + if (cs) + cs--; + } +} + +/**/ +void +viputafter(void) +{ + Cutbuffer buf = &cutbuf; + int n = zmult; + + startvichange(-1); + if (n < 0) + return; + if (zmod.flags & MOD_VIBUF) + buf = &vibuf[zmod.vibuf]; + if (!buf->buf) { + feep(); + return; + } + if(buf->flags & CUTBUFFER_LINE) { + cs = findeol(); + spaceinline(buf->len + 1); + line[cs++] = '\n'; + memcpy((char *)line + cs, buf->buf, buf->len); + vifirstnonblank(); + } else { + if (cs != findeol()) + cs++; + while (n--) { + spaceinline(buf->len); + memcpy((char *)line + cs, buf->buf, buf->len); + cs += buf->len; + } + if (cs) + cs--; + } + +} + +/**/ +void +vijoin(void) +{ + int x; + + startvichange(-1); + if ((x = findeol()) == ll) { + feep(); + return; + } + cs = x + 1; + for (x = 1; cs != ll && iblank(line[cs]); cs++, x++); + backdel(x); + if (cs && iblank(line[cs-1])) + cs--; + else { + spaceinline(1); + line[cs] = ' '; + } +} + +/**/ +void +viswapcase(void) +{ + int eol, n = zmult; + + startvichange(-1); + if (n < 1) + return; + eol = findeol(); + while (cs < eol && n--) { + if (islower(line[cs])) + line[cs] = tuupper(line[cs]); + else if (isupper(line[cs])) + line[cs] = tulower(line[cs]); + cs++; + } + if (cs && cs == eol) + cs--; +} + +/**/ +void +vicapslockpanic(void) +{ + beep(); + statusline = "press a lowercase key to continue"; + statusll = strlen(statusline); + refresh(); + while (!islower(getkey(0))); + statusline = NULL; +} + +/**/ +void +visetbuffer(void) +{ + int ch; + + if ((zmod.flags & MOD_VIBUF) || + (((ch = getkey(0)) < '1' || ch > '9') && + (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z'))) { + feep(); + return; + } + if (ch >= 'A' && ch <= 'Z') /* needed in cut() */ + zmod.flags |= MOD_VIAPP; + else + zmod.flags &= ~MOD_VIAPP; + zmod.vibuf = tulower(ch) + (idigit(ch) ? -'1' + 26 : -'a'); + zmod.flags |= MOD_VIBUF; + prefixflag = 1; +} + +/**/ +void +vikilleol(void) +{ + int n = findeol() - cs; + + startvichange(-1); + if (!n) { + /* error -- line already empty */ + feep(); + return; + } + /* delete to end of line */ + forekill(findeol() - cs, 0); +} + +/**/ +void +vipoundinsert(void) +{ + int oldcs = cs; + + startvichange(-1); + vifirstnonblank(); + if(line[cs] != '#') { + spaceinline(1); + line[cs] = '#'; + if(cs <= viinsbegin) + viinsbegin++; + cs = oldcs + (cs <= oldcs); + } else { + foredel(1); + if (cs < viinsbegin) + viinsbegin--; + cs = oldcs - (cs < oldcs); + } +} + +/**/ +void +viquotedinsert(void) +{ +#ifndef HAS_TIO + struct sgttyb sob; +#endif + + spaceinline(1); + line[cs] = '^'; + refresh(); +#ifndef HAS_TIO + sob = shttyinfo.sgttyb; + sob.sg_flags = (sob.sg_flags | RAW) & ~ECHO; + ioctl(SHTTY, TIOCSETN, &sob); +#endif + c = getkey(0); +#ifndef HAS_TIO + setterm(); +#endif + foredel(1); + if(c < 0) + feep(); + else + selfinsert(); +} + +/* the 0 key in vi: continue a repeat count in the manner of * + * digit-argument if possible, otherwise do vi-beginning-of-line. */ + +/**/ +void +vidigitorbeginningofline(void) +{ + if(zmod.flags & MOD_TMULT) + digitargument(); + else { + removesuffix(); + invalidatelist(); + vibeginningofline(); + } +} diff --git a/Src/Zle/zle_widget.sed b/Src/Zle/zle_widget.sed new file mode 100644 index 000000000..635322b42 --- /dev/null +++ b/Src/Zle/zle_widget.sed @@ -0,0 +1,7 @@ +/^ *W(/{ + s/[^,]*, *t_/ wi_/ + s/ *,.*/,/ + P + s/ wi_\(.*\),/#define w_\1 (\&widgets[wi_\1])/ + P +} diff --git a/Src/Zle/zle_word.c b/Src/Zle/zle_word.c new file mode 100644 index 000000000..923216ef8 --- /dev/null +++ b/Src/Zle/zle_word.c @@ -0,0 +1,477 @@ +/* + * zle_word.c - word-related editor functions + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "zle.mdh" +#include "zle_word.pro" + +/**/ +void +forwardword(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + backwardword(); + zmult = n; + return; + } + while (n--) { + while (cs != ll && iword(line[cs])) + cs++; + if (wordflag && !n) + return; + while (cs != ll && !iword(line[cs])) + cs++; + } +} + +/**/ +void +viforwardword(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + backwardword(); + zmult = n; + return; + } + while (n--) { + if (iident(line[cs])) + while (cs != ll && iident(line[cs])) + cs++; + else + while (cs != ll && !iident(line[cs]) && !iblank(line[cs])) + cs++; + if (wordflag && !n) + return; + while (cs != ll && iblank(line[cs])) + cs++; + } +} + +/**/ +void +viforwardblankword(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + vibackwardblankword(); + zmult = n; + return; + } + while (n--) { + while (cs != ll && !iblank(line[cs])) + cs++; + if (wordflag && !n) + return; + while (cs != ll && iblank(line[cs])) + cs++; + } +} + +/**/ +void +emacsforwardword(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + emacsbackwardword(); + zmult = n; + return; + } + while (n--) { + while (cs != ll && !iword(line[cs])) + cs++; + if (wordflag && !n) + return; + while (cs != ll && iword(line[cs])) + cs++; + } +} + +/**/ +void +viforwardblankwordend(void) +{ + int n = zmult; + + if (n < 0) + return; + while (n--) { + while (cs != ll && iblank(line[cs + 1])) + cs++; + while (cs != ll && !iblank(line[cs + 1])) + cs++; + } + if (cs != ll && virangeflag) + cs++; +} + +/**/ +void +viforwardwordend(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + backwardword(); + zmult = n; + return; + } + while (n--) { + if (iblank(line[cs + 1])) + while (cs != ll && iblank(line[cs + 1])) + cs++; + if (iident(line[cs + 1])) + while (cs != ll && iident(line[cs + 1])) + cs++; + else + while (cs != ll && !iident(line[cs + 1]) && !iblank(line[cs + 1])) + cs++; + } + if (cs != ll && virangeflag) + cs++; +} + +/**/ +void +backwardword(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + forwardword(); + zmult = n; + return; + } + while (n--) { + while (cs && !iword(line[cs - 1])) + cs--; + while (cs && iword(line[cs - 1])) + cs--; + } +} + +/**/ +void +vibackwardword(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + backwardword(); + zmult = n; + return; + } + while (n--) { + while (cs && iblank(line[cs - 1])) + cs--; + if (iident(line[cs - 1])) + while (cs && iident(line[cs - 1])) + cs--; + else + while (cs && !iident(line[cs - 1]) && !iblank(line[cs - 1])) + cs--; + } +} + +/**/ +void +vibackwardblankword(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + viforwardblankword(); + zmult = n; + return; + } + while (n--) { + while (cs && iblank(line[cs - 1])) + cs--; + while (cs && !iblank(line[cs - 1])) + cs--; + } +} + +/**/ +void +emacsbackwardword(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + emacsforwardword(); + zmult = n; + return; + } + while (n--) { + while (cs && !iword(line[cs - 1])) + cs--; + while (cs && iword(line[cs - 1])) + cs--; + } +} + +/**/ +void +backwarddeleteword(void) +{ + int x = cs, n = zmult; + + if (n < 0) { + zmult = -n; + deleteword(); + zmult = n; + return; + } + while (n--) { + while (x && !iword(line[x - 1])) + x--; + while (x && iword(line[x - 1])) + x--; + } + backdel(cs - x); +} + +/**/ +void +vibackwardkillword(void) +{ + int x = cs, lim = (viinsbegin > findbol()) ? viinsbegin : findbol(); + int n = zmult; + + if (n < 0) { + feep(); + return; + } +/* this taken from "vibackwardword" */ + while (n--) { + while ((x > lim) && iblank(line[x - 1])) + x--; + if (iident(line[x - 1])) + while ((x > lim) && iident(line[x - 1])) + x--; + else + while ((x > lim) && !iident(line[x - 1]) && !iblank(line[x - 1])) + x--; + } + backkill(cs - x, 1); +} + +/**/ +void +backwardkillword(void) +{ + int x = cs; + int n = zmult; + + if (n < 0) { + zmult = -n; + killword(); + zmult = n; + return; + } + while (n--) { + while (x && !iword(line[x - 1])) + x--; + while (x && iword(line[x - 1])) + x--; + } + backkill(cs - x, 1); +} + +/**/ +void +upcaseword(void) +{ + int n = zmult; + int neg = n < 0, ocs = cs; + + if (neg) + n = -n; + while (n--) { + while (cs != ll && !iword(line[cs])) + cs++; + while (cs != ll && iword(line[cs])) { + line[cs] = tuupper(line[cs]); + cs++; + } + } + if (neg) + cs = ocs; +} + +/**/ +void +downcaseword(void) +{ + int n = zmult; + int neg = n < 0, ocs = cs; + + if (neg) + n = -n; + while (n--) { + while (cs != ll && !iword(line[cs])) + cs++; + while (cs != ll && iword(line[cs])) { + line[cs] = tulower(line[cs]); + cs++; + } + } + if (neg) + cs = ocs; +} + +/**/ +void +capitalizeword(void) +{ + int first, n = zmult; + int neg = n < 0, ocs = cs; + + if (neg) + n = -n; + while (n--) { + first = 1; + while (cs != ll && !iword(line[cs])) + cs++; + while (cs != ll && iword(line[cs]) && !isalpha(line[cs])) + cs++; + while (cs != ll && iword(line[cs])) { + line[cs] = (first) ? tuupper(line[cs]) : tulower(line[cs]); + first = 0; + cs++; + } + } + if (neg) + cs = ocs; +} + +/**/ +void +deleteword(void) +{ + int x = cs; + int n = zmult; + + if (n < 0) { + zmult = -n; + backwarddeleteword(); + zmult = n; + return; + } + while (n--) { + while (x != ll && !iword(line[x])) + x++; + while (x != ll && iword(line[x])) + x++; + } + foredel(x - cs); +} + +/**/ +void +killword(void) +{ + int x = cs; + int n = zmult; + + if (n < 0) { + zmult = -n; + backwardkillword(); + zmult = n; + return; + } + while (n--) { + while (x != ll && !iword(line[x])) + x++; + while (x != ll && iword(line[x])) + x++; + } + forekill(x - cs, 0); +} + +/**/ +void +transposewords(void) +{ + int p1, p2, p3, p4, x = cs; + char *temp, *pp; + int n = zmult; + int neg = n < 0, ocs = cs; + + if (neg) + n = -n; + while (n--) { + while (x != ll && line[x] != '\n' && !iword(line[x])) + x++; + if (x == ll || line[x] == '\n') { + x = cs; + while (x && line[x - 1] != '\n' && !iword(line[x])) + x--; + if (!x || line[x - 1] == '\n') { + feep(); + return; + } + } + for (p4 = x; p4 != ll && iword(line[p4]); p4++); + for (p3 = p4; p3 && iword(line[p3 - 1]); p3--); + if (!p3) { + feep(); + return; + } + for (p2 = p3; p2 && !iword(line[p2 - 1]); p2--); + if (!p2) { + feep(); + return; + } + for (p1 = p2; p1 && iword(line[p1 - 1]); p1--); + pp = temp = (char *)halloc(p4 - p1 + 1); + struncpy(&pp, (char *) line + p3, p4 - p3); + struncpy(&pp, (char *) line + p2, p3 - p2); + struncpy(&pp, (char *) line + p1, p2 - p1); + strncpy((char *)line + p1, temp, p4 - p1); + cs = p4; + } + if (neg) + cs = ocs; +} -- cgit 1.4.1