about summary refs log tree commit diff
path: root/Src/Zle
diff options
context:
space:
mode:
Diffstat (limited to 'Src/Zle')
-rw-r--r--Src/Zle/.cvsignore14
-rw-r--r--Src/Zle/.distfiles10
-rw-r--r--Src/Zle/.exrc2
-rw-r--r--Src/Zle/comp.h137
-rw-r--r--Src/Zle/comp1.c291
-rw-r--r--Src/Zle/comp1.mdd3
-rw-r--r--Src/Zle/compctl.c1085
-rw-r--r--Src/Zle/compctl.mdd5
-rw-r--r--Src/Zle/deltochar.c91
-rw-r--r--Src/Zle/deltochar.mdd3
-rw-r--r--Src/Zle/iwidgets.list172
-rw-r--r--Src/Zle/zle.h137
-rw-r--r--Src/Zle/zle.mdd70
-rw-r--r--Src/Zle/zle_bindings.c421
-rw-r--r--Src/Zle/zle_hist.c1139
-rw-r--r--Src/Zle/zle_keymap.c1238
-rw-r--r--Src/Zle/zle_main.c907
-rw-r--r--Src/Zle/zle_misc.c816
-rw-r--r--Src/Zle/zle_move.c502
-rw-r--r--Src/Zle/zle_params.c196
-rw-r--r--Src/Zle/zle_refresh.c1116
-rw-r--r--Src/Zle/zle_things.sed9
-rw-r--r--Src/Zle/zle_thingy.c491
-rw-r--r--Src/Zle/zle_tricky.c4015
-rw-r--r--Src/Zle/zle_utils.c650
-rw-r--r--Src/Zle/zle_vi.c925
-rw-r--r--Src/Zle/zle_widget.sed7
-rw-r--r--Src/Zle/zle_word.c477
28 files changed, 14929 insertions, 0 deletions
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<return>  <--- typed before sleep returns
+
+	The shell sees \n instead of \r, since it was changed by the kernel
+	while zsh wasn't looking. Then in getkey() \n is changed back to \r,
+	and it sees "echo foo<accept line>", as expected. Without the double
+	swap the shell would see "echo foo\n", which is translated to
+	"echo fooecho foo<accept line>" because of the binding.
+	Note that if you type <line-feed> during the sleep the shell just sees
+	\n, which is translated to \r in getkey(), and you just get another
+	prompt. For type-ahead to work in ALL cases you have to use
+	stty inlcr.
+
+	Unfortunately it's IMPOSSIBLE to have a general solution if both
+	<return> and <line-feed> are mapped to the same character. The shell
+	could check if there is input and read it before setting it's own
+	terminal modes but if we get a \n we don't know whether to keep it or
+	change to \r :-(
+	*/
+
+#else				/* not HAS_TIO */
+    ti.sgttyb.sg_flags = (ti.sgttyb.sg_flags | CBREAK) & ~ECHO & ~XTABS;
+    ti.lmodes &= ~LFLUSHO;
+    eofchar = ti.tchars.t_eofc;
+    ti.tchars.t_quitc =
+	ti.ltchars.t_suspc =
+	ti.ltchars.t_flushc =
+	ti.ltchars.t_dsuspc = ti.ltchars.t_lnextc = -1;
+#endif
+
+#if defined(TTY_NEEDS_DRAINING) && defined(TIOCOUTQ) && defined(HAVE_SELECT)
+    if (baud) {			/**/
+	int n = 0;
+
+	while ((ioctl(SHTTY, TIOCOUTQ, (char *)&n) >= 0) && n) {
+	    struct timeval tv;
+
+	    tv.tv_sec = n / baud;
+	    tv.tv_usec = ((n % baud) * 1000000) / baud;
+	    select(0, NULL, NULL, NULL, &tv);
+	}
+    }
+#endif
+
+    settyinfo(&ti);
+}
+
+static char *kungetbuf;
+static int kungetct, kungetsz;
+
+/**/
+void
+ungetkey(int ch)
+{
+    if (kungetct == kungetsz)
+	kungetbuf = realloc(kungetbuf, kungetsz *= 2);
+    kungetbuf[kungetct++] = ch;
+}
+
+/**/
+void
+ungetkeys(char *s, int len)
+{
+    s += len;
+    while (len--)
+	ungetkey(*--s);
+}
+
+#if defined(pyr) && defined(HAVE_SELECT)
+static int
+breakread(int fd, char *buf, int n)
+{
+    fd_set f;
+
+    FD_ZERO(&f);
+    FD_SET(fd, &f);
+    return (select(fd + 1, (SELECT_ARG_2_T) & f, NULL, NULL, NULL) == -1 ?
+	    EOF : read(fd, buf, n));
+}
+
+# define read    breakread
+#endif
+
+/**/
+int
+getkey(int keytmout)
+{
+    char cc;
+    unsigned int ret;
+    long exp100ths;
+    int die = 0, r, icnt = 0;
+    int old_errno = errno;
+
+#ifdef HAVE_SELECT
+    fd_set foofd;
+
+#else
+# ifdef HAS_TIO
+    struct ttyinfo ti;
+
+# endif
+#endif
+
+    if (kungetct)
+	ret = STOUC(kungetbuf[--kungetct]);
+    else {
+	if (keytmout) {
+	    if (keytimeout > 500)
+		exp100ths = 500;
+	    else if (keytimeout > 0)
+		exp100ths = keytimeout;
+	    else
+		exp100ths = 0;
+#ifdef HAVE_SELECT
+	    if (exp100ths) {
+		struct timeval expire_tv;
+
+		expire_tv.tv_sec = exp100ths / 100;
+		expire_tv.tv_usec = (exp100ths % 100) * 10000L;
+		FD_ZERO(&foofd);
+		FD_SET(SHTTY, &foofd);
+		if (select(SHTTY+1, (SELECT_ARG_2_T) & foofd,
+			   NULL, NULL, &expire_tv) <= 0)
+		    return EOF;
+	    }
+#else
+# ifdef HAS_TIO
+	    ti = shttyinfo;
+	    ti.tio.c_lflag &= ~ICANON;
+	    ti.tio.c_cc[VMIN] = 0;
+	    ti.tio.c_cc[VTIME] = exp100ths / 10;
+#  ifdef HAVE_TERMIOS_H
+	    tcsetattr(SHTTY, TCSANOW, &ti.tio);
+#  else
+	    ioctl(SHTTY, TCSETA, &ti.tio);
+#  endif
+	    r = read(SHTTY, &cc, 1);
+#  ifdef HAVE_TERMIOS_H
+	    tcsetattr(SHTTY, TCSANOW, &shttyinfo.tio);
+#  else
+	    ioctl(SHTTY, TCSETA, &shttyinfo.tio);
+#  endif
+	    return (r <= 0) ? EOF : cc;
+# endif
+#endif
+	}
+	while ((r = read(SHTTY, &cc, 1)) != 1) {
+	    if (r == 0) {
+		/* The test for IGNOREEOF was added to make zsh ignore ^Ds
+		   that were typed while commands are running.  Unfortuantely
+		   this caused trouble under at least one system (SunOS 4.1).
+		   Here shells that lost their xterm (e.g. if it was killed
+		   with -9) didn't fail to read from the terminal but instead
+		   happily continued to read EOFs, so that the above read
+		   returned with 0, and, with IGNOREEOF set, this caused
+		   an infinite loop.  The simple way around this was to add
+		   the counter (icnt) so that this happens 20 times and than
+		   the shell gives up (yes, this is a bit dirty...). */
+		if (isset(IGNOREEOF) && icnt++ < 20)
+		    continue;
+		stopmsg = 1;
+		zexit(1, 0);
+	    }
+	    icnt = 0;
+	    if (errno == EINTR) {
+		die = 0;
+		if (!errflag && !retflag && !breaks)
+		    continue;
+		errflag = 0;
+		errno = old_errno;
+		return EOF;
+	    } else if (errno == EWOULDBLOCK) {
+		fcntl(0, F_SETFL, 0);
+	    } else if (errno == EIO && !die) {
+		ret = opts[MONITOR];
+		opts[MONITOR] = 1;
+		attachtty(mypgrp);
+		refresh();	/* kludge! */
+		opts[MONITOR] = ret;
+		die = 1;
+	    } else if (errno != 0) {
+		zerr("error on TTY read: %e", NULL, errno);
+		stopmsg = 1;
+		zexit(1, 0);
+	    }
+	}
+	if (cc == '\r')		/* undo the exchange of \n and \r determined by */
+	    cc = '\n';		/* setterm() */
+	else if (cc == '\n')
+	    cc = '\r';
+
+	ret = STOUC(cc);
+    }
+    if (vichgflag) {
+	if (vichgbufptr == vichgbufsz)
+	    vichgbuf = realloc(vichgbuf, vichgbufsz *= 2);
+	vichgbuf[vichgbufptr++] = ret;
+    }
+    errno = old_errno;
+    return ret;
+}
+
+/* Read a line.  It is returned metafied. */
+
+/**/
+unsigned char *
+zleread(char *lp, char *rp, int ha)
+{
+    unsigned char *s;
+    int old_errno = errno;
+    int tmout = getiparam("TMOUT");
+
+#ifdef HAVE_SELECT
+    long costmult;
+    struct timeval tv;
+    fd_set foofd;
+
+    baud = getiparam("BAUD");
+    costmult = (baud) ? 3840000L / baud : 0;
+    tv.tv_sec = 0;
+#endif
+
+    /* ZLE doesn't currently work recursively.  This is needed in case a *
+     * select loop is used in a function called from ZLE.  vared handles *
+     * this differently itself.                                          */
+    if(zleactive) {
+	char *pptbuf;
+	int pptlen;
+
+	pptbuf = unmetafy(promptexpand(lp, 0, NULL, NULL), &pptlen);
+	write(2, (WRITE_ARG_2_T)pptbuf, pptlen);
+	free(pptbuf);
+	return (unsigned char *)shingetline();
+    }
+
+    keytimeout = getiparam("KEYTIMEOUT");
+    if (!shout) {
+	if (SHTTY != -1)
+	    init_shout();
+
+	if (!shout)
+	    return NULL;
+	/* We could be smarter and default to a system read. */
+
+	/* If we just got a new shout, make sure the terminal is set up. */
+	if (termflags & TERM_UNKNOWN)
+	    init_term();
+    }
+
+    fflush(shout);
+    fflush(stderr);
+    intr();
+    insmode = unset(OVERSTRIKE);
+    eofsent = 0;
+    resetneeded = 0;
+    lpptbuf = promptexpand(lp, 1, NULL, NULL);
+    pmpt_attr = txtchange;
+    rpptbuf = promptexpand(rp, 1, NULL, NULL);
+    rpmpt_attr = txtchange;
+    histallowed = ha;
+    PERMALLOC {
+	histline = curhist;
+#ifdef HAVE_SELECT
+	FD_ZERO(&foofd);
+#endif
+	undoing = 1;
+	line = (unsigned char *)zalloc((linesz = 256) + 2);
+	virangeflag = lastcmd = done = cs = ll = mark = 0;
+	curhistline = NULL;
+	vichgflag = 0;
+	viinsbegin = 0;
+	statusline = NULL;
+	selectkeymap("main", 1);
+	fixsuffix();
+	if ((s = (unsigned char *)getlinknode(bufstack))) {
+	    setline((char *)s);
+	    zsfree((char *)s);
+	    if (stackcs != -1) {
+		cs = stackcs;
+		stackcs = -1;
+		if (cs > ll)
+		    cs = ll;
+	    }
+	    if (stackhist != -1) {
+		histline = stackhist;
+		stackhist = -1;
+	    }
+	}
+	initundo();
+	if (isset(PROMPTCR))
+	    putc('\r', shout);
+	if (tmout)
+	    alarm(tmout);
+	zleactive = 1;
+	resetneeded = 1;
+	errflag = retflag = 0;
+	lastcol = -1;
+	initmodifier(&zmod);
+	prefixflag = 0;
+	feepflag = 0;
+	refresh();
+	while (!done && !errflag) {
+
+	    statusline = NULL;
+	    vilinerange = 0;
+	    reselectkeymap();
+	    bindk = getkeycmd();
+	    if (!ll && isfirstln && c == eofchar) {
+		eofsent = 1;
+		break;
+	    }
+	    if (bindk) {
+		execzlefunc(bindk);
+		handleprefixes();
+		/* for vi mode, make sure the cursor isn't somewhere illegal */
+		if (invicmdmode() && cs > findbol() &&
+		    (cs == ll || line[cs] == '\n'))
+		    cs--;
+		if (undoing)
+		    handleundo();
+	    } else {
+		errflag = 1;
+		break;
+	    }
+#ifdef HAVE_SELECT
+	    if (baud && !(lastcmd & ZLE_MENUCMP)) {
+		FD_SET(SHTTY, &foofd);
+		if ((tv.tv_usec = cost * costmult) > 500000)
+		    tv.tv_usec = 500000;
+		if (!kungetct && select(SHTTY+1, (SELECT_ARG_2_T) & foofd,
+					NULL, NULL, &tv) <= 0)
+		    refresh();
+	    } else
+#endif
+		if (!kungetct)
+		    refresh();
+	    handlefeep();
+	}
+	statusline = NULL;
+	invalidatelist();
+	trashzle();
+	free(lpptbuf);
+	free(rpptbuf);
+	zleactive = 0;
+	alarm(0);
+    } LASTALLOC;
+    zsfree(curhistline);
+    freeundo();
+    if (eofsent) {
+	free(line);
+	line = NULL;
+    } else {
+	line[ll++] = '\n';
+	line = (unsigned char *) metafy((char *) line, ll, META_REALLOC);
+    }
+    forget_edits();
+    errno = old_errno;
+    return line;
+}
+
+/* execute a widget */
+
+/**/
+void
+execzlefunc(Thingy func)
+{
+    Widget w;
+
+    if(func->flags & DISABLED) {
+	/* this thingy is not the name of a widget */
+	char *nm = niceztrdup(func->nam);
+	char *msg = tricat("No such widget `", nm, "'");
+
+	zsfree(nm);
+	showmsg(msg);
+	zsfree(msg);
+	feep();
+    } else if((w = func->widget)->flags & WIDGET_INT) {
+	int wflags = w->flags;
+
+	if(!(wflags & ZLE_KEEPSUFFIX))
+	    removesuffix();
+	if(!(wflags & ZLE_MENUCMP)) {
+	    fixsuffix();
+	    invalidatelist();
+	}
+	if (wflags & ZLE_LINEMOVE)
+	    vilinerange = 1;
+	if(!(wflags & ZLE_LASTCOL))
+	    lastcol = -1;
+	w->u.fn();
+	lastcmd = wflags;
+    } else {
+	List l = getshfunc(w->u.fnnam);
+
+	if(l == &dummy_list) {
+	    /* the shell function doesn't exist */
+	    char *nm = niceztrdup(w->u.fnnam);
+	    char *msg = tricat("No such shell function `", nm, "'");
+
+	    zsfree(nm);
+	    showmsg(msg);
+	    zsfree(msg);
+	    feep();
+	} else {
+	  startparamscope();
+	  makezleparams();
+	  doshfunc(l, NULL, 0, 1);
+	  endparamscope();
+	  lastcmd = 0;
+	}
+    }
+}
+
+/* initialise command modifiers */
+
+/**/
+static void
+initmodifier(struct modifier *mp)
+{
+    mp->flags = 0;
+    mp->mult = 1;
+    mp->tmult = 1;
+    mp->vibuf = 0;
+}
+
+/* Reset command modifiers, unless the command just executed was a prefix. *
+ * Also set zmult, if the multiplier has been amended.                     */
+
+/**/
+static void
+handleprefixes(void)
+{
+    if (prefixflag) {
+	prefixflag = 0;
+	if(zmod.flags & MOD_TMULT) {
+	    zmod.flags |= MOD_MULT;
+	    zmod.mult = zmod.tmult;
+	}
+    } else
+	initmodifier(&zmod);
+}
+
+/* vared: edit (literally) a parameter value */
+
+/**/
+static int
+bin_vared(char *name, char **args, char *ops, int func)
+{
+    char *s;
+    char *t;
+    Param pm;
+    int create = 0;
+    char *p1 = NULL, *p2 = NULL;
+
+    /* all options are handled as arguments */
+    while (*args && **args == '-') {
+	while (*++(*args))
+	    switch (**args) {
+	    case 'c':
+		/* -c option -- allow creation of the parameter if it doesn't
+		yet exist */
+		create = 1;
+		break;
+	    case 'p':
+		/* -p option -- set main prompt string */
+		if ((*args)[1])
+		    p1 = *args + 1, *args = "" - 1;
+		else if (args[1])
+		    p1 = *(++args), *args = "" - 1;
+		else {
+		    zwarnnam(name, "prompt string expected after -%c", NULL,
+			     **args);
+		    return 1;
+		}
+		break;
+	    case 'r':
+		/* -r option -- set right prompt string */
+		if ((*args)[1])
+		    p2 = *args + 1, *args = "" - 1;
+		else if (args[1])
+		    p2 = *(++args), *args = "" - 1;
+		else {
+		    zwarnnam(name, "prompt string expected after -%c", NULL,
+			     **args);
+		    return 1;
+		}
+		break;
+	    case 'h':
+		/* -h option -- enable history */
+		ops['h'] = 1;
+		break;
+	    default:
+		/* unrecognised option character */
+		zwarnnam(name, "unknown option: %s", *args, 0);
+		return 1;
+	    }
+	args++;
+    }
+
+    /* check we have a parameter name */
+    if (!*args) {
+	zwarnnam(name, "missing variable", NULL, 0);
+	return 1;
+    }
+    /* handle non-existent parameter */
+    if (!(s = getsparam(args[0]))) {
+	if (create)
+	    createparam(args[0], PM_SCALAR);
+	else {
+	    zwarnnam(name, "no such variable: %s", args[0], 0);
+	    return 1;
+	}
+    }
+
+    if(zleactive) {
+	zwarnnam(name, "ZLE cannot be used recursively (yet)", NULL, 0);
+	return 1;
+    }
+
+    /* edit the parameter value */
+    PERMALLOC {
+	pushnode(bufstack, ztrdup(s));
+    } LASTALLOC;
+    t = (char *) zleread(p1, p2, ops['h']);
+    if (!t || errflag) {
+	/* error in editing */
+	errflag = 0;
+	return 1;
+    }
+    /* strip off trailing newline, if any */
+    if (t[strlen(t) - 1] == '\n')
+	t[strlen(t) - 1] = '\0';
+    /* final assignment of parameter value */
+    pm = (Param) paramtab->getnode(paramtab, args[0]);
+    if (pm && PM_TYPE(pm->flags) == PM_ARRAY) {
+	char **a;
+
+	PERMALLOC {
+	    a = spacesplit(t, 1);
+	} LASTALLOC;
+	setaparam(args[0], a);
+    } else
+	setsparam(args[0], t);
+    return 0;
+}
+
+/**/
+void
+describekeybriefly(void)
+{
+    char *seq, *str, *msg, *is;
+    Thingy func;
+
+    if (statusline)
+	return;
+    statusline = "Describe key briefly: _";
+    statusll = strlen(statusline);
+    refresh();
+    seq = getkeymapcmd(curkeymap, &func, &str);
+    statusline = NULL;
+    if(!*seq)
+	return;
+    msg = bindztrdup(seq);
+    msg = appstr(msg, " is ");
+    if (!func)
+	is = bindztrdup(str);
+    else
+	is = niceztrdup(func->nam);
+    msg = appstr(msg, is);
+    zsfree(is);
+    showmsg(msg);
+    zsfree(msg);
+}
+
+#define MAXFOUND 4
+
+struct findfunc {
+    Thingy func;
+    int found;
+    char *msg;
+};
+
+/**/
+static void
+scanfindfunc(char *seq, Thingy func, char *str, void *magic)
+{
+    struct findfunc *ff = magic;
+
+    if(func != ff->func)
+	return;
+    if (!ff->found++)
+	ff->msg = appstr(ff->msg, " is on");
+    if(ff->found <= MAXFOUND) {
+	char *b = bindztrdup(seq);
+
+	ff->msg = appstr(ff->msg, " ");
+	ff->msg = appstr(ff->msg, b);
+	zsfree(b);
+    }
+}
+
+/**/
+void
+whereis(void)
+{
+    struct findfunc ff;
+
+    if (!(ff.func = executenamedcommand("Where is: ")))
+	return;
+    ff.found = 0;
+    ff.msg = niceztrdup(ff.func->nam);
+    scankeymap(curkeymap, 1, scanfindfunc, &ff);
+    if (!ff.found)
+	ff.msg = appstr(ff.msg, " is not bound to any key");
+    else if(ff.found > MAXFOUND)
+	ff.msg = appstr(ff.msg, " et al");
+    showmsg(ff.msg);
+    zsfree(ff.msg);
+}
+
+/**/
+void
+trashzle(void)
+{
+    if (zleactive) {
+	/* This refresh() is just to get the main editor display right and *
+	 * get the cursor in the right place.  For that reason, we disable *
+	 * list display (which would otherwise result in infinite          *
+	 * recursion [at least, it would if refresh() didn't have its      *
+	 * extra `inlist' check]).                                         */
+	int sl = showinglist;
+	showinglist = 0;
+	refresh();
+	showinglist = sl;
+	moveto(nlnct, 0);
+	if (clearflag && tccan(TCCLEAREOD)) {
+	    tcout(TCCLEAREOD);
+	    clearflag = 0;
+	}
+	if (postedit)
+	    fprintf(shout, "%s", postedit);
+	fflush(shout);
+	resetneeded = 1;
+	settyinfo(&shttyinfo);
+    }
+    if (errflag)
+	kungetct = 0;
+}
+
+static struct builtin bintab[] = {
+    BUILTIN("bindkey", 0, bin_bindkey, 0, -1, 0, "evaMldDANmrsLR", NULL),
+    BUILTIN("vared",   0, bin_vared,   1,  7, 0, NULL,             NULL),
+    BUILTIN("zle",     0, bin_zle,     0, -1, 0, "lDANL",          NULL),
+};
+
+/**/
+int
+boot_zle(Module m)
+{
+    /* Set up editor entry points */
+    trashzleptr = trashzle;
+    gotwordptr = gotword;
+    refreshptr = refresh;
+    spaceinlineptr = spaceinline;
+    zlereadptr = zleread;
+
+    /* initialise the thingies */
+    init_thingies();
+
+    /* miscellaneous initialisations */
+    stackhist = stackcs = -1;
+    kungetbuf = (char *) zalloc(kungetsz = 32);
+
+    /* initialise the keymap system */
+    init_keymaps();
+
+    addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return 0;
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_zle(Module m)
+{
+    int i;
+
+    if(zleactive) {
+	zerrnam(m->nam, "can't unload the zle module while zle is active",
+	    NULL, 0);
+	return 1;
+    }
+
+    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    cleanup_keymaps();
+    deletehashtable(thingytab);
+
+    zfree(vichgbuf, vichgbufsz);
+    zfree(kungetbuf, kungetsz);
+    free_isrch_spots();
+
+    zfree(cutbuf.buf, cutbuf.len);
+    for(i = KRINGCT; i--; )
+	zfree(kring[i].buf, kring[i].len);
+    for(i = 35; i--; )
+	zfree(vibuf[i].buf, vibuf[i].len);
+
+    /* editor entry points */
+    trashzleptr = noop_function;
+    gotwordptr = noop_function;
+    refreshptr = noop_function;
+    spaceinlineptr = noop_function_int;
+    zlereadptr = fallback_zleread;
+
+    return 0;
+}
+
+#endif /* MODULE */
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: <mason> 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: <mason> 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: <mason>  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 <rpcsvc/nis.h>
+#else
+# ifdef HAVE_NIS
+#  include	<rpc/types.h>
+#  include	<rpc/rpc.h>
+#  include	<rpcsvc/ypclnt.h>
+#  include	<rpcsvc/yp_prot.h>
+
+/* 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 == <number of matches>). */
+	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.) *
+ * <Tab> 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') {
+	/* <return> 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;
+}