mirror of
https://github.com/zsh-users/zsh
synced 2024-11-02 20:47:49 +00:00
3ad2ca3305
For read -k and read -q where we use ZLE, we just want a single key and not full ZLE processing. So don't handle timed ZLE functions when preforming the read.
1753 lines
46 KiB
C
1753 lines
46 KiB
C
/*
|
|
* 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;
|
|
|
|
/**/
|
|
int keybuflen;
|
|
|
|
static int 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)
|
|
{
|
|
Keymap oldm = localkeymap;
|
|
localkeymap = m;
|
|
if (oldm && !m)
|
|
{
|
|
/*
|
|
* No local keymap; so we are returning to the global map. If
|
|
* the user ^Ced in the local map, they probably just want to go
|
|
* back to normal editing. So remove the interrupt error
|
|
* status.
|
|
*/
|
|
errflag &= ~ERRFLAG_INT;
|
|
}
|
|
}
|
|
|
|
/* Reopen the currently selected keymap, in case it got deleted. This *
|
|
* 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, const 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[1]);
|
|
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(IS_THINGY(fn, 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 *)zshcalloc(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 oppmap = newkeymap(NULL, "viopp");
|
|
Keymap vismap = newkeymap(NULL, "visual");
|
|
Keymap smap = newkeymap(NULL, ".safe");
|
|
Keymap vimaps[2], vilmaps[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_Dselfinsert);
|
|
unrefthingy(t_Dselfinsert);
|
|
unrefthingy(t_Dselfinsert);
|
|
smap->first['\n'] = refthingy(t_Dacceptline);
|
|
smap->first['\r'] = refthingy(t_Dacceptline);
|
|
|
|
/* 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');
|
|
}
|
|
vilmaps[0] = oppmap;
|
|
vilmaps[1] = vismap;
|
|
for (i = 0; i < 2; i++) {
|
|
/* vi visual selection and operator pending local maps */
|
|
kptr = vilmaps[i];
|
|
add_cursor_key(kptr, TCUPCURSOR, t_upline, 'A');
|
|
add_cursor_key(kptr, TCDOWNCURSOR, t_downline, 'B');
|
|
bindkey(kptr, "k", refthingy(t_upline), NULL);
|
|
bindkey(kptr, "j", refthingy(t_downline), NULL);
|
|
bindkey(kptr, "aa", refthingy(t_selectashellword), NULL);
|
|
bindkey(kptr, "ia", refthingy(t_selectinshellword), NULL);
|
|
bindkey(kptr, "aw", refthingy(t_selectaword), NULL);
|
|
bindkey(kptr, "iw", refthingy(t_selectinword), NULL);
|
|
bindkey(kptr, "aW", refthingy(t_selectablankword), NULL);
|
|
bindkey(kptr, "iW", refthingy(t_selectinblankword), NULL);
|
|
}
|
|
/* escape in operator pending cancels the operation */
|
|
bindkey(oppmap, "\33", refthingy(t_vicmdmode), NULL);
|
|
bindkey(vismap, "\33", refthingy(t_deactivateregion), NULL);
|
|
bindkey(vismap, "o", refthingy(t_exchangepointandmark), NULL);
|
|
bindkey(vismap, "p", refthingy(t_putreplaceselection), NULL);
|
|
bindkey(vismap, "u", refthingy(t_vidowncase), NULL);
|
|
bindkey(vismap, "U", refthingy(t_viupcase), NULL);
|
|
bindkey(vismap, "x", refthingy(t_videlete), NULL);
|
|
bindkey(vismap, "~", refthingy(t_vioperswapcase), NULL);
|
|
|
|
/* vi mode: some common vim bindings */
|
|
bindkey(amap, "ga", refthingy(t_whatcursorposition), NULL);
|
|
bindkey(amap, "ge", refthingy(t_vibackwardwordend), NULL);
|
|
bindkey(amap, "gE", refthingy(t_vibackwardblankwordend), NULL);
|
|
bindkey(amap, "gg", refthingy(t_beginningofbufferorhistory), NULL);
|
|
bindkey(amap, "gu", refthingy(t_vidowncase), NULL);
|
|
bindkey(amap, "gU", refthingy(t_viupcase), NULL);
|
|
bindkey(amap, "g~", refthingy(t_vioperswapcase), NULL);
|
|
bindkey(amap, "g~~", NULL, "g~g~");
|
|
bindkey(amap, "guu", NULL, "gugu");
|
|
bindkey(amap, "gUU", NULL, "gUgU");
|
|
|
|
/* 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);
|
|
|
|
/* bracketed paste applicable to all keymaps */
|
|
bindkey(emap, "\33[200~", refthingy(t_bracketedpaste), NULL);
|
|
bindkey(vmap, "\33[200~", refthingy(t_bracketedpaste), NULL);
|
|
bindkey(amap, "\33[200~", refthingy(t_bracketedpaste), 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(oppmap, "viopp", 0);
|
|
linkkeymap(vismap, "visual", 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 */
|
|
/*************************/
|
|
/**/
|
|
#ifdef MULTIBYTE_SUPPORT
|
|
/*
|
|
* Get the remainder of a character if we support multibyte
|
|
* input strings. It may not require any more input, but
|
|
* we haven't yet checked. What's read in so far is available
|
|
* in keybuf; if we read more we will top keybuf up.
|
|
*
|
|
* This version is used when we are still resolving the input key stream
|
|
* into bindings. Once that has been done this function shouldn't be
|
|
* used: instead, see getrestchar() in zle_main.c.
|
|
*
|
|
* This supports a self-insert binding at any stage of a key sequence.
|
|
* Typically we handle 8-bit characters by having only the first byte
|
|
* bound to self insert; then we immediately get here and read in as
|
|
* many further bytes as necessary. However, it's possible that any set
|
|
* of bytes up to full character is bound to self-insert; then we get
|
|
* here later and read as much as possible, which could be a complete
|
|
* character, from keybuf before attempting further input.
|
|
*
|
|
* At the end of the process, the full multibyte character is available
|
|
* in keybuf, so the return value may be superfluous.
|
|
*/
|
|
|
|
/**/
|
|
mod_export ZLE_INT_T
|
|
getrestchar_keybuf(void)
|
|
{
|
|
char c;
|
|
wchar_t outchar;
|
|
int inchar, timeout, bufind = 0, buflen = keybuflen;
|
|
static mbstate_t mbs;
|
|
size_t cnt;
|
|
|
|
/*
|
|
* We are guaranteed to set a valid wide last character,
|
|
* although it may be WEOF (which is technically not
|
|
* a wide character at all...)
|
|
*/
|
|
lastchar_wide_valid = 1;
|
|
memset(&mbs, 0, sizeof mbs);
|
|
|
|
/*
|
|
* Return may be zero if we have a NULL; handle this like
|
|
* any other character.
|
|
*/
|
|
while (1) {
|
|
if (bufind < buflen) {
|
|
c = STOUC(keybuf[bufind++]);
|
|
if (c == Meta) {
|
|
DPUTS(bufind == buflen, "Meta at end of keybuf");
|
|
c = STOUC(keybuf[bufind++]) ^ 32;
|
|
}
|
|
} else {
|
|
/*
|
|
* Always apply KEYTIMEOUT to the remains of the input
|
|
* character. The parts of a multibyte character should
|
|
* arrive together. If we don't do this the input can
|
|
* get stuck if an invalid byte sequence arrives.
|
|
*/
|
|
inchar = getbyte(1L, &timeout, 1);
|
|
/* getbyte deliberately resets lastchar_wide_valid */
|
|
lastchar_wide_valid = 1;
|
|
if (inchar == EOF) {
|
|
memset(&mbs, 0, sizeof mbs);
|
|
if (timeout)
|
|
{
|
|
/*
|
|
* This case means that we got a valid initial byte
|
|
* (since we tested for EOF above), but the followup
|
|
* timed out. This probably indicates a duff character.
|
|
* Return a '?'.
|
|
*/
|
|
lastchar = '?';
|
|
return lastchar_wide = L'?';
|
|
}
|
|
else
|
|
return lastchar_wide = WEOF;
|
|
}
|
|
c = inchar;
|
|
addkeybuf(inchar);
|
|
}
|
|
|
|
cnt = mbrtowc(&outchar, &c, 1, &mbs);
|
|
if (cnt == MB_INVALID) {
|
|
/*
|
|
* Invalid input. Hmm, what's the right thing to do here?
|
|
*/
|
|
memset(&mbs, 0, sizeof mbs);
|
|
return lastchar_wide = WEOF;
|
|
}
|
|
if (cnt != MB_INCOMPLETE)
|
|
break;
|
|
}
|
|
return lastchar_wide = (ZLE_INT_T)outchar;
|
|
}
|
|
/**/
|
|
#endif
|
|
|
|
/* 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;
|
|
int timeout = 0;
|
|
|
|
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(timeout) != 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 && !ispfx)
|
|
f = keybind(km, keybuf, &s);
|
|
ispfx |= keyisprefix(km, keybuf);
|
|
|
|
if (f != t_undefinedkey) {
|
|
lastlen = keybuflen;
|
|
func = f;
|
|
str = s;
|
|
lastc = lastchar;
|
|
|
|
/* can be patient with vi commands that need a motion operator: *
|
|
* they wait till a key is pressed for the movement anyway */
|
|
timeout = !(!virangeflag && !region_active && f && f->widget &&
|
|
f->widget->flags & ZLE_VIOPER);
|
|
#ifdef MULTIBYTE_SUPPORT
|
|
if ((f == Th(z_selfinsert) || f == Th(z_selfinsertunmeta)) &&
|
|
!lastchar_wide_valid && !ispfx) {
|
|
(void)getrestchar_keybuf();
|
|
lastlen = keybuflen;
|
|
}
|
|
#endif
|
|
}
|
|
if (!ispfx)
|
|
break;
|
|
}
|
|
if(!lastlen && keybuflen)
|
|
lastlen = keybuflen;
|
|
else
|
|
lastchar = lastc;
|
|
if(lastlen != keybuflen) {
|
|
/*
|
|
* We want to keep only the first lastlen bytes of the key
|
|
* buffer in the key buffer that were marked as used by the key
|
|
* binding above, and make the rest available for input again.
|
|
* That rest (but not what we are keeping) needs to be
|
|
* unmetafied.
|
|
*/
|
|
unmetafy(keybuf + lastlen, &keybuflen);
|
|
ungetbytes(keybuf+lastlen, keybuflen);
|
|
if(vichgflag)
|
|
curvichg.bufptr -= keybuflen;
|
|
keybuf[keybuflen = lastlen] = 0;
|
|
}
|
|
*funcp = func;
|
|
*strp = str;
|
|
return keybuf;
|
|
}
|
|
|
|
/**/
|
|
static void
|
|
addkeybuf(int c)
|
|
{
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* 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, 1);
|
|
|
|
if(c < 0)
|
|
return EOF;
|
|
addkeybuf(c);
|
|
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_unmeta(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) {
|
|
if (++hops == 20) {
|
|
zerr("string inserting another one too many times");
|
|
hops = 0;
|
|
return NULL;
|
|
}
|
|
ungetbytes_unmeta(str, strlen(str));
|
|
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;
|
|
}
|