/************************************************************************ * This program is Copyright (C) 1986 by Jonathan Payne. JOVE is * * provided to you without charge, and with no warranty. You may give * * away copies of JOVE, including sources, provided that this notice is * * included in all the files. * ************************************************************************/ /*+++* * title: maps.c * abstract: keymap handling routines. * author: T.R.Hageman, Groningen, The Netherlands. * created: june 1990 * modified: 04-Jul-92 * description: *---*/ #include "tune.h" /* to allow definition of debug flags there. */ #ifdef DEBUG /* General debug flag, turn on file-specific flag. */ # ifndef MAPS_DEBUG # define MAPS_DEBUG 1 # endif #else # if MAPS_DEBUG /* File-specific debug flag, turn on general flag. */ # define DEBUG # endif #endif #include "jove.h" RCS("$Id: maps.c,v 14.31.0.12 1994/06/23 02:46:25 tom Exp tom $") #include "ctype.h" #define Extern #include "maps.h" #undef DEF_MAP #ifndef FIXED_MAPS # define DEF_MAP(name,binds,n) { DEFMAP(name, binds, n), \ &Maps[((n)&~SPARSE)-1] } #else # define DEF_MAP(name,binds,n) { DEFMAP(NULL, binds, n) } #endif #define DEFMAP(name,binds,n) FROZEN|KEYMAP|((n)&SPARSE), name, binds /* The builtin keymaps are defined by the array `Maps'. For backward compatibility's sake, the prefix maps are also entered into the commands table, tagged with FUNCTION|PREFIX|ARG(n), and the `c_proc' field pointing to the correspondent entry in `Maps'. If FIXED_MAPS is defined, the only way for the user to get to the keymaps is through the commands table. The builtin sparsemaps are associated in a similar fashion with their commands, that is, the corresponding command is tagged with SPARSE|ARG(____MAP_INDEX). */ public Keymap Maps[] = { { DEFMAP( "mainmap", mainmap, 0 ) }, DEF_MAP( "prefix-1", pref1map, 1 ), DEF_MAP( "prefix-2", pref2map, 2 ), DEF_MAP( "prefix-3", miscmap, 3 ) #ifndef FIXED_MAPS , DEF_MAP( "window-find-map", (data_obj **) WFindMap, WINDFINDMAP_INDEX | SPARSE ) , DEF_MAP( "help-map", (data_obj **) HelpMap, HELPMAP_INDEX | SPARSE ) #endif }; #define MAINMAP (&Maps[0]) #ifndef FIXED_MAPS /* Sparsemap speed optimization: first entry of sparsemap array can point to a bit vector that caches the key bindings in the sparsemap. */ # define MAPSET_MAGIC 0x1B1D /* Impossible keystroke. */ # define MAPSET_OP(smap,OP,c) (((sparsemap*)(smap))->s_chr != MAPSET_MAGIC ||\ BITSET_OP(((sparsemap*)(smap))->s_cmd,OP,(c))) private data_obj **bind_sparse __(( Keymap *_(map), int _(c), int _(options) )); private void Active __(( void )); /* `keymaps' is the head of the list of all existing keymaps. `activemap' is the head of the list of currently active (top-level) keymaps. `activebuf' is the buffer for which the active list was last calculated. This is remembered so that the active list can be updated lazily, that is, only when it is needed. */ public Keymap *keymaps = &Maps[sizeof Maps / sizeof Maps[0] - 1], *activemap = MAINMAP; private Buffer *activebuf ZERO; /* * Return the up-to-date active keymap. */ public Keymap * active_map() { if (curbuf != activebuf) Active(); return activemap; } /* * create a keymap and link it in the list. * It is assumed it does not already exist. */ private Keymap *map_create __(( const char *_(name), int _(size) )); private Keymap * map_create(name, size) const char *name; int size; { #define FULL_MAPSIZE (sizeof(data_obj *) * MAPSIZE) register Keymap *map; register size_t total_size = sizeof(Keymap) + FULL_MAPSIZE; register int Type = KEYMAP; /* Only use sparse map if requested and it gains memory. */ if (size > 1 && size < (FULL_MAPSIZE - BITSET_SIZE(MAPSIZE))/sizeof(sparsemap) -1) { total_size = (sizeof(Keymap) + BITSET_SIZE(MAPSIZE) + sizeof(sparsemap) /*sentinel*/ + sizeof(sparsemap) * size); Type |= SPARSE; } map = emalloc(total_size); bzero(map, total_size); if (Type & SPARSE) { register sparsemap *sp = (sparsemap *) &map[1]; sp[size].s_chr = -1; /* set sentinel. */ sp[0].s_chr = MAPSET_MAGIC; /* set index cache. */ sp[0].s_cmd = (data_obj *) &sp[size + 1]; } map->Type = Type; map->Name = name; map->k_bind = (data_obj **) &map[1]; map->k_next = keymaps; keymaps = map; return map; } #ifdef IPROCS private Keymap *proc_map __(( void )); private Keymap * proc_map() { static Keymap *procmap ZERO; if (procmap == NULL) (procmap = map_create("process-map", 20))->Type |= FROZEN; /* so you can't delete it... */ return procmap; } #endif /* IPROCS */ private data_obj *findmap __(( const char *_(prompt) )); private data_obj * findmap(prompt) const char *prompt; { const char *names[400]; /* should be enough... */ register Keymap *map; /* build name vector */ { register const char **strs = names; map = keymaps; do *strs++ = map->Name; while (map = map->k_next); *strs = NULL; } { register int i = NOTHING; register const char *p; if ((p = prompt) == NULL) { /* [TRH] this is a kludge */ p = ProcFmt; i = RET_STATE; } if ((i = complete(names, i, p)) < 0) { if (i == ORIGINAL || i == AMBIGUOUS) return NULL; complain(NullStr); } for (map = keymaps; --i >= 0; map = map->k_next) ; } return (data_obj *) map; } /* determine the active keymaps; MAINMAP is always at the end of the list */ private void Active_1 __(( Keymap *_(map) )); private void Active() { register Buffer *cb = curbuf; register Keymap *map, *prev = MAINMAP; activebuf = cb; /* Start with a clean slate. */ map = keymaps; do { map->k_active = NULL; } while (K_NEXT(map)); #ifdef IPROCS if (cb->b_process) { map = proc_map(), map->k_active = prev, prev = map; } #endif if ((map = cb->b_keymap) && map != prev) { map->k_active = prev, prev = map; } activemap = prev; if (prev->k_active) { /* Scan Active maps for hidden Prefix maps. */ map_init_visit(); Active_1(prev); } } /* * Recursively find prefixes that cover others in the active list, * and chain these into their own active-list. */ private void Active_2 __(( Keymap *_(map), int _(c), Keymap *_(prefix) )); private void Active_1(map) register Keymap *map; { char found[MAPSIZE]; if (map->k_active == NULL) return; bzero(found, sizeof found); do { /* Fast scan through the keymaps. */ if (map->Type & SPARSE) { register sparsemap *sp = (sparsemap *) map->k_bind; register int c = sp->s_chr; do { register data_obj *dp; register Keymap *prefix; if (c == MAPSET_MAGIC) continue; if ((dp = sp->s_cmd) == NULL) continue; if (found[c]) continue; if (!isfunckey(c) && found[tolower(c)]++) continue; found[c]++; if ((prefix = IsPrefix(dp)) == NULL) continue; Active_2(map, c, prefix); } while ((c = (++sp)->s_chr) >= 0); } else { register int c = 0; do { register data_obj *dp; register Keymap *prefix; if ((dp = map->k_bind[c]) == NULL) continue; if (found[c]++) continue; if ((prefix = IsPrefix(dp)) == NULL) continue; Active_2(map, c, prefix); } while (++c < MAPSIZE); } } while (K_ACTIVE_NEXT(map)->k_active); } private void Active_2(map, c, prefix) register Keymap *map; int c; Keymap *prefix; { register Keymap *head = prefix; if (head->Type & VISITED) return; head->Type |= VISITED; while (K_ACTIVE_NEXT(map)) { register data_obj *dp; register Keymap *next; if ((dp = get_shallow_bind(map, c)) == NULL) continue; if ((next = IsPrefix(dp)) == NULL) break; if (next == head) continue; head->k_active = next; /* Check for loops; break the loop if necessary. */ do { if (next->k_active == head) next->k_active = NULL; } while (next = next->k_active); head = head->k_active; /* No point in continuing if we've already followed the chain in the past. */ if (head->Type & VISITED) break; head->Type |= VISITED; } /* and finally recurse into `prefix' to see if it contains keymaps. */ Active_1(prefix); } /*===== Create your own keymap. ==============================================*/ DEF_CMD( "make-keymap", NonExisting, NO ); _IF(def FIXED_MAPS) DEF_CMD( "make-keymap", MakeMap, NO ) _IF(ndef FIXED_MAPS) { register Keymap *map; if (map = (Keymap *) findmap((char *) 0)) s_mess("%N: %f %s [already exists]", map->Name); else map_create(copystr(Minibuf), exp); } /*===== Delete a keymap. =====================================================*/ DEF_CMD( "delete-keymap", DelMap, NO ) _IF(ndef FIXED_MAPS) { register Keymap *map = (Keymap *) findmap(ProcFmt), **hp; register Buffer *b; /* Don't allow builtin maps to be deleted. */ if (map->Type & FROZEN) complain("%N: %f %s [not that one!]", map->Name); /* unlink this one from the keymap list */ for (hp = &keymaps; (*hp) != map; hp = &(*hp)->k_next) if (*hp == NULL) complain("?keymap?"); (*hp) = map->k_next; /* now, delete any references to this keymap */ DelBind((data_obj *) map); if (b = world) do { if (b->b_keymap == map) b->b_keymap = NULL; } while (b = b->b_next); /* Now, delete the map itself. */ free((void_*) map->Name); free((void_*) map); Active(); /* ... may have been changed */ } /*===== Bind (or unbind) a keymap to curbuf. =================================*/ DEF_CMD( "bind-keymap-to-buffer", BindBuf, NO ) _IF(ndef FIXED_MAPS) { register Keymap *map = (Keymap *) findmap(ProcFmt); if (map == MAINMAP) map = NULL; else if (map->Type & FROZEN) complain("%N: %f %s [not that one!]", map->Name); curbuf->b_keymap = map; Active(); } #endif /* !FIXED_MAPS */ public data_obj * (*const Find[])__((const char *_(prompt))) = { findcom, /* FUNCTION */ findvar, /* VARIABLE */ findmac, /* MACRO */ #ifndef FIXED_MAPS findmap, /* KEYMAP */ #endif }; #define MAP(pp) ((Keymap *)(void_*)(pp)) /* {{Silly ^^^^^^^^ cast needed for crufty MSC compiler.}} */ #define FUN(cp) (((Command *)(cp))->c_proc) public Keymap * IsPrefix(dp) const data_obj *dp; { register Keymap *kp; if (kp = (Keymap *) dp) { switch (kp->Type & (TYPEMASK|SPARSE)) { #ifndef FIXED_MAPS case FUNCTION|SPARSE: /* This is to make commands that use a sparsemap like "window-find" `transparent' with respect to key (un)binding and -listing. (The command is still executed as a command since ExecCmd() resolves keymaps slightly differently than this routine.) These commands should also have the index (in Maps[]) of the corresponding sparsemap ARG()ed in. */ kp = &Maps[ObjArg(kp)]; break; case KEYMAP|SPARSE: break; case KEYMAP: break; #endif case FUNCTION|PREFIX: kp = MAP(FUN(kp)); break; default: kp = NULL; } } return kp; } /* * Setup keymaps for recursive visitation. */ void map_init_visit() { register Keymap *map; /* first un-visit all keymaps... */ map = keymaps; do map->Type &= ~VISITED; while (K_NEXT(map)); /* then visit active keymaps */ map = active_map(); do map->Type |= VISITED; while (K_ACTIVE_NEXT(map)); } /* Walk through the keymaps to find the location of the binding corresponding to the key sequence. The walk starts at `initial_map' and continues until the key sequence has been recognized, unless `dp' is NULL and the sequence stops early. (this allows you to unbind keymaps). */ DEF_REF( "quoted-insert", QuoteCmd ); public data_obj ** #ifndef FIXED_MAPS bind_key(initial_map, options) Keymap *initial_map; #else bind_key(options) # define initial_map MAINMAP #endif int options; { register Keymap *map = initial_map; register data_obj **this_bind; register int c, prev_c = EOF; #ifndef TINY /* to keep key display correct if keymap completion is not enforced. */ register char *end_prompt = mesgbuf; #endif for (;;) { #ifndef TINY if (!InJoverc && (options & MAP_ALLOW_PREFIX)) { end_prompt += strlen(end_prompt); } #endif c = addgetc(); if (options & MAP_ALLOW_PREFIX) { if (prev_c != QuoteChar) { #ifndef TINY register char savec = *end_prompt; *end_prompt = '\0'; #endif if (c == CR || c == LF || c < 0) { if (prev_c >= 0) break; } /* allows unbinding of "prefix {C-J,C-M,C-Q}" */ if (mainmap[c] == QuoteCmd) { prev_c = QuoteChar; continue; } if (c == AbortChar) complain("[aborted]"); #ifndef TINY *end_prompt = savec; #endif } } if ((unsigned) c >= MAPSIZE) /* includes (c < 0) */ complain((c >= 0) ? (char *) 0 : (prev_c < 0) ? "[Empty key sequence]" : "[Premature end of key sequence]"); prev_c = c; /* * if unbinding, walk the active list while the entry is unused */ do { #ifndef FIXED_MAPS if (map->Type & SPARSE) this_bind = bind_sparse(map, c, options); else #endif this_bind = &map->k_bind[c]; } while (K_ACTIVE_NEXT(map) && *this_bind == NULL && (options & MAP_FOLLOW_ACTIVE_LIST)); if ((map = IsPrefix( #ifndef FIXED_MAPS /* if we didn't follow active list before, do so now. */ (*this_bind == NULL && map) ? get_bind(map, c) : #endif *this_bind)) == NULL) break; } #ifdef MENUS if (options & (MAP_BIND|MAP_UNBIND)) Bindchange++; #endif return this_bind; } /*===== Unbind a key sequence. ===============================================*/ DEF_CMD( "unbind-key", UnbindC, NO ) { #define UNBIND_OPTIONS MAP_ALLOW_PREFIX|MAP_FOLLOW_ACTIVE_LIST|MAP_UNBIND #ifdef FIXED_MAPS # define map MAINMAP # define options UNBIND_OPTIONS #else /* !FIXED_MAPS */ register Keymap *map = active_map(); register int options = UNBIND_OPTIONS; if (exp_p) { if (exp <= 0) map = MAINMAP; else map = (Keymap *) findmap(": %f, in keymap: "); options &= ~MAP_FOLLOW_ACTIVE_LIST; } #endif /* FIXED_MAPS */ s_mess(ProcFmt); *MapKey(map, options) = NULL; #undef map #undef options #undef UNBIND_OPTIONS } /*===== Bind (or unbind) something. ==========================================*/ DEF_CMD( "bind-to-key", Bind, ARG(FUNCTION) ); DEF_CMD( "bind-keymap-to-key", Bind, ARG(KEYMAP) ); _IF(ndef FIXED_MAPS) DEF_CMD( "bind-macro-to-key", Bind, ARG(MACRO) ); DEF_CMD( "bind-variable-to-key", Bind, ARG(VARIABLE) ); DEF_CMD( "process-bind-to-key", Bind, ARG(PMAP|FUNCTION) ); _IF(ndef FIXED_MAPS)_IF(def IPROCS) DEF_CMD( "process-bind-macro-to-key", Bind, ARG(PMAP|MACRO) ) _IF(ndef FIXED_MAPS)_IF(def IPROCS) { register data_obj *dp = NULL; #ifdef FIXED_MAPS # define kind ObjArg(LastCmd) # define map MAINMAP #else /* !FIXED_MAPS */ register int kind = ObjArg(LastCmd); register Keymap *map = active_map(); # ifdef IPROCS if (kind & PMAP) { map = proc_map(); kind &= ~PMAP; } else # endif /* IPROCS */ if (exp_p) { if (exp <= 0) map = MAINMAP; else map = (Keymap *) findmap(": %f, in keymap: "); } #endif /* FIXED_MAPS */ dp = FindObj(kind, ProcFmt); s_mess(ProcArgFmt, dp->Name); *MapKey(map, MAP_BIND) = dp; #undef map #undef kind #ifndef FIXED_MAPS if (IsPrefix(dp)) /* This may have an effect on active keymaps. */ Active(); #endif } /* Delete all bindings of `dp' from the keymaps */ public void DelBind(dp) const data_obj *dp; { register Keymap *map = keymaps; do { /* keymaps list is guaranteed to be non-empty */ #ifndef FIXED_MAPS if (map->Type & SPARSE) { register sparsemap *sp = (sparsemap *) map->k_bind; do { if (sp->s_cmd == dp) { MAPSET_OP(map->k_bind, &= ~, sp->s_chr); sp->s_cmd = NULL; } } while ((++sp)->s_chr >= 0); } else #endif { register data_obj **mp = map->k_bind, **ep = mp + MAPSIZE; do { if (*mp == dp) *mp = NULL; } while (++mp != ep); } } while (K_NEXT(map)); } /*===== Execute a command (macro, variable, whatever...) =====================*/ int Interactive ZERO; const char ProcFmt[] = "%N: %f ", ProcDefFmt[] = "%N: %f (default %s) ", ProcArgFmt[] = "%N: %f %s "; public void ExecCmd(cp) register const data_obj *cp; { while (LastCmd = cp) { /* ordinarily just once */ register void (*proc)__(( void )) = FUN(cp); register int Type = cp->Type; switch (Type & TYPEMASK) { case FUNCTION|EDIT|NEGATE: exp = -exp; /* fall through. */ case FUNCTION|EDIT: if (MinorMode(View)) /* inline for efficiency. */ view_mode_check(); (*proc)(); break; case FUNCTION|NEGATE: exp = -exp; /* fall through. */ case FUNCTION: (*proc)(); break; #ifndef FIXED_MAPS case KEYMAP: proc = (void (*)__((void))) cp; /* fall through. */ #endif case FUNCTION|PREFIX: # define c Type /* alias! */ c = waitchar(); if (c == AbortChar) { message("[Aborted]"); rbell(); break; } cp = get_bind(MAP(proc), c); # undef c /* unalias! */ continue; case MACRO: do_macro((Macro *) cp); break; case FUNCTION|MAJOR_MODE: Type >>= 8; SetMajor(Type); break; case FUNCTION|MINOR_MODE: Type >>= 8; TogMinor(Type); break; default: if ((Type & REALTYPEMASK) != VARIABLE) complain("can't execute \"%f\", type 0x%x", Type); DoSetVar((Variable *) cp); break; } return; } /* only get here if cp == NULL, i.e., unbound. */ s_mess("[%sunbound]", key_strokes()); rbell(); } /* Dispatch the command for key `c' */ public void dispatch(c) int c; { register const data_obj *cp; register const Keymap *map = active_map(); this_cmd = 0; do { if (cp = get_shallow_bind(map, c)) { ExecCmd(cp); return; } } while (K_ACTIVE_NEXT(map)); rbell(); errormsg = NO; } #ifndef get_bind public data_obj * get_bind(map, c) register const Keymap *map; int c; { register data_obj *dp; do { if ((dp = get_shallow_bind(map, c))) break; } while (K_ACTIVE_NEXT(map)); return dp; } #endif /* !get_bind */ /* Support command: complain if current buffer in View mode. */ void view_mode_check() { register Buffer *cb = curbuf; if (BufMinorMode(cb, View)) complain("[Can't modify buffer \"%b\" in View mode]", cb); } /*===== Sparsemap handling. ==================================================*/ /* * Find a binding for character `c' in sparsemap `smap' (case-insensitive). #ifndef TINY * If `c' equals -1 (== sentinel for `smap->s_chr') just build a prompt message. #endif * {{This should be used only for builtin sparsemaps--and these should not * have an index cache.}} */ data_obj * findsparse(sparse_map, c) const sparsemap *sparse_map; register int c; { /* search in smap and build message on the fly... */ register const sparsemap *smap = sparse_map; register const char *fmt; static const char Format[] = ", %p: %s"; #ifndef TINY register const char *theFormat = Format; # define Format theFormat if (c < 0) { s_mess("%N: %f ("); Format = ", %p"; /* terse... */ } else #endif { mesgbuf[0] = '\0'; if (!isfunckey(c)) c = toupper(c); /* Sparsemaps are case-insensitive. */ } for (fmt = Format + 2; /* empty */; ++smap) { if (smap->s_cmd == NULL) { #ifndef FIXED_MAPS if (smap->s_chr >= 0) /* empty slot */ continue; /* else end of map (sentinel: smap->s_chr == -1) */ #endif #ifndef TINY if (c < 0) { /* make prompt */ add_mess(") "); break; } #endif add_mess("."); #ifndef TINY /* extended help, in case it won't fit on the message line. Do it AFTER we scanned through the table, so `?' can still be bound to something else if the user so desires. */ if (c == '?') { TOstart("*Help*", TRUE); Typeout("Key bindings for \"%f\"."); Typeout((char *) 0); smap = sparse_map; do { # ifndef FIXED_MAPS if (smap->s_cmd == NULL) continue; # endif Typeout(" %-5p %s", smap->s_chr, smap->s_cmd->Name); } while ((++smap)->s_chr >= 0); TOstop(); mesgbuf[0] = '\0'; } #endif complain((char *)0); } if (smap->s_chr == c) { mesgbuf[0] = '\0'; /* discard the error message... */ break; } add_mess(fmt, smap->s_chr, smap->s_cmd->Name); fmt = Format; } return smap->s_cmd; } #ifndef FIXED_MAPS /* This is similar to findsparse(), but without all the frills. */ public data_obj * get_sparse_bind(sparse_map, c) const Keymap *sparse_map; register int c; { register const sparsemap *smap = (sparsemap *) sparse_map->k_bind; ASSERT(sparse_map->Type & SPARSE); if (!isfunckey(c)) c = toupper(c); /* Sparsemaps are case-insensitive. */ if (!MAPSET_OP(smap, &, c)) return NULL; for (; /* empty */; ++smap) { if (smap->s_cmd == NULL) { if (smap->s_chr >= 0) /* empty slot */ continue; /* else end of map (sentinel: smap->s_chr == -1) */ break; } if (smap->s_chr == c) { break; } } return smap->s_cmd; } private data_obj ** bind_sparse(map, c, options) Keymap *map; register int c; int options; { register sparsemap *smap; register sparsemap *this_bind = NULL; ASSERT(map->Type & SPARSE); if (!isfunckey(c)) c = toupper(c); /* Sparsemaps are case-insensitive. */ for (smap = (sparsemap *) map->k_bind; /* empty */; smap++) { if (smap->s_cmd != NULL) { if (smap->s_chr != c) continue; /* Match! */ this_bind = smap; if (options & MAP_UNBIND) { smap = (sparsemap *) map->k_bind; MAPSET_OP(smap, &= ~, c); } break; } /* else unbound (smap->s_cmd == NULL) */ if (this_bind == NULL) /* remember first empty slot. */ this_bind = smap; if (smap->s_chr >= 0) /* empty slot */ continue; /* else end of map (sentinel: smap->s_chr == -1) */ /* If we're trying to bind something and the sparsemap is full, expand it by allocating a larger table and copying the old contents. */ if (!(options & MAP_BIND)) break; if (this_bind == smap) { # define SPARSE_EXPAND 8 register int size = (char *) smap - (char *) map->k_bind; smap = emalloc(size + sizeof(*smap) * (SPARSE_EXPAND+1)); /* make this_bind point in new table. */ this_bind = (sparsemap *)((char *) smap + size); byte_copy(map->k_bind, smap, size); bzero(this_bind, (sizeof(*smap) * (SPARSE_EXPAND+1))); this_bind[SPARSE_EXPAND].s_chr = -1; /* dispose contents only if we can... */ if (!ISSTATIC(map->k_bind) && map->k_bind != (data_obj **)&map[1]) free((void_*) map->k_bind); map->k_bind = (data_obj **) smap; } this_bind->s_chr = c; smap = (sparsemap *) map->k_bind; MAPSET_OP(smap, |=, c); break; } return &this_bind->s_cmd; } #endif /* FIXED_MAPS */ /*====================================================================== * $Log: maps.c,v $ * Revision 14.31.0.12 1994/06/23 02:46:25 tom * (findsparse): fix name of scratch buffer. * * Revision 14.31.0.11 1994/06/10 13:51:12 tom * (view_mode_check): new support function; (ExecCmd): use it. * * Revision 14.31 1993/02/15 02:01:48 tom * allow debug options to be specified in "tune.h" cf. "Local.h". * * Revision 14.30 1993/02/05 00:07:30 tom * cleanup whitespace; some random optimizations; standardize DEBUG options; * improve activemap detection: now finds overlapping prefix maps too; add * optional bit vector indexed by keystroke to sparsemaps for efficient access; * improve integration of sparsemaps with regular keymaps. * * Revision 14.28 1992/10/24 01:24:21 tom * integrate sparsemaps in regular keymap mechanism; * convert to "port{ansi,defs}.h" conventions. * * Revision 14.27 1992/09/22 15:23:11 tom * replace CTL('Q') with `QuoteChar'. * * Revision 14.26 1992/08/26 23:56:56 tom * add RCS directives. * */