/*
 * 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 */
};

/* Can't be deleted (.safe) */
#define KMN_IMMORTAL (1<<1)

struct keymap {
    Thingy first[256];	/* base binding of each character */
    HashTable multi;	/* multi-character bindings */
    /*
     * The "real" name of this keymap.
     * For an aliased keymap, this is the first name to be defined.
     * If this is deleted but there are other names we randomly pick another
     * one, avoiding the name "main".  The principal use
     * for this is to make it clear what "main" is aliased to.
     *
     * If "main" is the only name for this map, this will be NULL.
     * That's fine, there's no alias.  We'll pick a primary if we
     * alias "main" again.
     */
    KeymapName primary;
    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;
    char *prefix;
    int prefixlen;
};

/* This structure is used when scanning for prefix bindings to remove */

struct remprefstate {
    Keymap km;
    char *prefix;
    int prefixlen;
};

#define BS_LIST (1<<0)
#define BS_ALL  (1<<1)

/* local functions */

#include "zle_keymap.pro"

/* currently selected keymap, and its name */

/**/
Keymap curkeymap, localkeymap;
/**/
char *curkeymapname;

/* the hash table of keymap names */

/**/
mod_export HashTable keymapnamtab;

/* key sequence reading data */

/**/
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->cmpnodes    = strcmp;
    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) zshcalloc(sizeof(*kmn));

    kmn->keymap = keymap;
    return kmn;
}


/*
 * Reference a keymap from a keymapname.
 * Used when linking keymaps.  This includes the first link to a
 * newly created keymap.
 */

static void
refkeymap_by_name(KeymapName kmn)
{
    refkeymap(kmn->keymap);
    if (!kmn->keymap->primary && strcmp(kmn->nam, "main") != 0)
	kmn->keymap->primary = kmn;
}

/*
 * Communication to keymap scanner when looking for a new primary name.
 */
static Keymap km_rename_me;

/* Find a new primary name for a keymap.  See below. */

static void
scanprimaryname(HashNode hn, int ignored)
{
    KeymapName n = (KeymapName) hn;

    (void)ignored;

    /* Check if we've already found a new primary name. */
    if (km_rename_me->primary)
	return;
    /* Don't use "main". */
    if (!strcmp(n->nam, "main"))
	return;
    if (n->keymap == km_rename_me)
	km_rename_me->primary = n;
}

/*
 * Unreference a keymap from a keymapname.
 * Used when unlinking keymaps to ensure there is still a primary
 * name for the keymap, unless it is an unaliased "main".
 */
static void
unrefkeymap_by_name(KeymapName kmname)
{
    Keymap km = kmname->keymap;
    if (unrefkeymap(km) && km->primary == kmname) {
	/*
	 * The primary name for the keymap has gone,
	 * but the keymap is still referred to; find a new primary name
	 * for it.  Sort the keymap to make the result deterministic.
	 */
	/* Set the primary name to NULL so we can check if we've found one */
	km->primary = NULL;
	km_rename_me = km;
	scanhashtable(keymapnamtab, 1, 0, 0, scanprimaryname, 0);
	/* Just for neatness */
	km_rename_me = NULL;
    }
}


