/*
 * complete.c - the complete module, interface part
 *
 * This file is part of zsh, the Z shell.
 *
 * Copyright (c) 1999 Sven Wischnowsky
 * 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 Sven Wischnowsky 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 Sven Wischnowsky and the Zsh Development Group have been advised of
 * the possibility of such damage.
 *
 * Sven Wischnowsky 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 Sven Wischnowsky and the
 * Zsh Development Group have no obligation to provide maintenance,
 * support, updates, enhancements, or modifications.
 *
 */

#include "complete.mdh"
#include "complete.pro"

/* global variables for shell parameters in new style completion */

/**/
mod_export
zlong compcurrent,
      complistmax;
/**/
zlong complistlines,
      compignored;

/**/
mod_export
char **compwords,
     **compredirs,
     *compprefix,
     *compsuffix,
     *complastprefix,
     *complastsuffix,
     *compisuffix,
     *compqiprefix,
     *compqisuffix,
     *compquote,
     *compqstack,      /* compstate[all_quotes] */
     *comppatmatch,
     *complastprompt;
/**/
char *compiprefix,
     *compcontext,
     *compparameter,
     *compredirect,
     *compquoting,
     *comprestore,
     *complist,
     *compinsert,
     *compexact,
     *compexactstr,
     *comppatinsert,
     *comptoend,      /* compstate[to_end]; populates 'movetoend' */
     *compoldlist,
     *compoldins,
     *compvared;

/*
 * An array of Param structures for compsys special parameters;
 * see 'comprparams' below.  An entry for $compstate is added
 * by makecompparams().
 *
 * See CP_REALPARAMS.
 */

/**/
Param *comprpms;

/* 
 * An array of Param structures for elements of $compstate; see
 * 'compkparams' below.
 *
 * See CP_KEYPARAMS.
 */

/**/
Param *compkpms;

/**/
mod_export void
freecmlist(Cmlist l)
{
    Cmlist n;

    while (l) {
	n = l->next;
	freecmatcher(l->matcher);
	zsfree(l->str);

	zfree(l, sizeof(struct cmlist));

	l = n;
    }
}

/**/
mod_export void
freecmatcher(Cmatcher m)
{
    Cmatcher n;

    if (!m || --(m->refc))
	return;

    while (m) {
	n = m->next;
	freecpattern(m->line);
	freecpattern(m->word);
	freecpattern(m->left);
	freecpattern(m->right);

	zfree(m, sizeof(struct cmatcher));

	m = n;
    }
}

/**/
void
freecpattern(Cpattern p)
{
    Cpattern n;

    while (p) {
	n = p->next;
	if (p->tp <= CPAT_EQUIV)
	    free(p->u.str);
	zfree(p, sizeof(struct cpattern));

	p = n;
    }
}

/* Copy a completion matcher list into permanent storage. */

/**/
mod_export Cmatcher
cpcmatcher(Cmatcher m)
{
    Cmatcher r = NULL, *p = &r, n;

    while (m) {
	*p = n = (Cmatcher) zalloc(sizeof(struct cmatcher));

	n->refc = 1;
	n->next = NULL;
	n->flags = m->flags;
	n->line = cpcpattern(m->line);
	n->llen = m->llen;
	n->word = cpcpattern(m->word);
	n->wlen = m->wlen;
	n->left = cpcpattern(m->left);
	n->lalen = m->lalen;
	n->right = cpcpattern(m->right);
	n->ralen = m->ralen;

	p = &(n->next);
	m = m->next;
    }
    return r;
}

/*
 * Copy a single entry in a matcher pattern.
 * If useheap is 1, it comes from the heap.
 */

/**/
mod_export Cpattern
cp_cpattern_element(Cpattern o)
{
    Cpattern n = zalloc(sizeof(struct cpattern));

    n->next = NULL;

    n->tp = o->tp;
    switch (o->tp)
    {
    case CPAT_CCLASS:
    case CPAT_NCLASS:
    case CPAT_EQUIV:
	n->u.str = ztrdup(o->u.str);
	break;

    case CPAT_CHAR:
	n->u.chr = o->u.chr;
	break;

    default:
	/* just to keep compiler quiet */
	break;
    }

    return n;
}

/* Copy a completion matcher pattern. */

/**/
static Cpattern
cpcpattern(Cpattern o)
{
    Cpattern r = NULL, *p = &r;

    while (o) {
	*p = cp_cpattern_element(o);
	p = &((*p)->next);
	o = o->next;
    }
    return r;
}

/* 
 * Parse a string for matcher control, containing multiple matchers.
 *
 * 's' is the string to be parsed.
 *
 * 'name' is the name of the builtin from which this is called, for errors.
 *
 * Return 'pcm_err' on error; a NULL return value means ...
 */

