This is map.c in view mode; [Download] [Up]
/* map.c */
/* Copyright 1995 by Steve Kirkendall */
char id_map[] = "$Id: map.c,v 2.26 1996/09/21 00:01:46 steve Exp $";
#include "elvis.h"
static void trace P_((char *where));
/* This structure is used to store maps and abbreviations. The distinction
* between them is that maps are stored in the list referenced by the "maps"
* pointer, while abbreviations are referenced by the "abbrs" pointer.
*/
typedef struct _map
{
struct _map *next; /* another mapping */
CHAR *label; /* label of the map/abbr, or NULL */
CHAR *rawin; /* the "rawin" characters */
CHAR *cooked;/* the "cooked" characters */
short rawlen; /* length of the "rawin" characters */
short cooklen;/* length of the "cooked" characters */
MAPFLAGS flags; /* various flags */
BOOLEAN invoked;/* has the map been used lately? */
} MAP;
static MAP *maps; /* the map table */
static MAP *abbrs; /* the abbreviation table */
/* This function adds a map to the map table, or replaces an existing map.
* New maps will be added to the end of the list of maps.
*
* "rawin" is the first argument to a ":map" command and "cooked" is the
* second. "rawlen" and "cooklen" are their lengths; the strings don't need
* to be NUL terminated. The "rawin" string may be either the actual characters
* to be mapped, or a key label which is supported by the GUI; this function
* will call the GUI's keylabel() function to perform the conversion. This
* function will allocate copies of both of these strings, so the calling
* function can discard its own copies as soon as this function returns.
*
*"label" is either NULL or a key label. The :map command always passes NULL,
* and the GUI's init will usually call this function with a label string. The
* label should be NUL-terminated, and its storage space cannot be discarded
* after the map because this function does *NOT* make a copy of the label.
*
* "flags" indicates when the map should be effective.
*/
void mapinsert(rawin, rawlen, cooked, cooklen, label, flags)
CHAR *rawin; /* characters sent by key */
int rawlen; /* length of rawin */
CHAR *cooked;/* characters that the key should appear to send */
int cooklen;/* length of cooked */
CHAR *label; /* label of the key */
MAPFLAGS flags; /* when the map should take effect */
{
MAP *scan, *lag;
MAP **head; /* points to either "maps" or "abbrs" */
int i;
MAPFLAGS parser;
/* Determine whether this will be a map or abbreviation */
head = (flags & MAP_ABBR) ? &abbrs : &maps;
/* If the cooked string starts with "visual " then the command should
* always be executed as VI commands. This also implies that the map
* should be defined for input mode as well.
*/
if (cooklen > 7 && !CHARncmp(cooked, toCHAR("visual "), 7))
{
flags |= (MAP_INPUT|MAP_ASCMD);
cooklen -= 7;
cooked += 7;
}
/* extract the parser bits from flags */
parser = (flags & (MAP_INPUT|MAP_COMMAND));
assert(parser);
/* If more that one parser was requested, then recursively map each
* one separately.
*/
if (parser != MAP_INPUT && parser != MAP_COMMAND)
{
flags &= ~(MAP_INPUT|MAP_COMMAND);
if (parser & MAP_INPUT)
mapinsert(rawin, rawlen, cooked, cooklen, label, flags|MAP_INPUT);
if (parser & MAP_COMMAND)
mapinsert(rawin, rawlen, cooked, cooklen, label, flags|MAP_COMMAND);
return;
}
/* In MAP_COMMAND mode, MAP_ASCMD is unnecessary */
if (parser & MAP_COMMAND)
{
flags &= ~MAP_ASCMD;
}
/* if no label was supplied, maybe we should try to find one? */
if (head == &maps && !label && gui->keylabel)
{
i = (*gui->keylabel)(rawin, rawlen, &label, &rawin);
if (i > 0)
{
rawlen = i;
}
}
/* see if this is already mapped */
for (lag = NULL, scan = *head;
scan &&
((scan->flags & (MAP_INPUT|MAP_COMMAND)) != parser ||
scan->rawlen != rawlen ||
memcmp(scan->rawin, rawin, rawlen * sizeof(CHAR)));
lag = scan, scan = scan->next)
{
}
/* if not mapped, then create a map */
if (!scan)
{
/* allocate & initialize a MAP struct */
scan = (MAP *)safekept(1, sizeof(MAP));
scan->label = label;
scan->rawin = (CHAR *)safekept(rawlen, sizeof(CHAR));
memcpy(scan->rawin, rawin, rawlen * sizeof(CHAR));
scan->rawlen = rawlen;
/* link it into the list of maps. */
if (lag)
{
lag->next = scan;
}
else
{
*head = scan;
}
}
else /* recycle an old map */
{
/* free the old cooked string */
safefree(scan->cooked);
}
/* save a copy of the new cooked string */
scan->cooked = safekept(cooklen, sizeof(CHAR));
memcpy(scan->cooked, cooked, cooklen * sizeof(CHAR));
scan->cooklen = cooklen;
scan->flags = flags;
}
/* This function deletes a map, or changes its break flag. It is used by the
* :unmap, :break, and :unbreak commands. The "rawin" string can be either
* the label or rawin string. Returns True if succesful, or False if the map
* couldn't be found.
*/
BOOLEAN mapdelete(rawin, rawlen, flags, del, brk)
CHAR *rawin; /* the key to be unmapped */
int rawlen; /* length of rawin */
MAPFLAGS flags; /* when the key is mapped now */
BOOLEAN del; /* delete the map? (else adjust break flag) */
BOOLEAN brk; /* what to set the break flag to */
{
MAP *scan, *lag;
MAP **head;
CHAR *label;
int i;
/* Determine whether this will be a map or abbreviation */
head = (flags & MAP_ABBR) ? &abbrs : &maps;
/* When unmapping, we only care about the keystroke parser bits */
flags &= (MAP_INPUT|MAP_COMMAND);
/* if no label was supplied, maybe we should try to find one? */
if (head == &maps && gui->keylabel)
{
i = (*gui->keylabel)(rawin, rawlen, &label, &rawin);
if (i > 0)
{
rawlen = i;
}
}
/* see if this is already mapped */
for (lag = NULL, scan = *head;
scan && (
(scan->flags & (MAP_INPUT|MAP_COMMAND)) != flags ||
scan->rawlen != rawlen ||
memcmp(scan->rawin, rawin, rawlen * sizeof(CHAR)));
lag = scan, scan = scan->next)
{
}
/* if not mapped, then fail */
if (!scan)
{
return False;
}
/* perform the action... */
if (del)
{
/* remove the map from the list of maps */
if (lag)
{
lag->next = scan->next;
}
else
{
*head = scan->next;
}
/* free the map */
safefree(scan->rawin);
safefree(scan->cooked);
safefree(scan);
}
else if (brk)
{
scan->flags |= MAP_BREAK;
}
else
{
scan->flags &= ~MAP_BREAK;
}
return True;
}
/* These two variables are used to store characters which have been read but
* not yet parsed, and maybe not even mapped.
*/
static CHAR queue[500]; /* the mapping queue */
static int qty = 0; /* number of keys in the queue */
static int resolved = 0; /* number of resolved keys (no mapping needed) */
static long learning; /* bitmap of "learn" buffers */
static CHAR traceimg[60]; /* image of queue, for maptrace option */
static BOOLEAN tracereal; /* any real keys since last trace? */
static BOOLEAN tracestep; /* don't queue next keystroke */
/* build the trace image, and show it */
static void trace(where)
char *where;
{
int i, j, end;
BUFFER log;
MARKBUF logend;
MARKBUF logstart;
CHAR ch[1];
/* if not tracing, then do nothing */
if (o_maptrace == 'o')
return;
/* reset tracereal */
tracereal = False;
/* Decide how much of the queue to show. */
for (end = qty; end >= 25; end -= 10)
{
}
/* generate the image */
for (i = j = 0; i < end; i++, j += 2)
{
if (iscntrl(queue[i]))
{
traceimg[j] = '^';
traceimg[j + 1] = ELVCTRL(queue[i]);
}
else
{
traceimg[j] = ' ';
traceimg[j + 1] = queue[i];
}
}
traceimg[j] = '\0';
/* show the image */
msg(MSG_STATUS, "[sSdd]$1:($2>>50)", where, traceimg, resolved, qty);
(void)eventdraw(windefault->gw);
guiflush();
/* maybe log it */
if (o_maplog != 'o')
{
log = bufalloc(toCHAR(TRACE_BUF), 0);
if (o_maplog == 'r')
{
bufreplace(marktmp(logstart, log, 0L), marktmp(logend, log, o_bufchars(log)), NULL, 0L);
o_maplog = 'a';
}
o_internal(log) = True;
bufreplace(marktmp(logend, log, o_bufchars(log)), &logend, toCHAR(where), (long)strlen(where));
ch[0] = ':';
bufreplace(marktmp(logend, log, o_bufchars(log)), &logend, ch, 1L);
bufreplace(marktmp(logend, log, o_bufchars(log)), &logend, traceimg, (long)CHARlen(traceimg));
ch[0] = '\n';
bufreplace(marktmp(logend, log, o_bufchars(log)), &logend, ch, 1L);
}
/* maybe arrange for single-stepping to occur on next keystroke */
if (o_maptrace == 's'
&& !(windefault && (windefault->state->mapflags & MAP_DISABLE)))
{
tracestep = True;
}
}
/* This function implements mapping. It is called with either 1 or more new
* characters from keypress events, or with 0 to indicate that a timeout
* occurred. It calls the current window's keystroke parser; we assume that
* the windefault variable is set correctly.
*/
MAPSTATE mapdo(keys, nkeys)
CHAR *keys; /* characters from the keyboard */
int nkeys; /* number of keys */
{
MAP *scan; /* used for scanning through maps */
int ambkey, ambuser;/* ambiguous key maps and user maps */
MAP *match; /* longest fully matching map */
BOOLEAN ascmd = False; /* did we just resolve an ASCMD map? */
BOOLEAN didtimeout; /* did we timeout? */
MAPFLAGS now; /* current keystroke parsing state */
BUFFER buf; /* a cut buffer that is in "learn" mode */
CHAR cbname; /* name of cut buffer */
MARKBUF mark; /* points to the end of buf */
int i, j;
assert(0 <= resolved && resolved <= qty);
assert(windefault);
/* if nkeys==0 then we timed out */
didtimeout = (BOOLEAN)(nkeys == 0);
/* tracing */
if (!tracereal && guipoll(False))
{
tracereal = True;
mapalert();
}
if (tracestep)
{
if (keys[0] == ELVCTRL('C'))
{
tracereal = True;
mapalert();
}
else if (keys[0] == 'r')
{
o_maptrace = 'r';
}
nkeys = 0;
tracestep = False;
}
if (nkeys > 0)
{
tracereal = True;
}
/* append the keys to any cutbuffer that is in "learn" mode */
if (learning)
{
for (cbname = 'a'; cbname <= 'z'; cbname++)
{
if (learning & (1 << (cbname & 0x1f)))
{
buf = cutbuffer(cbname, False);
if (buf)
{
bufreplace(marktmp(mark, buf,
o_bufchars(buf)), &mark, keys, (long)nkeys);
}
}
}
}
/* Add the new keys to the end of the queue, being careful
* to avoid overflow.
*/
while (qty < QTY(queue) && nkeys > 0)
{
queue[qty++] = *keys++;
nkeys--;
}
/* repeatedly apply maps and then parse resolved keys */
for (;;)
{
/* send any resolved keys to the current window's parser */
if (resolved > 0)
{
while (resolved > 0)
{
/* If there aren't any more windows, stop! */
if (!windows)
return MAP_CLEAR;
assert(windefault);
/* maybe show trace */
if (!tracereal) trace("cmd");
/* Delete the next keystroke from the queue */
resolved--;
qty--;
j = queue[0];
for (i = 0; i < qty; i++)
{
queue[i] = queue[i + 1];
}
/* If the key is supposed to be treated as a command,
* then send a ^O before the keystroke. This is a
* kludgy way to implement the input-mode ^O command.
*/
if (ascmd)
{
statekey(ELVCTRL('O'));
}
/* Make sure the MAP_DISABLE flag is turned off.
* Any character of the cooked string can force it
* on, but we only care if the *last* one forces
* it on. By forcing it off before handling each
* cooked keystroke, we can ignore all but the last.
*/
windefault->state->mapflags &= ~MAP_DISABLE;
/* Send the keystroke to the parser. */
statekey((_CHAR_)j);
/* if single-stepping, then we're done for now */
if (tracestep)
{
return MAP_CLEAR;
}
}
ascmd = False;
}
/* if all keys have been processed, then return MAP_CLEAR */
if (qty == 0)
{
assert(resolved == 0);
for (scan = maps; scan; scan = scan->next)
{
scan->invoked = False;
}
return MAP_CLEAR;
}
/* figure out what the current map context is */
if (windefault->state->mapflags & MAP_DISABLE)
{
now = 0;
windefault->state->mapflags &= ~MAP_DISABLE;
}
else
{
now = (windefault->state->mapflags & (MAP_INPUT|MAP_COMMAND|MAP_OPEN));
}
/* try to match the remaining keys to each map */
ambkey = ambuser = 0;
match = NULL;
if (now & (MAP_INPUT|MAP_COMMAND)) /* if mapping is allowed... */
{
for (scan = maps; scan; scan = scan->next)
{
/* ignore maps for a different context */
if ((scan->flags & now) != now)
{
continue;
}
/* is it an ambiguous (incomplete) match? */
if (!didtimeout
&& scan->rawlen > qty
&& !memcmp(scan->rawin, queue, qty * sizeof(CHAR)))
{
/* increment an ambiguous match counter */
if (scan->label)
ambkey++;
else
ambuser++;
}
/* is it a complete match, and either the first such or
* longer than any previous complete matches?
*/
if (scan->rawlen <= qty
&& !memcmp(scan->rawin, queue, scan->rawlen * sizeof(CHAR))
&& (!match || match->rawlen < scan->rawlen))
{
/* remember this match */
match = scan;
}
}
}
/* if ambiguous, then return MAP_USER or MAP_KEY */
if (ambuser > 0)
return MAP_USER;
else if (ambkey > 0)
return MAP_KEY;
/* if any complete map, then apply it */
if (match)
{
/* detect animation macros, so we can update the screen
* while they run. Note that we bypass this for key
* maps, since an autorepeated arrow key shouldn't make
* us update the screen for each line scrolled.
*/
if (!match->label && o_optimize)
{
if (!match->invoked)
{
match->invoked = True;
}
else
{
drawimage(windefault);
guiflush();
}
}
/* maybe show the the map queue before */
if ((match->flags & MAP_BREAK) && o_maptrace == 'r')
o_maptrace = 's';
trace("map");
/* shift the contents of the queue to allow for cooked
* strings that are of a different length than rawin.
*/
if (match->rawlen < match->cooklen)
{
/* insert some room */
for (i = qty + match->cooklen - match->rawlen, j = qty;
j > match->rawlen;
)
{
queue[--i] = queue[--j];
}
}
else if (match->rawlen > match->cooklen)
{
/* delete some keys */
for (i = match->cooklen, j = match->rawlen; i < qty; )
{
queue[i++] = queue[j++];
}
}
qty += match->cooklen - match->rawlen;
ascmd = (BOOLEAN)((match->flags & MAP_ASCMD) != 0);
/* copy the cooked string into the queue */
memcpy(queue, match->cooked, match->cooklen * sizeof(CHAR));
/* if the "remap" option is off, then the cooked chars
* have now been resolved.
*/
if (!o_remap)
{
resolved = match->cooklen;
}
/* if single-stepping, then we're done for now */
if (tracestep)
{
return MAP_CLEAR;
}
}
else /* no matches of any kind */
{
/* first char is resolved: not mapped */
resolved = 1;
}
}
/*NOTREACHED*/
}
/* This function attempts to place one or more characters back into the
* keyboard's typeahead queue. If this would cause the typeahead queue
* to overflow, then this function has no effect.
*/
void mapunget(keys, nkeys, remap)
CHAR *keys; /* keys to stuff into the type-ahead buffer */
int nkeys; /* number of keys */
BOOLEAN remap; /* are the ungotten keys subject to key maps? */
{
int i;
/* if this would cause overflow, then do nothing */
if (nkeys + qty > QTY(queue))
{
return;
}
/* maybe show trace */
trace("ung");
/* shift old characters to make room for new characters */
if (qty > 0)
{
for (i = qty; --i >= 0; )
{
queue[i + nkeys] = queue[i];
}
}
/* copy the new characters into the queue */
for (i = 0; i < nkeys; i++)
{
queue[i] = keys[i];
}
qty += nkeys;
/* Should the new characters be subjected to mapping? */
if (!remap)
{
/* no -- assume they're resolved */
resolved += nkeys;
}
else
{
/* yes -- assume they're unresolved, along with any following
* characters.
*/
resolved = 0;
}
}
/* This function is used for listing the contents of the map table in a
* human-readable format. Each call returns a single line of text in a
* static CHAR array, or NULL after the last line has been output. After
* calling this function once, you *MUST* call it repeatedly until it
* returns NULL. No other map functions should be called during this time.
*/
CHAR *maplist(flags, reflen)
MAPFLAGS flags; /* which maps to output */
int *reflen;/* where to store length, or NULL if don't care */
{
static MAP *m; /* used for scanning map list */
static CHAR buf[200];
CHAR *scan, *build;
int i;
/* find first/next map item */
m = (m ? m->next : (flags & MAP_ABBR) ? abbrs : maps);
flags &= ~MAP_ABBR;
while (m && (m->flags & flags) == 0)
{
m = m->next;
}
/* if no more items, return NULL */
if (!m)
{
return (CHAR *)0;
}
memset(buf, ' ', sizeof buf);
if (m->label)
{
i = CHARlen(m->label);
CHARncpy(buf, m->label, (size_t)(i>9 ? 9 : i));
}
for (scan = m->rawin, i = m->rawlen, build = &buf[10];
i > 0 && build < &buf[QTY(buf)-4];
i--, scan++)
{
if (*scan < ' ' || *scan == '\177')
{
*build++ = '^';
*build++ = ELVCTRL(*scan);
}
else
{
*build++ = *scan;
}
}
do
{
*build++ = ' ';
} while (build < &buf[20]);
if (m->flags & MAP_ASCMD)
{
CHARncpy(build, toCHAR("visual "), 7);
build += 7;
}
for (scan = m->cooked, i = m->cooklen;
i > 0 && build < &buf[QTY(buf)-3];
i--, scan++)
{
if (*scan < ' ' || *scan == '\177')
{
*build++ = '^';
*build++ = ELVCTRL(*scan);
}
else
{
*build++ = *scan;
}
}
*build++ = '\n';
*build = '\0';
/* return the line */
if (reflen)
{
*reflen = (long)(build - buf);
}
return buf;
}
/* This function causes future keystrokes to be stored in a cut buffer */
RESULT maplearn(cbname, start)
_CHAR_ cbname;
BOOLEAN start;
{
long bit;
MARKBUF tmp, end;
BUFFER buf;
CHAR cmd;
/* reject if not a letter */
if (!((cbname >= 'a' && cbname <= 'z')
|| (cbname >= 'A' && cbname <= 'Z')))
{
return RESULT_ERROR;
}
/* Set/reset the "learn" bit for the named cut buffer. Note that we
* return RESULT_COMPLETE if you stop recording keystrokes on a buffer
* that wasn't recording to begin with.
*/
bit = (1 << (cbname & 0x1f));
if (start)
learning |= bit;
else if (!(learning & bit))
return RESULT_COMPLETE;
else
learning ^= bit;
/* If we're starting and the cut buffer name is lowercase,
* then we need to reset the cut buffer to 0 characters
*/
if (start && islower(cbname))
{
/* reset the cut buffer to zero characters */
cutyank(cbname, marktmp(tmp, bufdefault, 0), &tmp, 'c', False);
}
/* If we're ending and the last two characters were "]a" or "@a" (or
* whatever the buffer name was) then delete the last two characters.
*/
if (!start)
{
buf = cutbuffer(cbname, False);
if (!buf)
return RESULT_ERROR;
if (o_bufchars(buf) >= CUT_TYPELEN + 2
&& scanchar(marktmp(tmp, buf, o_bufchars(buf) - 1)) == cbname
&& ((cmd = scanchar(marktmp(tmp, buf, o_bufchars(buf) - 2))) == ']'
|| cmd == '@'))
{
bufreplace(marktmp(tmp, buf, o_bufchars(buf) - 2),
marktmp(end, buf, o_bufchars(buf)), NULL, 0);
}
}
return RESULT_COMPLETE;
}
/* This function returns a character indicating the current learn state.
* This will be ',' if no cut buffers are in learn mode, or the name of
* the first buffer which is in learn mode.
*/
CHAR maplrnchar(dflt)
_CHAR_ dflt;
{
CHAR cbname;
if (!learning)
return dflt;
for (cbname = 'a'; (learning & (1 << (cbname & 0x1f))) == 0; cbname++)
{
assert(cbname < 'z');
}
return cbname;
}
/* This function implements a POSIX "terminal alert." This involves discarding
* any pending keytrokes, and aborting @ macros and maps. And then the GUI's
* bell must be rung.
*/
void mapalert()
{
/* maybe display log info */
if (!tracereal) trace(":::");
/* cancel all pending key states, etc. */
qty = resolved = learning = 0;
}
CHAR *mapabbr(bkwd, oldptr, newptr, exline)
CHAR *bkwd; /* possible abbreviation, BACKWARDS */
long *oldptr;/* where to store the length of short form */
long *newptr;/* where to store the length of long form */
BOOLEAN exline; /* inputting an ex command line? (else normal input) */
{
MAP *m; /* used for scanning the abbr list */
MAP *match; /* longest matching abbreviation */
int i, j;
/* compare against all abbreviations */
match = NULL;
for (m = abbrs; m; m = m->next)
{
/* Skip this abbr if it is for the wrong context */
if ((m->flags & MAP_INPUT) == (exline ? MAP_INPUT : 0))
{
continue;
}
/* Compare each character. This is a little tricky since the
* input word is backwards.
*/
for (i = 0, j = m->rawlen - 1;
j >= 0 && bkwd[i] == m->rawin[j]; i++, j--)
{
}
/* If all characters matched, and the preceding character in the
* raw text wasn't alphanumeric, then we have a match. If this
* match is longer than any previous match, then remember it.
*/
if (j < 0
&& !isalnum(bkwd[i])
&& (!match || match->rawlen < i))
{
match = m;
}
}
/* If we found a match, return it. */
if (match)
{
*oldptr = match->rawlen;
*newptr = match->cooklen;
return match->cooked;
}
return NULL;
}
#ifndef NO_MKEXRC
/* This function is used for saving the current map table as a series of
* :map commands. It is used by the :mkexrc command.
*/
void mapsave(buf)
BUFFER buf; /* the buffer to append to */
{
MARKBUF append; /* where to put the command */
static MAP *m; /* used for scanning map list */
static CHAR text[200];
long len;
CHAR *scan;
int i;
(void)marktmp(append, buf, o_bufchars(buf));
/* for each map... */
for (m = maps; m; m = m->next)
{
/* if for a GUI-specific key, ignore it */
if (m->label && *m->label != '#')
{
continue;
}
/* construct a "map" command */
CHARcpy(text, toCHAR("map"));
len = 3;
if ((m->flags & MAP_INPUT) != 0)
{
text[len++] = '!';
}
text[len++] = ' ';
/* Append the raw code. Use function label if possible */
if (m->label)
{
CHARcpy(&text[len], m->label);
len += CHARlen(m->label);
}
else
{
for (scan = m->rawin, i = m->rawlen;
i > 0 && len < QTY(text) - 4;
i--, scan++)
{
if (*scan == ' ' || *scan == '\t' || *scan == '|')
{
text[len++] = ELVCTRL('V');
}
text[len++] = *scan;
}
}
text[len++] = ' ';
/* construct the "cooked" string */
if (m->flags & MAP_ASCMD)
{
CHARncpy(&text[len], toCHAR("visual "), 7);
len += 7;
}
for (scan = m->cooked, i = m->cooklen;
i > 0 && len < QTY(text) - 3;
i--, scan++)
{
if (*scan == '|')
{
text[len++] = ELVCTRL('V');
}
text[len++] = *scan;
}
text[len++] = '\n';
/* append the command to the buffer */
bufreplace(&append, &append, text, len);
markaddoffset(&append, len);
}
}
#endif /* not NO_MKEXRC */
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.