/**/
static void
freekeymapnamnode(HashNode hn)
{
    KeymapName kmn = (KeymapName) hn;

    zsfree(kmn->nam);
    unrefkeymap_by_name(kmn);
    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->cmpnodes    = strcmp;
    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) zshcalloc(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;

/**/
mod_export Keymap
newkeymap(Keymap tocopy, char *kmname)
{
    Keymap km = zshcalloc(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, UNUSED(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);
}

/**/
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, UNUSED(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;
}

/**/
mod_export int
unlinkkeymap(char *name, int ignm)
{
    KeymapName n = (KeymapName) keymapnamtab->getnode(keymapnamtab, name);
    if(!n)
	return 2;
    if(!ignm && (n->flags & KMN_IMMORTAL))
	return 1;
    keymapnamtab->freenode(keymapnamtab->removenode(keymapnamtab, name));
    return 0;
}

/**/
mod_export int
linkkeymap(Keymap km, char *name, int imm)
{
    KeymapName n = (KeymapName) keymapnamtab->getnode(keymapnamtab, name);
    if(n) {
	if(n->flags & KMN_IMMORTAL)
	    return 1;
	if(n->keymap == km)
	    return 0;
	unrefkeymap_by_name(n);
	n->keymap = km;
    } else {
	n = makekeymapnamnode(km);
	if (imm)
	    n->flags |= KMN_IMMORTAL;
	keymapnamtab->addnode(keymapnamtab, ztrdup(name), n);
    }
    refkeymap_by_name(n);
    return 0;
}

/**/
void
refkeymap(Keymap km)
{
    km->rc++;
}

/* Unreference keymap, returning new reference count, 0 if deleted */

/**/
int
unrefkeymap(Keymap km)
{
    if (!--km->rc) {
	deletekeymap(km);
	return 0;
    }

    return km->rc;
}

/* 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 = nicedup(name, 0);
	char *msg = tricat("No such keymap `", nm, "'");

	zsfree(nm);
	showmsg(msg);
	zsfree(msg);
	if(!fb)
	    return 1;
	km = openkeymap(name = ".safe");
    }
    if(name != curkeymapname) {
	char *oname = curkeymapname;

	curkeymapname = ztrdup(name);

	if (oname && zleactive && strcmp(oname, curkeymapname))
	    zlecallhook("zle-keymap-select", oname);
	zsfree(oname);
    }
    curkeymap = km;
    return 0;
}

/* Select a local key map. */

/**/
mod_export void
selectlocalmap(Keymap m)
{
    localkeymap = m;
}

/* 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.                                                   */

/**/
mod_export 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;
	    fs[1] = 0;
	    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, Options ops, UNUSED(int func))
{
    static struct opn {
	char o;
	char selp;
	int (*func) _((char *, char *, Keymap, char **, Options, char));
	int min, max;
    } const opns[] = {
	{ 'l', 0, bin_bindkey_lsmaps, 0,  -1 },
	{ '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 && !OPT_ISSET(ops,STOUC(op->o)); op++) ;
    if(op->o)
	for(opp = op; (++opp)->o; )
	    if(OPT_ISSET(ops,STOUC(opp->o))) {
		zwarnnam(name, "incompatible operation selection options");
		return 1;
	    }
    n = OPT_ISSET(ops,'e') + OPT_ISSET(ops,'v') + 
	OPT_ISSET(ops,'a') + OPT_ISSET(ops,'M');
    if(!op->selp && n) {
	zwarnnam(name, "keymap cannot be selected with -%c", op->o);
	return 1;
    }
    if(n > 1) {
	zwarnnam(name, "incompatible keymap selection options");
	return 1;
    }

    /* keymap selection */
    if(op->selp) {
	if(OPT_ISSET(ops,'e'))
	    kmname = "emacs";
	else if(OPT_ISSET(ops,'v'))
	    kmname = "viins";
	else if(OPT_ISSET(ops,'a'))
	    kmname = "vicmd";
	else if(OPT_ISSET(ops,'M')) {
	    kmname = OPT_ARG(ops,'M');
	} else
	    kmname = "main";
	km = openkeymap(kmname);
	if(!km) {
	    zwarnnam(name, "no such keymap `%s'", kmname);
	    return 1;
	}
	if(OPT_ISSET(ops,'e') || OPT_ISSET(ops,'v'))
	    linkkeymap(km, "main", 0);
    } else {
	kmname = NULL;
	km = NULL;
    }

    /* listing is a special case */
    if(!op->o && (!argv[0] || !argv[1])) {
	if(OPT_ISSET(ops,'e') || OPT_ISSET(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", op->o);
	return 1;
    } else if(op->max != -1 && n > op->max) {
	zwarnnam(name, "too many arguments for -%c", 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, UNUSED(char *kmname), UNUSED(Keymap km), char **argv, Options ops, UNUSED(char func))
{
    int ret = 0;
    if (*argv) {
	for (; *argv; argv++) {
	    KeymapName kmn = (KeymapName)
		keymapnamtab->getnode(keymapnamtab, *argv);
	    if (!kmn) {
		zwarnnam(name, "no such keymap: `%s'", *argv);
		ret = 1;
	    } else {
		scanlistmaps((HashNode)kmn, OPT_ISSET(ops,'L'));
	    }
	}
    } else {
	scanhashtable(keymapnamtab, 1, 0, 0, scanlistmaps, OPT_ISSET(ops,'L'));
    }
    return ret;
}

/**/
static void
scanlistmaps(HashNode hn, int list_verbose)
{
    KeymapName n = (KeymapName) hn;

    if (list_verbose) {
	Keymap km = n->keymap;
	/*
	 * Don't list ".safe" as a bindkey command; we can't
	 * actually create it that way.
	 */
	if (!strcmp(n->nam, ".safe"))
	    return;
	fputs("bindkey -", stdout);
	if (km->primary && km->primary != n) {
	    KeymapName pn = km->primary;
	    fputs("A ", stdout);
	    if (pn->nam[0] == '-')
		fputs("-- ", stdout);
	    quotedzputs(pn->nam, stdout);
	    fputc(' ', stdout);
	} else {
	    fputs("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(UNUSED(char *name), UNUSED(char *kmname), UNUSED(Keymap km), UNUSED(char **argv), UNUSED(Options ops), UNUSED(char func))
{
    keymapnamtab->emptytable(keymapnamtab);
    default_bindings();
    return 0;
}

/* delete named keymaps */

/**/
static int
bin_bindkey_del(char *name, UNUSED(char *kmname), UNUSED(Keymap km), char **argv, UNUSED(Options ops), UNUSED(char func))
{
    int ret = 0;

    do {
	int r = unlinkkeymap(*argv, 0);
	if(r == 1)
	    zwarnnam(name, "keymap name `%s' is protected", *argv);
	else if(r == 2)
	    zwarnnam(name, "no such keymap `%s'", *argv);
	ret |= !!r;
    } while(*++argv);
    return ret;
}

/* link named keymaps */

/**/
static int
bin_bindkey_link(char *name, UNUSED(char *kmname), Keymap km, char **argv, UNUSED(Options ops), UNUSED(char func))
{
    km = openkeymap(argv[0]);
    if(!km) {
	zwarnnam(name, "no such keymap `%s'", argv[0]);
	return 1;
    } else if(linkkeymap(km, argv[1], 0)) {
	zwarnnam(name, "keymap name `%s' is protected", argv[1]);
	return 1;
    }
    return 0;
}

/* create a new keymap */

/**/
static int
bin_bindkey_new(char *name, UNUSED(char *kmname), Keymap km, char **argv, UNUSED(Options ops), UNUSED(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]);
	return 1;
    }
    if(argv[1]) {
	km = openkeymap(argv[1]);
	if(!km) {
	    zwarnnam(name, "no such keymap `%s'", argv[0]);
	    return 1;
	}
    } else
	km = NULL;
    linkkeymap(newkeymap(km, argv[0]), argv[0], 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, UNUSED(char **argv), UNUSED(Options ops), UNUSED(char func))
{
    char m[3], *str;
    int i;
    Thingy fn;

    if(km->flags & KM_IMMUTABLE) {
	zwarnnam(name, "keymap `%s' is protected", kmname);
	return 1;
    }
#ifdef MULTIBYTE_SUPPORT
    zwarnnam(name, "warning: `bindkey -m' disables multibyte support");
#endif
    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, Options 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");
		return 1;
	    }
    }
    if(km->flags & KM_IMMUTABLE) {
	zwarnnam(name, "keymap `%s' is protected", kmname);
	return 1;
    }
    if (func == 'r' && OPT_ISSET(ops,'p')) {
	char *useq, *bseq;
	int len;
	struct remprefstate rps;
	rps.km = km;
	while ((useq = *argv++)) {
	    bseq = getkeystring(useq, &len, GETKEYS_BINDKEY, NULL);
	    rps.prefix = metafy(bseq, len, META_USEHEAP);
	    rps.prefixlen = strlen(rps.prefix);
	    scankeymap(km, 0, scanremoveprefix, &rps);
	}
	return 0;
    }
    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, GETKEYS_BINDKEY, NULL);
	    fn = NULL;
	    str = metafy(str, len, META_HREALLOC);
	} else {
	    fn = rthingy(*++argv);
	    str = NULL;
	}
	bseq = getkeystring(useq, &len, GETKEYS_BINDKEY, NULL);
	seq = metafy(bseq, len, META_USEHEAP);
	if(OPT_ISSET(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);
		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");
		unrefthingy(fn);
		ret = 1;
	    }
	}
    } while(*++argv);
    return ret;
}

/* Remove bindings for key sequences which have the given (proper) prefix. */

/**/
static void
scanremoveprefix(char *seq, UNUSED(Thingy bind), UNUSED(char *str), void *magic)
{
    struct remprefstate *rps = magic;

    if (strncmp(seq, rps->prefix, rps->prefixlen) || !seq[rps->prefixlen])
	return;

    bindkey(rps->km, seq, refthingy(t_undefinedkey), NULL);
}

/* 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, Options ops, UNUSED(char func))
{
    struct bindstate bs;

    bs.flags = OPT_ISSET(ops,'L') ? BS_LIST : 0;
    bs.kmname = kmname;
    if(argv[0] && !OPT_ISSET(ops,'p')) {
	int len;
	char *seq;

	seq = getkeystring(argv[0], &len, GETKEYS_BINDKEY, NULL);
	seq = metafy(seq, len, META_HREALLOC);
	bs.flags |= BS_ALL;
	bs.firstseq = bs.lastseq = seq;
	bs.bind = keybind(km, seq, &bs.str);
	bs.prefix = NULL;
	bs.prefixlen = 0;
	bindlistout(&bs);
    } else {
	/* empty prefix is equivalent to no prefix */
	if (OPT_ISSET(ops,'p') && (!argv[0] || argv[0][0])) {
	    if (!argv[0]) {
		zwarnnam(name, "option -p requires a prefix string");
		return 1;
	    }
	    bs.prefix = getkeystring(argv[0], &bs.prefixlen, GETKEYS_BINDKEY,
				     NULL);
	    bs.prefix = metafy(bs.prefix, bs.prefixlen, META_HREALLOC);
	    bs.prefixlen = strlen(bs.prefix);
	} else {
	    bs.prefix = NULL;
	    bs.prefixlen = 0;
	}
	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 (bs->prefixlen &&
	(strncmp(seq, bs->prefix, bs->prefixlen) || !seq[bs->prefixlen]))
	return;

    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) {
	if (bs->flags & BS_LIST)
	    quotedzputs(bs->bind->nam, stdout);
	else
	    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);
}

/* cleanup entry point (for unloading the zle module) */

/**/
void
cleanup_keymaps(void)
{
    unrefthingy(lastnamed);
    deletehashtable(keymapnamtab);
    zfree(keybuf, keybufsz);
}

static char *cursorptr;

/* utility function for termcap output routine to add to string */

static int 
add_cursor_char(int c)
{
    *cursorptr++ = c;
    return 0;
}

/* interrogate termcap for cursor keys and add bindings to keymap */

/**/
static void
add_cursor_key(Keymap km, int tccode, Thingy thingy, int defchar)
{
    char buf[2048];
    int ok = 0;

    /*
     * Be careful not to try too hard with bindings for dubious or
     * dysfunctional terminals.
     */
    if (tccan(tccode) && !(termflags & (TERM_NOUP|TERM_BAD|TERM_UNKNOWN))) {
	/*
	 * We can use the real termcap sequence.  We need to
	 * persuade termcap to output `move cursor 1 char' and capture it.
	 */
	cursorptr = buf;
	tputs(tcstr[tccode], 1, add_cursor_char);
	*cursorptr = '\0';

	/*
	 * Sanity checking.  If the cursor key is zero-length (unlikely,
	 * but this is termcap we're talking about), or it's a single
	 * character, then we don't bind it.
	 */
	if (buf[0] && buf[1] && (buf[0] != Meta || buf[2]))
	    ok = 1;
    }
    if (!ok) {
	/* Assume the normal VT100-like values. */
	sprintf(buf, "\33[%c", defchar);
    }
    bindkey(km, buf, refthingy(thingy), NULL);

    /*
     * If the string looked like \e[? or \eO?, bind the other one, too.
     * This is necessary to make cursor keys work on many xterms with
     * both normal and application modes.
     */
    if (buf[0] == '\33' && (buf[1] == '[' || buf[1] == 'O') && 
	buf[2] && !buf[3])
    {
	buf[1] = (buf[1] == '[') ? 'O' : '[';
	bindkey(km, buf, refthingy(thingy), NULL);
    }
}

/* 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");
    Keymap vimaps[2], kptr;
    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.                  */

    vimaps[0] = vmap;
    vimaps[1] = amap;
    for (i = 0; i < 2; i++) {
	kptr = vimaps[i];
	/* vi command and insert modes: arrow keys */
	add_cursor_key(kptr, TCUPCURSOR, t_uplineorhistory, 'A');
	add_cursor_key(kptr, TCDOWNCURSOR, t_downlineorhistory, 'B');
	add_cursor_key(kptr, TCLEFTCURSOR, t_vibackwardchar, 'D');
	add_cursor_key(kptr, TCRIGHTCURSOR, t_viforwardchar, 'C');
    }

    /* emacs mode: arrow keys */ 
    add_cursor_key(emap, TCUPCURSOR, t_uplineorhistory, 'A');
    add_cursor_key(emap, TCDOWNCURSOR, t_downlineorhistory, 'B');
    add_cursor_key(emap, TCLEFTCURSOR, t_backwardchar, 'D');
    add_cursor_key(emap, TCRIGHTCURSOR, t_forwardchar, 'C');
   
    /* 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", 0);
    linkkeymap(emap, "emacs", 0);
    linkkeymap(amap, "vicmd", 0);
    linkkeymap(smap, ".safe", 1);
    if (((ed = zgetenv("VISUAL")) && strstr(ed, "vi")) ||
	((ed = zgetenv("EDITOR")) && strstr(ed, "vi")))
	linkkeymap(vmap, "main", 0);
    else
	linkkeymap(emap, "main", 0);

    /* the .safe map cannot be modified or deleted */
    smap->flags |= KM_IMMUTABLE;

    /* isearch keymap: initially empty */
    isearch_keymap = newkeymap(NULL, "isearch");
    linkkeymap(isearch_keymap, "isearch", 0);

    /* command keymap: make sure accept-line and send-break are bound */
    command_keymap = newkeymap(NULL, "command");
    command_keymap->first['\n'] = refthingy(t_acceptline);
    command_keymap->first['\r'] = refthingy(t_acceptline);
    command_keymap->first['G'&0x1F] = refthingy(t_sendbreak);
    linkkeymap(command_keymap, "command", 0);
}

/*************************/
/* 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 = lastchar;

    keybuflen = 0;
    keybuf[0] = 0;
    /*
     * getkeybuf returns multibyte strings, which may not
     * yet correspond to complete wide characters, regardless
     * of the locale.  This is because we can't be sure whether
     * the key bindings and keyboard input always return such
     * characters.  So we always look up bindings for each
     * chunk of string.  Intelligence within self-insert tries
     * to fix up insertion of real wide characters properly.
     *
     * Note that this does not stop the user binding wide characters to
     * arbitrary functions, just so long as the string used in the
     * argument to bindkey is in the correct form for the locale.
     * That's beyond our control.
     */
    while(getkeybuf(!!lastlen) != EOF) {
	char *s;
	Thingy f;
	int loc = !!localkeymap;
	int ispfx = 0;

	if (loc) {
	    loc = ((f = keybind(localkeymap, keybuf, &s)) != t_undefinedkey);
	    ispfx = keyisprefix(localkeymap, keybuf);
	}
	if (!loc)
	    f = keybind(km, keybuf, &s);
	ispfx |= keyisprefix(km, keybuf);

	if (f != t_undefinedkey) {
	    lastlen = keybuflen;
	    func = f;
	    str = s;
	    lastc = lastchar;
	}
	if (!ispfx)
	    break;
    }
    if(!lastlen && keybuflen)
	lastlen = keybuflen;
    else
	lastchar = lastc;
    if(lastlen != keybuflen) {
	unmetafy(keybuf + lastlen, &keybuflen);
	ungetbytes(keybuf+lastlen, keybuflen);
	if(vichgflag)
	    vichgbufptr -= keybuflen;
	keybuf[lastlen] = 0;
    }
    *funcp = func;
    *strp = str;
    return keybuf;
}