/**/
mod_export Cmatcher
parse_cmatcher(char *name, char *s)
{
    Cmatcher ret = NULL, r = NULL, n;
    Cpattern line, word, left, right;
    int fl, fl2, ll, wl, lal, ral, err, both;

    if (!*s)
	return NULL;

    while (*s) {
	lal = ral = both = fl2 = 0;
	left = right = NULL;

	while (*s && inblank(*s)) s++;

	if (!*s) break;

	switch (*s) {
	case 'b': fl2 = CMF_INTER; /* FALLTHROUGH */
	case 'l': fl = CMF_LEFT; break;
	case 'e': fl2 = CMF_INTER; /* FALLTHROUGH */
	case 'r': fl = CMF_RIGHT; break;
	case 'm': fl = 0; break;
	case 'B': fl2 = CMF_INTER; /* FALLTHROUGH */
	case 'L': fl = CMF_LEFT | CMF_LINE; break;
	case 'E': fl2 = CMF_INTER; /* FALLTHROUGH */
	case 'R': fl = CMF_RIGHT | CMF_LINE; break;
	case 'M': fl = CMF_LINE; break;
	case 'x': break;
	default:
	    if (name)
		zwarnnam(name, "unknown match specification character `%c'",
			 *s);
	    return pcm_err;
	}
	if (s[1] != ':') {
	    if (name)
		zwarnnam(name, "missing `:'");
	    return pcm_err;
	}
	if (*s == 'x') {
	    if (s[2] && !inblank(s[2])) {
		if (name)
		    zwarnnam(name,
			"unexpected pattern following x: specification");
		return pcm_err;
	    }
	    return ret;
	}
	s += 2;
	if (!*s) {
	    if (name)
		zwarnnam(name, "missing patterns");
	    return pcm_err;
	}
	if ((fl & CMF_LEFT) && !fl2) {
	    left = parse_pattern(name, &s, &lal, '|', &err);
	    if (err)
		return pcm_err;

	    if ((both = (*s && s[1] == '|')))
		s++;

	    if (!*s || !*++s) {
		if (name) {
                   if (both)
                       zwarnnam(name, "missing right anchor");
                   else
                       zwarnnam(name, "missing line pattern");
		}
		return pcm_err;
	    }
	} else
	    left = NULL;

	line = parse_pattern(name, &s, &ll,
			     (((fl & CMF_RIGHT) && !fl2) ? '|' : '='),
			     &err);
	if (err)
	    return pcm_err;
	if (both) {
	    right = line;
	    ral = ll;
	    line = NULL;
	    ll = 0;
	}
	if ((fl & CMF_RIGHT) && !fl2 && (!*s || !*++s)) {
	    if (name)
		zwarnnam(name, "missing right anchor");
           return pcm_err;
	} else if (!(fl & CMF_RIGHT) || fl2) {
	    if (!*s) {
		if (name)
		    zwarnnam(name, "missing word pattern");
		return pcm_err;
	    }
	    s++;
	}
	if ((fl & CMF_RIGHT) && !fl2) {
	    if (*s == '|') {
		left = line;
		lal = ll;
		line = NULL;
		ll = 0;
		s++;
	    }
	    right = parse_pattern(name, &s, &ral, '=', &err);
	    if (err)
		return pcm_err;
	    if (!*s) {
		if (name)
		    zwarnnam(name, "missing word pattern");
		return pcm_err;
	    }
	    s++;
       }

	if (*s == '*') {
	    if (!(fl & (CMF_LEFT | CMF_RIGHT))) {
		if (name)
		    zwarnnam(name, "need anchor for `*'");
		return pcm_err;
	    }
	    word = NULL;
	    if (*++s == '*') {
		s++;
		wl = -2;
	    } else
		wl = -1;
	} else {
	    word = parse_pattern(name, &s, &wl, 0, &err);

	    if (!word && !line) {
		if (name)
		    zwarnnam(name, "need non-empty word or line pattern");
		return pcm_err;
	    }
	}
	if (err)
	    return pcm_err;

	n = (Cmatcher) hcalloc(sizeof(*ret));
	n->next = NULL;
	n->flags = fl | fl2;
	n->line = line;
	n->llen = ll;
	n->word = word;
	n->wlen = wl;
	n->left = left;
	n->lalen = lal;
	n->right = right;
	n->ralen = ral;

	if (ret)
	    r->next = n;
	else
	    ret = n;

	r = n;
    }
    return ret;
}

/*
 * Parse a pattern for matcher control. 
 * name is the name of the builtin from which this is called, for errors.
 * *sp is the input string and will be updated to the end of the parsed
 *   pattern.
 * *lp will be set to the number of characters (possibly multibyte)
 *   that the pattern will match.  This must be deterministic, given
 *   the syntax allowed here.
 * e, if non-zero, is the ASCII end character to match; if zero,
 *   stop on a blank.
 * *err is set to 1 to indicate an error, else to 0.
 */

/**/
static Cpattern
parse_pattern(char *name, char **sp, int *lp, char e, int *err)
{
    Cpattern ret = NULL, r = NULL, n;
    char *s = *sp;
    convchar_t inchar;
    int l = 0, inlen;

    *err = 0;

    MB_METACHARINIT();
    while (*s && (e ? (*s != e) : !inblank(*s))) {
	n = (Cpattern) hcalloc(sizeof(*n));
	n->next = NULL;

	if (*s == '[' || *s == '{') {
	    s = parse_class(n, s);
	    if (!*s) {
		*err = 1;
		zwarnnam(name, "unterminated character class");
		return NULL;
	    }
	    s++;
	} else if (*s == '?') {
	    n->tp = CPAT_ANY;
	    s++;
	} else if (*s == '*' || *s == '(' || *s == ')' || *s == '=') {
	    *err = 1;
	    zwarnnam(name, "invalid pattern character `%c'", *s);
	    return NULL;
	} else {
	    if (*s == '\\' && s[1])
		s++;

	    inlen = MB_METACHARLENCONV(s, &inchar);
#ifdef MULTIBYTE_SUPPORT
	    if (inchar == WEOF)
		inchar = (convchar_t)(*s == Meta ? s[1] ^ 32 : *s);
#endif
	    s += inlen;
	    n->tp = CPAT_CHAR;
	    n->u.chr = inchar;
	}
	if (ret)
	    r->next = n;
	else
	    ret = n;

	r = n;

	l++;
    }
    *sp = (char *) s;
    *lp = l;
    return ret;
}

/* Parse a character class for matcher control. */

/**/
static char *
parse_class(Cpattern p, char *iptr)
{
    int endchar, firsttime = 1;
    char *optr, *nptr;

    if (*iptr++ == '[') {
	endchar = ']';
	/* TODO: surely [^]] is valid? */
	if ((*iptr == '!' || *iptr == '^') && iptr[1] != ']') {
	    p->tp = CPAT_NCLASS;
	    iptr++;
	} else
	    p->tp = CPAT_CCLASS;
    } else {
	endchar = '}';
	p->tp = CPAT_EQUIV;
    }

    /* find end of class.  End character can appear literally first. */
    for (optr = iptr; optr == iptr || *optr != endchar; optr++)
	if (!*optr)
	    return optr;
    /*
     * We can always fit the parsed class within the same length
     * because of the tokenization (including a null byte).
     *
     * As the input string is metafied, but shouldn't contain shell
     * tokens, we can just add our own tokens willy nilly.
     */
    optr = p->u.str = zhalloc((optr-iptr) + 1);

    while (firsttime || *iptr != endchar) {
	int ch;

	if (*iptr == '[' && iptr[1] == ':' &&
	    (nptr = strchr((char *)iptr + 2, ':')) && nptr[1] == ']') {
	    /* Range type */
	    iptr += 2;
	    ch = range_type((char *)iptr, nptr-iptr);
	    iptr = nptr + 2;
	    if (ch != PP_UNKWN)
		*optr++ = (unsigned char) Meta + ch;
	} else {
	    /* characters stay metafied */
	    char *ptr1 = iptr;
	    if (*iptr == Meta)
		iptr++;
	    iptr++;
	    if (*iptr == '-' && iptr[1] && iptr[1] != endchar) {
		/* a run of characters */
		iptr++;
		/* range token */
		*optr++ = Meta + PP_RANGE;

		/* start of range character */
		if (*ptr1 == Meta) {
		    *optr++ = Meta;
		    *optr++ = ptr1[1] ^ 32;
		} else
		    *optr++ = *ptr1;

		if (*iptr == Meta) {
		    *optr++ = *iptr++;
		    *optr++ = *iptr++;
		} else
		    *optr++ = *iptr++;
	    } else {
		if (*ptr1 == Meta) {
		    *optr++ = Meta;
		    *optr++ = ptr1[1] ^ 32;
		} else
		    *optr++ = *ptr1;
	    }
	}
	firsttime = 0;
    }

    *optr = '\0';
    return iptr;
}