/*
 * Add a (possibly metafied) byte to the key input so far.
 * This handles individual bytes of a multibyte string separately;
 * see note in getkeymapcmd.  Hence there is no wide character
 * support at this level.
 *
 * TODO: Need to be careful about whether we return EOF in the
 * middle of a wide character.  However, I think we're OK since
 * EOF and 0xff are distinct and we're reading bytes from the
 * lower level, so EOF really does mean something went wrong.  Even so,
 * I'm worried enough to leave this note here for now.
 */

/**/
static int
getkeybuf(int w)
{
    int c = getbyte((long)w, NULL);

    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().    */

/**/
mod_export void
ungetkeycmd(void)
{
    ungetbytes(keybuf, keybuflen);
}

/* read a command from the current keymap, with widgets */

/**/
mod_export 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");
	    hops = 0;
	    return NULL;
	}
	pb = unmetafy(ztrdup(str), &len);
	ungetbytes(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;
}

/**/
mod_export void
zlesetkeymap(int mode)
{
    Keymap km = openkeymap((mode == VIMODE) ? "viins" : "emacs");
    if (!km)
	return;
    linkkeymap(km, "main", 0);
}

/**/
mod_export int
readcommand(UNUSED(char **args))
{
    Thingy thingy = getkeycmd();

    if (!thingy)
	return 1;

    setsparam("REPLY", ztrdup(thingy->nam));
    return 0;
}