static struct { char *name; int abbrev; int oflag; } orderopts[] = {
    { "nosort", 2, CAF_NOSORT },
    { "match", 3, CAF_MATSORT },
    { "numeric", 3, CAF_NUMSORT },
    { "reverse", 3, CAF_REVSORT }
};

/* Parse the option to compadd -o, if flags is non-NULL set it
 * returns -1 if the argument isn't a valid ordering, 0 otherwise */

/**/
static int
parse_ordering(const char *arg, int *flags)
{
    int o, fl = 0;
    const char *next, *opt = arg;
    do {
	int found = 0;
	next = strchr(opt, ',');
	if (!next)
	    next = opt + strlen(opt);

	for (o = sizeof(orderopts)/sizeof(*orderopts) - 1; o >= 0 &&
		!found; --o)
	{
	    if ((found = next - opt >= orderopts[o].abbrev &&
	            !strncmp(orderopts[o].name, opt, next - opt)))
		fl |= orderopts[o].oflag;
	}
	if (!found) {
	    if (flags) /* default to "match" */
		*flags = CAF_MATSORT;
	    return -1;
	}
    } while (*next && ((opt = next + 1)));
    if (flags)
	*flags |= fl;
    return 0;
}

/**/
static int
bin_compadd(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
{
    struct cadata dat;
    char *mstr = NULL; /* argument of -M options, accumulated */
    char *oarg = NULL; /* argument of -o option */
    int added; /* return value */
    Cmatcher match = NULL;
    size_t dparlen = 0, dparsize = 0; /* no. of -D options and array size */

    if (incompfunc != 1) {
	zwarnnam(name, "can only be called from completion function");
	return 1;
    }
    dat.ipre = dat.isuf = dat.ppre = dat.psuf = dat.prpre = dat.mesg =
	dat.pre = dat.suf = dat.group = dat.rems = dat.remf = dat.disp =
	dat.ign = dat.exp = dat.apar = dat.opar = NULL;
    dat.dpar = NULL;
    dat.match = NULL;
    dat.flags = 0;
    dat.aflags = CAF_MATCH;
    dat.dummies = -1;

    for (; *argv && **argv ==  '-'; argv++) {
	char *p; /* loop variable, points into argv */
	if (!(*argv)[1]) {
	    argv++;
	    break;
	}
	for (p = *argv + 1; *p; p++) {
	    char *m = NULL; /* argument of -M option (this one only) */
	    int order = 0;  /* if -o found (argument to which is optional) */
	    char **sp = NULL; /* the argument to an option should be copied
				 to *sp. */
	    const char *e; /* error message */
	    switch (*p) {
	    case 'q':
		dat.flags |= CMF_REMOVE;
		break;
	    case 'Q':
		dat.aflags |= CAF_QUOTE;
		break;
	    case 'C':
		dat.aflags |= CAF_ALL;
		break;
	    case 'f':
		dat.flags |= CMF_FILE;
		break;
	    case 'e':
		dat.flags |= CMF_ISPAR;
		break;
	    case 'a':
		dat.aflags |= CAF_ARRAYS;
		break;
	    case 'k':
		dat.aflags |= CAF_ARRAYS|CAF_KEYS;
		break;
	    case 'F':
		sp = &(dat.ign);
		e = "string expected after -%c";
		break;
	    case 'n':
		dat.flags |= CMF_NOLIST;
		break;
	    case 'U':
		dat.aflags &= ~CAF_MATCH;
		break;
	    case 'P':
		sp = &(dat.pre);
		e = "string expected after -%c";
		break;
	    case 'S':
		sp = &(dat.suf);
		e = "string expected after -%c";
		break;
	    case 'J':
		sp = &(dat.group);
		e = "group name expected after -%c";
		break;
	    case 'V':
		if (!dat.group)
		    dat.aflags |= CAF_NOSORT;
		sp = &(dat.group);
		e = "group name expected after -%c";
		break;
	    case '1':
		if (!(dat.aflags & CAF_UNIQCON))
		    dat.aflags |= CAF_UNIQALL;
		break;
	    case '2':
		if (!(dat.aflags & CAF_UNIQALL))
		    dat.aflags |= CAF_UNIQCON;
		break;
	    case 'i':
		sp = &(dat.ipre);
		e = "string expected after -%c";
		break;
	    case 'I':
		sp = &(dat.isuf);
		e = "string expected after -%c";
		break;
	    case 'p':
		sp = &(dat.ppre);
		e = "string expected after -%c";
		break;
	    case 's':
		sp = &(dat.psuf);
		e = "string expected after -%c";
		break;
	    case 'W':
		sp = &(dat.prpre);
		e = "string expected after -%c";
		break;
	    case 'M':
		sp = &m;
		e = "matching specification expected after -%c";
		break;
	    case 'X':
		sp = &(dat.exp);
		e = "string expected after -%c";
		break;
	    case 'x':
		sp = &(dat.mesg);
		e = "string expected after -%c";
		break;
	    case 'r':
		dat.flags |= CMF_REMOVE;
		sp = &(dat.rems);
		e = "string expected after -%c";
		break;
	    case 'R':
		dat.flags |= CMF_REMOVE;
		sp = &(dat.remf);
		e = "function name expected after -%c";
		break;
	    case 'A':
		sp = &(dat.apar);
		e = "parameter name expected after -%c";
		break;
	    case 'O':
		sp = &(dat.opar);
		e = "parameter name expected after -%c";
		break;
	    case 'D':
		if (dparsize <= dparlen + 1) {
		    dparsize = (dparsize + 1) * 2;
		    dat.dpar = (char **)zrealloc(dat.dpar, sizeof(char *) * dparsize);
		}
		sp = dat.dpar + dparlen++;
		*sp = dat.dpar[dparlen] = NULL;
		e = "parameter name expected after -%c";
		break;
	    case 'd':
		sp = &(dat.disp);
		e = "parameter name expected after -%c";
		break;
	    case 'l':
		dat.flags |= CMF_DISPLINE;
		break;
	    case 'o':
		/* we honour just the first -o option but need to skip
		 * over a valid argument to subsequent -o options */
		order = oarg ? -1 : 1;
		sp = &oarg;
		/* no error string because argument is optional */
		break;
	    case 'E':
                if (p[1]) {
                    dat.dummies = atoi(p + 1);
		    p += strlen(p+1);
                } else if (argv[1]) {
                    argv++;
                    dat.dummies = atoi(*argv);
                } else {
                    zwarnnam(name, "number expected after -%c", *p);
		    zsfree(mstr);
		    zfree(dat.dpar, dparsize);
                    return 1;
                }
                if (dat.dummies < 0) {
                    zwarnnam(name, "invalid number: %d", dat.dummies);
		    zsfree(mstr);
		    zfree(dat.dpar, dparsize);
                    return 1;
                }
		break;
	    case '-':
		argv++;
		goto ca_args;
	    default:
		zwarnnam(name, "bad option: -%c", *p);
		zsfree(mstr);
		zfree(dat.dpar, dparsize);
		return 1;
	    }
	    if (sp) {
		if (p[1]) {
		    /* Pasted argument: -Xfoo. */
		    if (!*sp) /* take first option only */
			*sp = p + 1;
		    if (!order || !parse_ordering(oarg, order == 1 ? &dat.aflags : NULL))
			p += strlen(p+1);
		} else if (argv[1]) {
		    /* Argument in a separate word: -X foo. */
		    argv++;
		    if (!*sp)
			*sp = *argv;
		    if (order && parse_ordering(oarg, order == 1 ? &dat.aflags : NULL))
			--argv;
		} else if (!order) {
		    /* Missing argument: argv[N] == "-X", argv[N+1] == NULL. */
		    zwarnnam(name, e, *p);
		    zsfree(mstr);
		    zfree(dat.dpar, dparsize);
		    return 1;
		}
		if (m) {
		    if (mstr) {
			char *tmp = tricat(mstr, " ", m);
			zsfree(mstr);
			mstr = tmp;
		    } else
			mstr = ztrdup(m);
		}
	    }
	}
    }

 ca_args:

    if (mstr && (dat.aflags & CAF_MATCH) &&
	    (match = parse_cmatcher(name, mstr)) == pcm_err)
    {
	zsfree(mstr);
	zfree(dat.dpar, dparsize);
	return 1;
    }
    zsfree(mstr);

    if (!*argv && !dat.group && !dat.mesg &&
	!(dat.aflags & (CAF_NOSORT|CAF_UNIQALL|CAF_UNIQCON|CAF_ALL))) {
	zfree(dat.dpar, dparsize);
	return 1;
    }

    dat.match = match = cpcmatcher(match);
    added = addmatches(&dat, argv);
    freecmatcher(match);
    zfree(dat.dpar, dparsize);

    return added;
}

#define CVT_RANGENUM 0
#define CVT_RANGEPAT 1
#define CVT_PRENUM   2
#define CVT_PREPAT   3
#define CVT_SUFNUM   4
#define CVT_SUFPAT   5

/**/
mod_export void
ignore_prefix(int l)
{
    if (l) {
	char *tmp, sav;
	int pl = strlen(compprefix);

	if (l > pl)
	    l = pl;

	sav = compprefix[l];

	compprefix[l] = '\0';
	tmp = tricat(compiprefix, compprefix, "");
	zsfree(compiprefix);
	compiprefix = tmp;
	compprefix[l] = sav;
	tmp = ztrdup(compprefix + l);
	zsfree(compprefix);
	compprefix = tmp;
    }
}

/**/
mod_export void
ignore_suffix(int l)
{
    if (l) {
	char *tmp, sav;
	int sl = strlen(compsuffix);

	if ((l = sl - l) < 0)
	    l = 0;

	tmp = tricat(compsuffix + l, compisuffix, "");
	zsfree(compisuffix);
	compisuffix = tmp;
	sav = compsuffix[l];
	compsuffix[l] = '\0';
	tmp = ztrdup(compsuffix);
	compsuffix[l] = sav;
	zsfree(compsuffix);
	compsuffix = tmp;
    }
}

/**/
mod_export void
restrict_range(int b, int e)
{
    int wl = arrlen(compwords) - 1;

    if (wl && b >= 0 && e >= 0 && (b > 0 || e < wl)) {
	int i;
	char **p, **q, **pp;

	if (e > wl)
	    e = wl;

	i = e - b + 1;
	p = (char **) zshcalloc((i + 1) * sizeof(char *));

	for (q = p, pp = compwords + b; i; i--, q++, pp++)
	    *q = ztrdup(*pp);
	freearray(compwords);
	compwords = p;
	compcurrent -= b;
    }
}

/**/
static int
do_comp_vars(int test, int na, char *sa, int nb, char *sb, int mod)
{
    switch (test) {
    case CVT_RANGENUM:
	{
	    int l = arrlen(compwords);

	    if (na < 0)
		na += l;
	    else
		na--;
	    if (nb < 0)
		nb += l;
	    else
		nb--;

	    if (compcurrent - 1 < na || compcurrent - 1 > nb)
		return 0;
	    if (mod)
		restrict_range(na, nb);
	    return 1;
	}
    case CVT_RANGEPAT:
	{
	    char **p;
	    int i, l = arrlen(compwords), t = 0, b = 0, e = l - 1;
	    Patprog pp;

	    i = compcurrent - 1;
	    if (i < 0 || i >= l)
		return 0;

	    singsub(&sa);
	    pp = patcompile(sa, PAT_HEAPDUP, NULL);

	    for (i--, p = compwords + i; i >= 0; p--, i--) {
		if (pattry(pp, *p)) {
		    b = i + 1;
		    t = 1;
		    break;
		}
	    }
	    if (t && sb) {
		int tt = 0;

		singsub(&sb);
		pp = patcompile(sb, PAT_STATIC, NULL);

		for (i++, p = compwords + i; i < l; p++, i++) {
		    if (pattry(pp, *p)) {
			e = i - 1;
			tt = 1;
			break;
		    }
		}
		if (tt && i < compcurrent)
		    t = 0;
	    }
	    if (e < b)
		t = 0;
	    if (t && mod)
		restrict_range(b, e);
	    return t;
	}
    case CVT_PRENUM:
    case CVT_SUFNUM:
	if (na < 0)
	    return 0;
	if (na > 0 && mod) {
#ifdef MULTIBYTE_SUPPORT
	    if (isset(MULTIBYTE)) {
		if (test == CVT_PRENUM) {
		    const char *ptr = compprefix;
		    int len = 1;
		    int sum = 0;
		    while (*ptr && na && len) {
			wint_t wc;
			len = mb_metacharlenconv(ptr, &wc);
			ptr += len;
			sum += len;
			na--;
		    }
		    if (na)
			return 0;
		    na = sum;
		} else {
		    char *end = compsuffix + strlen(compsuffix);
		    char *ptr = end;
		    while (na-- && ptr > compsuffix)
			 ptr = backwardmetafiedchar(compsuffix, ptr, NULL);
		    if (na >= 0)
			return 0;
		    na = end - ptr;
		}
	    } else
#endif
	    if ((int)strlen(test == CVT_PRENUM ? compprefix : compsuffix) < na)
		return 0;
	    if (test == CVT_PRENUM)
		ignore_prefix(na);
	    else
		ignore_suffix(na);
	    return 1;
	}
	return 1;
    case CVT_PREPAT:
    case CVT_SUFPAT:
	{
	    Patprog pp;

	    if (!na)
		return 0;

	    if (!(pp = patcompile(sa, PAT_HEAPDUP, 0)))
		return 0;

	    if (test == CVT_PREPAT) {
		int l, add;
		char *p, sav;

		if (!(l = strlen(compprefix)))
		    return ((na == 1 || na == -1) && pattry(pp, compprefix));
		if (na < 0) {
		    p = compprefix + l;
		    na = -na;
		    add = -1;
		} else {
		    p = compprefix + 1 + (*compprefix == Meta);
		    if (p > compprefix + l)
			p = compprefix + l;
		    add = 1;
		}
		for (;;) {
		    sav = *p;
		    *p = '\0';
		    test = pattry(pp, compprefix);
		    *p = sav;
		    if (test && !--na)
			break;
		    if (add > 0) {
			if (p == compprefix + l)
			    return 0;
			p = p + 1 + (*p == Meta);
			if (p > compprefix + l)
			    p = compprefix + l;
		    } else {
			if (p == compprefix)
			    return 0;
			p--;
			if (p > compprefix && p[-1] == Meta)
			    p--;
		    }
		}
		if (mod)
		    ignore_prefix(p - compprefix);
	    } else {
		int l, ol, add;
		char *p;

		if (!(ol = l = strlen(compsuffix)))
		    return ((na == 1 || na == -1) && pattry(pp, compsuffix));
		if (na < 0) {
		    p = compsuffix;
		    na = -na;
		    add = 1;
		} else {
		    p = compsuffix + l - 1;
		    if (p > compsuffix && p[-1] == Meta)
			p--;
		    add = -1;
		}
		for (;;) {
		    if (pattry(pp, p) && !--na)
			break;

		    if (add > 0) {
			if (p == compsuffix + l)
			    return 0;
			if (*p == Meta)
			    p += 2;
			else
			    p++;
		    } else {
			if (p == compsuffix)
			    return 0;
			p--;
			if (p > compsuffix && p[-1] == Meta)
			    p--;
		    }
		}

		if (mod)
		    ignore_suffix(ol - (p - compsuffix));
	    }
	    return 1;
	}
    }
    return 0;
}

/**/
static int
bin_compset(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
{
    int test = 0, na = 0, nb = 0;
    char *sa = NULL, *sb = NULL;

    if (incompfunc != 1) {
	zwarnnam(name, "can only be called from completion function");
	return 1;
    }
    if (argv[0][0] != '-') {
	zwarnnam(name, "missing option");
	return 1;
    }
    switch (argv[0][1]) {
    case 'n': test = CVT_RANGENUM; break;
    case 'N': test = CVT_RANGEPAT; break;
    case 'p': test = CVT_PRENUM; break;
    case 'P': test = CVT_PREPAT; break;
    case 's': test = CVT_SUFNUM; break;
    case 'S': test = CVT_SUFPAT; break;
    case 'q': return set_comp_sep();
    default:
	zwarnnam(name, "bad option -%c", argv[0][1]);
	return 1;
    }
    if (argv[0][2]) {
	sa = argv[0] + 2;
	sb = argv[1];
	na = 2;
    } else {
	if (!(sa = argv[1])) {
	    zwarnnam(name, "missing string for option -%c", argv[0][1]);
	    return 1;
	}
	sb = argv[2];
	na = 3;
    }
    if (((test == CVT_PRENUM || test == CVT_SUFNUM) ? !!sb :
	 (sb && argv[na]))) {
	zwarnnam(name, "too many arguments");
	return 1;
    }
    switch (test) {
    case CVT_RANGENUM:
	na = atoi(sa);
	nb = (sb ? atoi(sb) : -1);
	break;
    case CVT_RANGEPAT:
	tokenize(sa);
	remnulargs(sa);
	if (sb) {
	    tokenize(sb);
	    remnulargs(sb);
	}
	break;
    case CVT_PRENUM:
    case CVT_SUFNUM:
	na = atoi(sa);
	break;
    case CVT_PREPAT:
    case CVT_SUFPAT:
	if (sb) {
	    na = atoi(sa);
	    sa = sb;
	} else
	    na = -1;
	tokenize(sa);
	remnulargs(sa);
	break;
    }
    return !do_comp_vars(test, na, sa, nb, sb, 1);
}

/* Definitions for the special parameters. Note that these have to match the
 * order of the CP_* bits in comp.h */

#define VAL(X) ((void *) (&(X)))
#define GSU(X) ((GsuScalar)(void *) (&(X)))
struct compparam {
    char *name;
    int type;
    void *var;
    GsuScalar gsu;
};

static const struct gsu_scalar compvarscalar_gsu =
{ strvargetfn, strvarsetfn, compunsetfn };
static const struct gsu_scalar complist_gsu =
{ get_complist, set_complist, compunsetfn };
static const struct gsu_scalar unambig_gsu =
{ get_unambig, nullstrsetfn, compunsetfn };
static const struct gsu_scalar unambig_pos_gsu =
{ get_unambig_pos, nullstrsetfn, compunsetfn };
static const struct gsu_scalar insert_pos_gsu =
{ get_insert_pos, nullstrsetfn, compunsetfn };
static const struct gsu_scalar compqstack_gsu =
{ get_compqstack, nullstrsetfn, compunsetfn };

static const struct gsu_integer compvarinteger_gsu =
{ intvargetfn, intvarsetfn, compunsetfn };
static const struct gsu_integer nmatches_gsu =
{ get_nmatches, NULL, compunsetfn };
static const struct gsu_integer unambig_curs_gsu =
{ get_unambig_curs, NULL, compunsetfn };
static const struct gsu_integer listlines_gsu =
{ get_listlines, NULL, compunsetfn };

static const struct gsu_array compvararray_gsu =
{ arrvargetfn, arrvarsetfn, compunsetfn };


static struct compparam comprparams[] = {
    { "words", PM_ARRAY, VAL(compwords), NULL },
    { "redirections", PM_ARRAY, VAL(compredirs), NULL },
    { "CURRENT", PM_INTEGER, VAL(compcurrent), NULL },
    { "PREFIX", PM_SCALAR, VAL(compprefix), NULL },
    { "SUFFIX", PM_SCALAR, VAL(compsuffix), NULL },
    { "IPREFIX", PM_SCALAR, VAL(compiprefix), NULL },
    { "ISUFFIX", PM_SCALAR, VAL(compisuffix), NULL },
    { "QIPREFIX", PM_SCALAR | PM_READONLY, VAL(compqiprefix), NULL },
    { "QISUFFIX", PM_SCALAR | PM_READONLY, VAL(compqisuffix), NULL },
    { NULL, 0, NULL, NULL }
};

static struct compparam compkparams[] = {
    { "nmatches", PM_INTEGER | PM_READONLY, NULL, GSU(nmatches_gsu) },
    { "context", PM_SCALAR, VAL(compcontext), NULL },
    { "parameter", PM_SCALAR, VAL(compparameter), NULL },
    { "redirect", PM_SCALAR, VAL(compredirect), NULL },
    { "quote", PM_SCALAR | PM_READONLY, VAL(compquote), NULL },
    { "quoting", PM_SCALAR | PM_READONLY, VAL(compquoting), NULL },
    { "restore", PM_SCALAR, VAL(comprestore), NULL },
    { "list", PM_SCALAR, NULL, GSU(complist_gsu) },
    { "insert", PM_SCALAR, VAL(compinsert), NULL },
    { "exact", PM_SCALAR, VAL(compexact), NULL },
    { "exact_string", PM_SCALAR, VAL(compexactstr), NULL },
    { "pattern_match", PM_SCALAR, VAL(comppatmatch), NULL },
    { "pattern_insert", PM_SCALAR, VAL(comppatinsert), NULL },
    { "unambiguous", PM_SCALAR | PM_READONLY, NULL, GSU(unambig_gsu) },
    { "unambiguous_cursor", PM_INTEGER | PM_READONLY, NULL,
      GSU(unambig_curs_gsu) },
    { "unambiguous_positions", PM_SCALAR | PM_READONLY, NULL,
      GSU(unambig_pos_gsu) },
    { "insert_positions", PM_SCALAR | PM_READONLY, NULL,
      GSU(insert_pos_gsu) },
    { "list_max", PM_INTEGER, VAL(complistmax), NULL },
    { "last_prompt", PM_SCALAR, VAL(complastprompt), NULL },
    { "to_end", PM_SCALAR, VAL(comptoend), NULL },
    { "old_list", PM_SCALAR, VAL(compoldlist), NULL },
    { "old_insert", PM_SCALAR, VAL(compoldins), NULL },
    { "vared", PM_SCALAR, VAL(compvared), NULL },
    { "list_lines", PM_INTEGER | PM_READONLY, NULL, GSU(listlines_gsu) },
    { "all_quotes", PM_SCALAR | PM_READONLY, NULL, GSU(compqstack_gsu) },
    { "ignored", PM_INTEGER | PM_READONLY, VAL(compignored), NULL },
    { NULL, 0, NULL, NULL }
};

#define COMPSTATENAME "compstate"

static void
addcompparams(struct compparam *cp, Param *pp)
{
    for (; cp->name; cp++, pp++) {
	Param pm = createparam(cp->name,
			       cp->type |PM_SPECIAL|PM_REMOVABLE|PM_LOCAL);
	if (!pm)
	    pm = (Param) paramtab->getnode(paramtab, cp->name);
	DPUTS(!pm, "param not set in addcompparams");

	*pp = pm;
	pm->level = locallevel + 1;
	if ((pm->u.data = cp->var)) {
	    switch(PM_TYPE(cp->type)) {
	    case PM_SCALAR:
		pm->gsu.s = &compvarscalar_gsu;
		break;
	    case PM_INTEGER:
		pm->gsu.i = &compvarinteger_gsu;
		pm->base = 10;
		break;
	    case PM_ARRAY:
		pm->gsu.a = &compvararray_gsu;
		break;
	    }
	} else {
	    pm->gsu.s = cp->gsu;
	}
    }
}

static const struct gsu_hash compstate_gsu =
{ get_compstate, set_compstate, compunsetfn };

/**/
void
makecompparams(void)
{
    Param cpm;
    HashTable tht;

    addcompparams(comprparams, comprpms);

    if (!(cpm = createparam(
	      COMPSTATENAME,
	      PM_SPECIAL|PM_REMOVABLE|PM_SINGLE|PM_LOCAL|PM_HASHED)))
	cpm = (Param) paramtab->getnode(paramtab, COMPSTATENAME);
    DPUTS(!cpm, "param not set in makecompparams");

    comprpms[CPN_COMPSTATE] = cpm;
    tht = paramtab;
    cpm->level = locallevel + 1;
    cpm->gsu.h = &compstate_gsu;
    cpm->u.hash = paramtab = newparamtable(31, COMPSTATENAME);
    addcompparams(compkparams, compkpms);
    paramtab = tht;
}

/**/
static HashTable
get_compstate(Param pm)
{
    return pm->u.hash;
}

/**/
static void
set_compstate(Param pm, HashTable ht)
{
    struct compparam *cp;
    Param *pp;
    HashNode hn;
    int i;
    struct value v;
    char *str;

    if (!ht)
        return;

    for (i = 0; i < ht->hsize; i++)
	for (hn = ht->nodes[i]; hn; hn = hn->next)
	    for (cp = compkparams,
		 pp = compkpms; cp->name; cp++, pp++)
		if (!strcmp(hn->nam, cp->name)) {
		    v.isarr = v.flags = v.start = 0;
		    v.end = -1;
		    v.arr = NULL;
		    v.pm = (Param) hn;
		    if (cp->type == PM_INTEGER)
			*((zlong *) cp->var) = getintvalue(&v);
		    else if ((str = getstrvalue(&v))) {
			zsfree(*((char **) cp->var));
			*((char **) cp->var) = ztrdup(str);
		    }
		    (*pp)->node.flags &= ~PM_UNSET;

		    break;
		}
    if (ht != pm->u.hash)
	deleteparamtable(ht);
}

/**/
static zlong
get_nmatches(UNUSED(Param pm))
{
    return (permmatches(0) ? 0 : nmatches);
}

/**/
static zlong
get_listlines(UNUSED(Param pm))
{
    return list_lines();
}

/**/
static void
set_complist(UNUSED(Param pm), char *v)
{
    comp_list(v);
}

/**/
static char *
get_complist(UNUSED(Param pm))
{
    return complist;
}

/**/
static char *
get_unambig(UNUSED(Param pm))
{
    return unambig_data(NULL, NULL, NULL);
}

/**/
static zlong
get_unambig_curs(UNUSED(Param pm))
{
    int c;

    unambig_data(&c, NULL, NULL);

    return c;
}

/**/
static char *
get_unambig_pos(UNUSED(Param pm))
{
    char *p;

    unambig_data(NULL, &p, NULL);

    return p;
}

/**/
static char *
get_insert_pos(UNUSED(Param pm))
{
    char *p;

    unambig_data(NULL, NULL, &p);

    return p;
}

/**/
static char *
get_compqstack(UNUSED(Param pm))
{
    char *p, *ptr, *cqp;

    if (!compqstack)		/* TODO: don't think this can happen... */
	return "";

    ptr = p = zhalloc(2*strlen(compqstack)+1);

    for (cqp = compqstack; *cqp; cqp++) {
	char *str = comp_quoting_string(*cqp);
	*ptr++ = *str;
    }
    *ptr = '\0';

    return p;
}

/**/
static void
compunsetfn(Param pm, int exp)
{
    if (exp) {
	if (pm->u.data) {
	    if (PM_TYPE(pm->node.flags) == PM_SCALAR) {
		zsfree(*((char **) pm->u.data));
		*((char **) pm->u.data) = ztrdup("");
	    } else if (PM_TYPE(pm->node.flags) == PM_ARRAY) {
		freearray(*((char ***) pm->u.data));
		*((char ***) pm->u.data) = zshcalloc(sizeof(char *));
	    } else if (PM_TYPE(pm->node.flags) == PM_HASHED) {
		deleteparamtable(pm->u.hash);
		pm->u.hash = NULL;
	    }
	}
    } else if (PM_TYPE(pm->node.flags) == PM_HASHED) {
	Param *p;
	int i;

	deletehashtable(pm->u.hash);
	pm->u.hash = NULL;

	for (p = compkpms, i = CP_KEYPARAMS; i--; p++)
	    *p = NULL;
    }
    if (!exp) {
	Param *p;
	int i;

	for (p = comprpms, i = CP_REALPARAMS; i; p++, i--)
	    if (*p == pm) {
		*p = NULL;
		break;
	    }
    }
}

/**/
void
comp_setunset(int rset, int runset, int kset, int kunset)
{
    Param *p;

    if (comprpms && (rset >= 0 || runset >= 0)) {
	for (p = comprpms; rset || runset; rset >>= 1, runset >>= 1, p++) {
	    if (*p) {
		if (rset & 1)
		    (*p)->node.flags &= ~PM_UNSET;
		if (runset & 1)
		    (*p)->node.flags |= PM_UNSET;
	    }
	}
    }
    if (compkpms && (kset >= 0 || kunset >= 0)) {
	for (p = compkpms; kset || kunset; kset >>= 1, kunset >>= 1, p++) {
	    if (*p) {
		if (kset & 1)
		    (*p)->node.flags &= ~PM_UNSET;
		if (kunset & 1)
		    (*p)->node.flags |= PM_UNSET;
	    }
	}
    }
}

/**/
static int
comp_wrapper(Eprog prog, FuncWrap w, char *name)
{
    if (incompfunc != 1)
	return 1;
    else {
	char *orest, *opre, *osuf, *oipre, *oisuf, **owords, **oredirs;
	char *oqipre, *oqisuf, *oq, *oqi, *oqs, *oaq;
	zlong ocur;
	unsigned int runset = 0, kunset = 0, m, sm;
	Param *pp;

	m = CP_WORDS | CP_REDIRS | CP_CURRENT | CP_PREFIX | CP_SUFFIX | 
	    CP_IPREFIX | CP_ISUFFIX | CP_QIPREFIX | CP_QISUFFIX;
	for (pp = comprpms, sm = 1; m; pp++, m >>= 1, sm <<= 1) {
	    if ((m & 1) && ((*pp)->node.flags & PM_UNSET))
		runset |= sm;
	}
	if (compkpms[CPN_RESTORE]->node.flags & PM_UNSET)
	    kunset = CP_RESTORE;
	orest = comprestore;
	comprestore = ztrdup("auto");
	ocur = compcurrent;
	opre = ztrdup(compprefix);
	osuf = ztrdup(compsuffix);
	oipre = ztrdup(compiprefix);
	oisuf = ztrdup(compisuffix);
	oqipre = ztrdup(compqiprefix);
	oqisuf = ztrdup(compqisuffix);
	oq = ztrdup(compquote);
	oqi = ztrdup(compquoting);
	oqs = ztrdup(compqstack);
	oaq = ztrdup(autoq);
	owords = zarrdup(compwords);
	oredirs = zarrdup(compredirs);

	runshfunc(prog, w, name);

	if (comprestore && !strcmp(comprestore, "auto")) {
	    compcurrent = ocur;
	    zsfree(compprefix);
	    compprefix = opre;
	    zsfree(compsuffix);
	    compsuffix = osuf;
	    zsfree(compiprefix);
	    compiprefix = oipre;
	    zsfree(compisuffix);
	    compisuffix = oisuf;
	    zsfree(compqiprefix);
	    compqiprefix = oqipre;
	    zsfree(compqisuffix);
	    compqisuffix = oqisuf;
	    zsfree(compquote);
	    compquote = oq;
	    zsfree(compquoting);
	    compquoting = oqi;
	    zsfree(compqstack);
	    compqstack = oqs;
	    zsfree(autoq);
	    autoq = oaq;
	    freearray(compwords);
	    freearray(compredirs);
	    compwords = owords;
            compredirs = oredirs;
	    comp_setunset(CP_COMPSTATE |
			  (~runset & (CP_WORDS | CP_REDIRS |
                                      CP_CURRENT | CP_PREFIX |
                                      CP_SUFFIX | CP_IPREFIX | CP_ISUFFIX |
                                      CP_QIPREFIX | CP_QISUFFIX)),
			  (runset & CP_ALLREALS),
			  (~kunset & CP_RESTORE), (kunset & CP_ALLKEYS));
	} else {
	    comp_setunset(CP_COMPSTATE, 0, (~kunset & CP_RESTORE),
			  (kunset & CP_RESTORE));
	    zsfree(opre);
	    zsfree(osuf);
	    zsfree(oipre);
	    zsfree(oisuf);
	    zsfree(oqipre);
	    zsfree(oqisuf);
	    zsfree(oq);
	    zsfree(oqi);
	    zsfree(oqs);
	    zsfree(oaq);
	    freearray(owords);
	    freearray(oredirs);
	}
	zsfree(comprestore);
	comprestore = orest;

	return 0;
    }
}

/**/
static int
comp_check(void)
{
    if (incompfunc != 1) {
	zerr("condition can only be used in completion function");
	return 0;
    }
    return 1;
}

/**/
static int
cond_psfix(char **a, int id)
{
    if (comp_check()) {
	if (a[1])
	    return do_comp_vars(id, cond_val(a, 0), cond_str(a, 1, 1),
				0, NULL, 0);
	else
	    return do_comp_vars(id, -1, cond_str(a, 0, 1), 0, NULL, 0);
    }
    return 0;
}

/**/
static int
cond_range(char **a, int id)
{
    return do_comp_vars(CVT_RANGEPAT, 0, cond_str(a, 0, 1), 0,
			(id ? cond_str(a, 1, 1) : NULL), 0);
}

static struct builtin bintab[] = {
    BUILTIN("compadd", BINF_HANDLES_OPTS, bin_compadd, 0, -1, 0, NULL, NULL),
    BUILTIN("compset", 0, bin_compset, 1, 3, 0, NULL, NULL),
};

static struct conddef cotab[] = {
    CONDDEF("after", 0, cond_range, 1, 1, 0),
    CONDDEF("between", 0, cond_range, 2, 2, 1),
    CONDDEF("prefix", 0, cond_psfix, 1, 2, CVT_PREPAT),
    CONDDEF("suffix", 0, cond_psfix, 1, 2, CVT_SUFPAT),
};

static struct funcwrap wrapper[] = {
    WRAPDEF(comp_wrapper),
};

/* The order of the entries in this table has to match the *HOOK
 * macros in comp.h */

/**/
struct hookdef comphooks[] = {
    HOOKDEF("insert_match", NULL, HOOKF_ALL),
    HOOKDEF("menu_start", NULL, HOOKF_ALL),
    HOOKDEF("compctl_make", NULL, 0),
    HOOKDEF("compctl_cleanup", NULL, 0),
    HOOKDEF("comp_list_matches", ilistmatches, 0),
};

static struct features module_features = {
    bintab, sizeof(bintab)/sizeof(*bintab),
    cotab, sizeof(cotab)/sizeof(*cotab),
    NULL, 0,
    NULL, 0,
    0
};

/**/
int
setup_(UNUSED(Module m))
{
    hasperm = 0;

    comprpms = compkpms = NULL;
    compwords = compredirs = NULL;
    compprefix = compsuffix = compiprefix = compisuffix = 
	compqiprefix = compqisuffix =
	compcontext = compparameter = compredirect = compquote =
	compquoting = comprestore = complist = compinsert =
	compexact = compexactstr = comppatmatch = comppatinsert =
	complastprompt = comptoend = compoldlist = compoldins =
	compvared = compqstack = NULL;
    complastprefix = ztrdup("");
    complastsuffix = ztrdup("");
    complistmax = 0;
    hascompmod = 1;

    return 0;
}

/**/
int
features_(Module m, char ***features)
{
    *features = featuresarray(m, &module_features);
    return 0;
}

/**/
int
enables_(Module m, int **enables)
{
    return handlefeatures(m, &module_features, enables);
}

/**/
int
boot_(Module m)
{
    addhookfunc("complete", (Hookfn) do_completion);
    addhookfunc("before_complete", (Hookfn) before_complete);
    addhookfunc("after_complete", (Hookfn) after_complete);
    addhookfunc("accept_completion", (Hookfn) accept_last);
    addhookfunc("list_matches", (Hookfn) list_matches);
    addhookfunc("invalidate_list", (Hookfn) invalidate_list);
    (void)addhookdefs(m, comphooks, sizeof(comphooks)/sizeof(*comphooks));
    return addwrapper(m, wrapper);
}

/**/
int
cleanup_(Module m)
{
    deletehookfunc("complete", (Hookfn) do_completion);
    deletehookfunc("before_complete", (Hookfn) before_complete);
    deletehookfunc("after_complete", (Hookfn) after_complete);
    deletehookfunc("accept_completion", (Hookfn) accept_last);
    deletehookfunc("list_matches", (Hookfn) list_matches);
    deletehookfunc("invalidate_list", (Hookfn) invalidate_list);
    (void)deletehookdefs(m, comphooks,
			 sizeof(comphooks)/sizeof(*comphooks));
    deletewrapper(m, wrapper);
    return setfeatureenables(m, &module_features, NULL);
}

/**/
int
finish_(UNUSED(Module m))
{
    if (compwords)
	freearray(compwords);
    if (compredirs)
	freearray(compredirs);
    zsfree(compprefix);
    zsfree(compsuffix);
    zsfree(complastprefix);
    zsfree(complastsuffix);
    zsfree(compiprefix);
    zsfree(compisuffix);
    zsfree(compqiprefix);
    zsfree(compqisuffix);
    zsfree(compcontext);
    zsfree(compparameter);
    zsfree(compredirect);
    zsfree(compquote);
    zsfree(compqstack);
    zsfree(compquoting);
    zsfree(comprestore);
    zsfree(complist);
    zsfree(compinsert);
    zsfree(compexact);
    zsfree(compexactstr);
    zsfree(comppatmatch);
    zsfree(comppatinsert);
    zsfree(complastprompt);
    zsfree(comptoend);
    zsfree(compoldlist);
    zsfree(compoldins);
    zsfree(compvared);

    hascompmod = 0;

    return 0;
}