1071 lines
22 KiB
C
1071 lines
22 KiB
C
/* tio.c */
|
|
|
|
/* Author:
|
|
* Steve Kirkendall
|
|
* 14407 SW Teal Blvd. #C
|
|
* Beaverton, OR 97005
|
|
* kirkenda@cs.pdx.edu
|
|
*/
|
|
|
|
|
|
/* This file contains terminal I/O functions */
|
|
|
|
#include "config.h"
|
|
#include "vi.h"
|
|
#include "ctype.h"
|
|
|
|
static int showmsg P_((void));
|
|
|
|
/* This function reads in a line from the terminal. */
|
|
int vgets(prompt, buf, bsize)
|
|
int prompt; /* the prompt character, or '\0' for none */
|
|
char *buf; /* buffer into which the string is read */
|
|
int bsize; /* size of the buffer */
|
|
{
|
|
int len; /* how much we've read so far */
|
|
int ch; /* a character from the user */
|
|
int quoted; /* is the next char quoted? */
|
|
int tab; /* column position of cursor */
|
|
char widths[132]; /* widths of characters */
|
|
int word; /* index of first letter of word */
|
|
#ifndef NO_DIGRAPH
|
|
int erased; /* 0, or first char of a digraph */
|
|
#endif
|
|
|
|
/* show the prompt */
|
|
move(LINES - 1, 0);
|
|
tab = 0;
|
|
if (prompt)
|
|
{
|
|
addch(prompt);
|
|
tab = 1;
|
|
}
|
|
clrtoeol();
|
|
refresh();
|
|
|
|
/* read in the line */
|
|
#ifndef NO_DIGRAPH
|
|
erased =
|
|
#endif
|
|
quoted = len = 0;
|
|
for (;;)
|
|
{
|
|
#ifndef NO_ABBR
|
|
if (quoted || mode == MODE_EX)
|
|
{
|
|
ch = getkey(0);
|
|
}
|
|
else
|
|
{
|
|
/* maybe expand an abbreviation while getting key */
|
|
for (word = len; --word >= 0 && !isspace(buf[word]); )
|
|
{
|
|
}
|
|
word++;
|
|
ch = getabkey(WHEN_EX, &buf[word], len - word);
|
|
}
|
|
#else
|
|
ch = getkey(0);
|
|
#endif
|
|
#ifndef NO_EXTENSIONS
|
|
if (ch == ctrl('O'))
|
|
{
|
|
ch = getkey(quoted ? 0 : WHEN_EX);
|
|
}
|
|
#endif
|
|
|
|
/* some special conversions */
|
|
#if 0
|
|
if (ch == ctrl('D') && len == 0)
|
|
ch = ctrl('[');
|
|
#endif
|
|
#ifndef NO_DIGRAPH
|
|
if (*o_digraph && erased != 0 && ch != '\b')
|
|
{
|
|
ch = digraph(erased, ch);
|
|
erased = 0;
|
|
}
|
|
#endif
|
|
|
|
/* inhibit detection of special chars (except ^J) after a ^V */
|
|
if (quoted && ch != '\n')
|
|
{
|
|
ch |= 256;
|
|
}
|
|
|
|
/* process the character */
|
|
switch(ch)
|
|
{
|
|
case ctrl('V'):
|
|
qaddch('^');
|
|
qaddch('\b');
|
|
quoted = TRUE;
|
|
break;
|
|
|
|
case ctrl('D'):
|
|
return -1;
|
|
|
|
case ctrl('['):
|
|
case '\n':
|
|
#if OSK
|
|
case '\l':
|
|
#else
|
|
case '\r':
|
|
#endif
|
|
clrtoeol();
|
|
goto BreakBreak;
|
|
|
|
#ifndef CRUNCH
|
|
case ctrl('U'):
|
|
while (len > 0)
|
|
{
|
|
len--;
|
|
while (widths[len]-- > 0)
|
|
{
|
|
qaddch('\b');
|
|
qaddch(' ');
|
|
qaddch('\b');
|
|
}
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
case '\b':
|
|
if (len > 0)
|
|
{
|
|
len--;
|
|
#ifndef NO_DIGRAPH
|
|
erased = buf[len];
|
|
#endif
|
|
for (ch = widths[len]; ch > 0; ch--)
|
|
addch('\b');
|
|
if (mode == MODE_EX)
|
|
{
|
|
clrtoeol();
|
|
}
|
|
tab -= widths[len];
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* strip off quotation bit */
|
|
if (ch & 256)
|
|
{
|
|
ch &= ~256;
|
|
qaddch(' ');
|
|
qaddch('\b');
|
|
}
|
|
|
|
/* add & echo the char */
|
|
if (len < bsize - 1)
|
|
{
|
|
if (ch == '\t' && !quoted)
|
|
{
|
|
widths[len] = *o_tabstop - (tab % *o_tabstop);
|
|
addstr(" " + 8 - widths[len]);
|
|
tab += widths[len];
|
|
}
|
|
else if (ch > 0 && ch < ' ') /* > 0 by GB */
|
|
{
|
|
addch('^');
|
|
addch(ch + '@');
|
|
widths[len] = 2;
|
|
tab += 2;
|
|
}
|
|
else if (ch == '\177')
|
|
{
|
|
addch('^');
|
|
addch('?');
|
|
widths[len] = 2;
|
|
tab += 2;
|
|
}
|
|
else
|
|
{
|
|
addch(ch);
|
|
widths[len] = 1;
|
|
tab++;
|
|
}
|
|
buf[len++] = ch;
|
|
}
|
|
else
|
|
{
|
|
beep();
|
|
}
|
|
quoted = FALSE;
|
|
}
|
|
}
|
|
BreakBreak:
|
|
refresh();
|
|
buf[len] = '\0';
|
|
return len;
|
|
}
|
|
|
|
|
|
static int manymsgs; /* This variable keeps msgs from overwriting each other */
|
|
static char pmsg[80]; /* previous message (waiting to be displayed) */
|
|
|
|
|
|
static int showmsg()
|
|
{
|
|
/* if there is no message to show, then don't */
|
|
if (!manymsgs)
|
|
return FALSE;
|
|
|
|
/* display the message */
|
|
move(LINES - 1, 0);
|
|
if (*pmsg)
|
|
{
|
|
standout();
|
|
qaddch(' ');
|
|
qaddstr(pmsg);
|
|
qaddch(' ');
|
|
standend();
|
|
}
|
|
clrtoeol();
|
|
|
|
manymsgs = FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
void endmsgs()
|
|
{
|
|
if (manymsgs)
|
|
{
|
|
showmsg();
|
|
addch('\n');
|
|
}
|
|
}
|
|
|
|
/* Write a message in an appropriate way. This should really be a varargs
|
|
* function, but there is no such thing as vwprintw. Hack!!!
|
|
*
|
|
* In MODE_EX or MODE_COLON, the message is written immediately, with a
|
|
* newline at the end.
|
|
*
|
|
* In MODE_VI, the message is stored in a character buffer. It is not
|
|
* displayed until getkey() is called. msg() will call getkey() itself,
|
|
* if necessary, to prevent messages from being lost.
|
|
*
|
|
* msg("") - clears the message line
|
|
* msg("%s %d", ...) - does a printf onto the message line
|
|
*/
|
|
#ifdef __STDC__
|
|
void msg (char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start (ap, fmt);
|
|
#else
|
|
void msg(fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7)
|
|
char *fmt;
|
|
long arg1, arg2, arg3, arg4, arg5, arg6, arg7;
|
|
{
|
|
#endif
|
|
if (mode != MODE_VI)
|
|
{
|
|
#ifdef __STDC__
|
|
vsprintf (pmsg, fmt, ap);
|
|
#else
|
|
sprintf(pmsg, fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
|
|
#endif
|
|
qaddstr(pmsg);
|
|
addch('\n');
|
|
exrefresh();
|
|
}
|
|
else
|
|
{
|
|
/* wait for keypress between consecutive msgs */
|
|
if (manymsgs)
|
|
{
|
|
getkey(WHEN_MSG);
|
|
}
|
|
|
|
/* real message */
|
|
#ifdef __STDC__
|
|
vsprintf (pmsg, fmt, ap);
|
|
#else
|
|
sprintf(pmsg, fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
|
|
#endif
|
|
if (*fmt)
|
|
{
|
|
manymsgs = TRUE;
|
|
}
|
|
}
|
|
#ifdef __STDC__
|
|
va_end (ap);
|
|
#endif
|
|
}
|
|
|
|
|
|
/* This function calls refresh() if the option exrefresh is set */
|
|
void exrefresh()
|
|
{
|
|
char *scan;
|
|
|
|
/* If this ex command wrote ANYTHING set exwrote so vi's : command
|
|
* can tell that it must wait for a user keystroke before redrawing.
|
|
*/
|
|
for (scan=kbuf; scan<stdscr; scan++)
|
|
if (*scan == '\n')
|
|
exwrote = TRUE;
|
|
|
|
/* now we do the refresh thing */
|
|
if (*o_exrefresh)
|
|
{
|
|
refresh();
|
|
}
|
|
else
|
|
{
|
|
wqrefresh();
|
|
}
|
|
if (mode != MODE_VI)
|
|
{
|
|
manymsgs = FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
/* 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 abbreviation */
|
|
short len; /* length of the "rawin" characters */
|
|
short flags; /* various flags */
|
|
char *label; /* label of the map/abbr, or NULL */
|
|
char *rawin; /* the "rawin" characters */
|
|
char *cooked;/* the "cooked" characters */
|
|
} MAP;
|
|
|
|
static char keybuf[KEYBUFSIZE];
|
|
static int cend; /* end of input characters */
|
|
static int user; /* from user through end are chars typed by user */
|
|
static int next; /* index of the next character to be returned */
|
|
static MAP *match; /* the matching map, found by countmatch() */
|
|
static MAP *maps; /* the map table */
|
|
#ifndef NO_ABBR
|
|
static MAP *abbrs; /* the abbreviation table */
|
|
#endif
|
|
|
|
|
|
|
|
/* ring the terminal's bell */
|
|
void beep()
|
|
{
|
|
/* do a visible/audible bell */
|
|
if (*o_flash)
|
|
{
|
|
do_VB();
|
|
refresh();
|
|
}
|
|
else if (*o_errorbells)
|
|
{
|
|
tputs("\007", 1, faddch);
|
|
}
|
|
|
|
/* discard any buffered input, and abort macros */
|
|
next = user = cend;
|
|
}
|
|
|
|
|
|
|
|
/* This function replaces a "rawin" character sequence with the "cooked" version,
|
|
* by modifying the internal type-ahead buffer.
|
|
*/
|
|
void execmap(rawlen, cookedstr, visual)
|
|
int rawlen; /* length of rawin text -- string to delete */
|
|
char *cookedstr; /* the cooked text -- string to insert */
|
|
int visual; /* boolean -- chars to be executed in visual mode? */
|
|
{
|
|
int cookedlen;
|
|
char *src, *dst;
|
|
int i;
|
|
|
|
/* find the length of the cooked string */
|
|
cookedlen = strlen(cookedstr);
|
|
#ifndef NO_EXTENSIONS
|
|
if (visual)
|
|
{
|
|
cookedlen *= 2;
|
|
}
|
|
#endif
|
|
|
|
/* if too big to fit in type-ahead buffer, then don't do it */
|
|
if (cookedlen + (cend - next) - rawlen > KEYBUFSIZE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* shift to make room for cookedstr at the front of keybuf */
|
|
src = &keybuf[next + rawlen];
|
|
dst = &keybuf[cookedlen];
|
|
i = cend - (next + rawlen);
|
|
if (src >= dst)
|
|
{
|
|
while (i-- > 0)
|
|
{
|
|
*dst++ = *src++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
src += i;
|
|
dst += i;
|
|
while (i-- > 0)
|
|
{
|
|
*--dst = *--src;
|
|
}
|
|
}
|
|
|
|
/* insert cookedstr, and adjust offsets */
|
|
cend += cookedlen - rawlen - next;
|
|
user += cookedlen - rawlen - next;
|
|
next = 0;
|
|
for (dst = keybuf, src = cookedstr; *src; )
|
|
{
|
|
#ifndef NO_EXTENSIONS
|
|
if (visual)
|
|
{
|
|
*dst++ = ctrl('O');
|
|
cookedlen--;
|
|
}
|
|
#endif
|
|
*dst++ = *src++;
|
|
}
|
|
|
|
#ifdef DEBUG2
|
|
{
|
|
#include <stdio.h>
|
|
FILE *debout;
|
|
int i;
|
|
|
|
debout = fopen("debug.out", "a");
|
|
fprintf(debout, "After execmap(%d, \"%s\", %d)...\n", rawlen, cookedstr, visual);
|
|
for (i = 0; i < cend; i++)
|
|
{
|
|
if (i == next) fprintf(debout, "(next)");
|
|
if (i == user) fprintf(debout, "(user)");
|
|
if (UCHAR(keybuf[i]) < ' ')
|
|
fprintf(debout, "^%c", keybuf[i] ^ '@');
|
|
else
|
|
fprintf(debout, "%c", keybuf[i]);
|
|
}
|
|
fprintf(debout, "(end)\n");
|
|
fclose(debout);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifndef NO_CURSORSHAPE
|
|
/* made global so that suspend_curses() can reset it. -nox */
|
|
int oldcurs;
|
|
#endif
|
|
/* This function calls ttyread(). If necessary, it will also redraw the screen,
|
|
* change the cursor shape, display the mode, and update the ruler. If the
|
|
* number of characters read is 0, and we didn't time-out, then it exits because
|
|
* we've apparently reached the end of an EX script.
|
|
*/
|
|
static int fillkeybuf(when, timeout)
|
|
int when; /* mixture of WHEN_XXX flags */
|
|
int timeout;/* timeout in 1/10 second increments, or 0 */
|
|
{
|
|
int nkeys;
|
|
#ifndef NO_SHOWMODE
|
|
static int oldwhen; /* "when" from last time */
|
|
static int oldleft;
|
|
static long oldtop;
|
|
static long oldnlines;
|
|
char *str;
|
|
#endif
|
|
|
|
#ifdef DEBUG
|
|
watch();
|
|
#endif
|
|
|
|
|
|
#ifndef NO_CURSORSHAPE
|
|
/* make sure the cursor is the right shape */
|
|
if (has_CQ)
|
|
{
|
|
if (when != oldcurs)
|
|
{
|
|
switch (when)
|
|
{
|
|
case WHEN_EX: do_CX(); break;
|
|
case WHEN_VICMD: do_CV(); break;
|
|
case WHEN_VIINP: do_CI(); break;
|
|
case WHEN_VIREP: do_CR(); break;
|
|
}
|
|
oldcurs = when;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifndef NO_SHOWMODE
|
|
/* if "showmode" then say which mode we're in */
|
|
if (*o_smd && (when & WHENMASK))
|
|
{
|
|
/* redraw the screen before we check to see whether the
|
|
* "showmode" message needs to be redrawn.
|
|
*/
|
|
redraw(cursor, !(when & WHEN_VICMD));
|
|
|
|
/* now the "topline" test should be valid */
|
|
if (when != oldwhen
|
|
|| topline != oldtop
|
|
|| leftcol != oldleft
|
|
|| nlines != oldnlines)
|
|
{
|
|
oldwhen = when;
|
|
oldtop = topline;
|
|
oldleft = leftcol;
|
|
oldnlines = nlines;
|
|
|
|
if (when & WHEN_VICMD) str = "Command";
|
|
else if (when & WHEN_VIINP) str = " Input ";
|
|
else if (when & WHEN_VIREP) str = "Replace";
|
|
else if (when & WHEN_REP1) str = "Replc 1";
|
|
else if (when & WHEN_CUT) str = "Buffer ";
|
|
else if (when & WHEN_MARK) str = " Mark ";
|
|
else if (when & WHEN_CHAR) str = "Dest Ch";
|
|
else str = (char *)0;
|
|
|
|
if (str)
|
|
{
|
|
move(LINES - 1, COLS - 10);
|
|
standout();
|
|
qaddstr(str);
|
|
standend();
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifndef NO_EXTENSIONS
|
|
/* maybe display the ruler */
|
|
if (*o_ruler && (when & (WHEN_VICMD|WHEN_VIINP|WHEN_VIREP)))
|
|
{
|
|
char buf[20];
|
|
|
|
redraw(cursor, !(when & WHEN_VICMD));
|
|
pfetch(markline(cursor));
|
|
sprintf(buf, "%7ld,%-4d", markline(cursor), 1 + idx2col(cursor, ptext, when & (WHEN_VIINP|WHEN_VIREP)));
|
|
move(LINES - 1, COLS - 22);
|
|
addstr(buf);
|
|
}
|
|
#endif
|
|
|
|
/* redraw, so the cursor is in the right place */
|
|
if (when & WHENMASK)
|
|
{
|
|
redraw(cursor, !(when & (WHENMASK & ~(WHEN_VIREP|WHEN_VIINP))));
|
|
}
|
|
|
|
/* Okay, now we can finally read the rawin keystrokes */
|
|
refresh();
|
|
nkeys = ttyread(keybuf + cend, sizeof keybuf - cend, timeout);
|
|
|
|
/* if nkeys == 0 then we've reached EOF of an ex script. */
|
|
if (nkeys == 0 && timeout == 0)
|
|
{
|
|
tmpabort(TRUE);
|
|
move(LINES - 1, 0);
|
|
clrtoeol();
|
|
refresh();
|
|
endwin();
|
|
exit(exitcode);
|
|
}
|
|
|
|
cend += nkeys;
|
|
user += nkeys;
|
|
return nkeys;
|
|
}
|
|
|
|
|
|
/* This function counts the number of maps that could match the characters
|
|
* between &keybuf[next] and &keybuf[cend], including incomplete matches.
|
|
* The longest comlete match is remembered via the "match" variable.
|
|
*/
|
|
static int countmatch(when)
|
|
int when; /* mixture of WHEN_XXX flags */
|
|
{
|
|
MAP *map;
|
|
int count;
|
|
|
|
/* clear the "match" variable */
|
|
match = (MAP *)0;
|
|
|
|
/* check every map */
|
|
for (count = 0, map = maps; map; map = map->next)
|
|
{
|
|
/* can't match if wrong mode */
|
|
if ((map->flags & when) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/* would this be a complete match? */
|
|
if (map->len <= cend - next)
|
|
{
|
|
/* Yes, it would be. Now does it really match? */
|
|
if (!strncmp(map->rawin, &keybuf[next], map->len))
|
|
{
|
|
count++;
|
|
|
|
/* if this is the longest complete match,
|
|
* then remember it.
|
|
*/
|
|
if (!match || match->len < map->len)
|
|
{
|
|
match = map;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* No, it wouldn't. But check for partial match */
|
|
if (!strncmp(map->rawin, &keybuf[next], cend - next))
|
|
{
|
|
/* increment by 2 instead of 1 so that, in the
|
|
* event that we have a partial match with a
|
|
* single map, we don't mistakenly assume we
|
|
* have resolved the map yet.
|
|
*/
|
|
count += 2;
|
|
}
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
|
|
#ifndef NO_ABBR
|
|
/* This function checks to see whether a word is an abbreviation. If it is,
|
|
* then an appropriate number of backspoace characters is inserted into the
|
|
* type-ahead buffer, followed by the expanded form of the abbreviation.
|
|
*/
|
|
static void expandabbr(word, wlen)
|
|
char *word;
|
|
int wlen;
|
|
{
|
|
MAP *abbr;
|
|
|
|
/* if the next character wouldn't end the word, then don't expand */
|
|
if (isalnum(keybuf[next]) || keybuf[next] == ctrl('V') || keybuf[next] == '\b')
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* find the abbreviation, if any */
|
|
for (abbr = abbrs;
|
|
abbr && (abbr->len != wlen || strncmp(abbr->rawin, word, wlen));
|
|
abbr = abbr->next)
|
|
{
|
|
}
|
|
|
|
/* If an abbreviation was found, then expand it by inserting the long
|
|
* version into the type-ahead buffer, and then inserting (in front of
|
|
* the long version) enough backspaces to erase to the short version.
|
|
*/
|
|
if (abbr)
|
|
{
|
|
execmap(0, abbr->cooked, FALSE);
|
|
while (wlen > 15)
|
|
{
|
|
execmap(0, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b", FALSE);
|
|
wlen -= 15;
|
|
}
|
|
if (wlen > 0)
|
|
{
|
|
execmap(0, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" + 15 - wlen, FALSE);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
/* This function calls getabkey() without attempting to expand abbreviations */
|
|
int getkey(when)
|
|
int when; /* mixture of WHEN_XXX flags */
|
|
{
|
|
return getabkey(when, "", 0);
|
|
}
|
|
|
|
|
|
/* This is it. This function returns keystrokes one-at-a-time, after mapping
|
|
* and abbreviations have been taken into account.
|
|
*/
|
|
int getabkey(when, word, wlen)
|
|
int when; /* mixture of WHEN_XXX flags */
|
|
char *word; /* a word that may need to be expanded as an abbr */
|
|
int wlen; /* length of "word" -- since "word" might not have \0 */
|
|
{
|
|
int matches;
|
|
|
|
/* if this key is needed for delay between multiple error messages,
|
|
* then reset the manymsgs flag and abort any mapped key sequence.
|
|
*/
|
|
if (showmsg())
|
|
{
|
|
if (when == WHEN_MSG)
|
|
{
|
|
#ifndef CRUNCH
|
|
if (!*o_more)
|
|
{
|
|
refresh();
|
|
return ' ';
|
|
}
|
|
#endif
|
|
qaddstr("[More...]");
|
|
refresh();
|
|
execmap(user, "", FALSE);
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
/* periodically check for screwed up internal tables */
|
|
watch();
|
|
#endif
|
|
|
|
/* if buffer empty, read some characters without timeout */
|
|
if (next >= cend)
|
|
{
|
|
next = user = cend = 0;
|
|
fillkeybuf(when, 0);
|
|
}
|
|
|
|
/* try to map the key, unless already mapped and not ":set noremap" */
|
|
if (next <= user || *o_remap)
|
|
{
|
|
do
|
|
{
|
|
do
|
|
{
|
|
matches = countmatch(when);
|
|
} while (matches > 1 && fillkeybuf(when, *o_keytime) > 0);
|
|
if (matches == 1)
|
|
{
|
|
execmap(match->len, match->cooked,
|
|
(match->flags & WHEN_INMV) != 0
|
|
&& (when & (WHEN_VIINP|WHEN_VIREP)) != 0);
|
|
}
|
|
} while (*o_remap && matches == 1);
|
|
}
|
|
|
|
/* ERASEKEY should always be mapped to '\b'. */
|
|
if (keybuf[next] == ERASEKEY)
|
|
{
|
|
keybuf[next] = '\b';
|
|
}
|
|
|
|
#ifndef NO_ABBR
|
|
/* try to expand an abbreviation, except in visual command mode */
|
|
if (wlen > 0 && (mode & (WHEN_EX|WHEN_VIINP|WHEN_VIREP)) != 0)
|
|
{
|
|
expandabbr(word, wlen);
|
|
}
|
|
#endif
|
|
|
|
/* return the next key */
|
|
return keybuf[next++];
|
|
}
|
|
|
|
/* This function maps or unmaps a key */
|
|
void mapkey(rawin, cooked, when, name)
|
|
char *rawin; /* the input key sequence, before mapping */
|
|
char *cooked;/* after mapping -- or NULL to remove map */
|
|
int when; /* bitmap of when mapping should happen */
|
|
char *name; /* name of the key, NULL for no name, "abbr" for abbr */
|
|
{
|
|
MAP **head; /* head of list of maps or abbreviations */
|
|
MAP *scan; /* used for scanning through the list */
|
|
MAP *prev; /* used during deletions */
|
|
|
|
/* Is this a map or an abbreviation? Choose the right list. */
|
|
#ifndef NO_ABBR
|
|
head = ((!name || strcmp(name, "abbr")) ? &maps : &abbrs);
|
|
#else
|
|
head = &maps;
|
|
#endif
|
|
|
|
/* try to find the map in the list */
|
|
for (scan = *head, prev = (MAP *)0;
|
|
scan && (strcmp(rawin, scan->rawin) && strcmp(rawin, scan->cooked) ||
|
|
!(scan->flags & when & (WHEN_EX|WHEN_VICMD|WHEN_VIINP|WHEN_VIREP)));
|
|
prev = scan, scan = scan->next)
|
|
{
|
|
}
|
|
|
|
/* trying to map? (not unmap) */
|
|
if (cooked && *cooked)
|
|
{
|
|
/* if map starts with "visual ", then mark it as a visual map */
|
|
if (head == &maps && !strncmp(cooked, "visual ", 7))
|
|
{
|
|
cooked += 7;
|
|
when |= WHEN_INMV;
|
|
}
|
|
|
|
/* "visual" maps always work in input mode */
|
|
if (when & WHEN_INMV)
|
|
{
|
|
when |= WHEN_VIINP|WHEN_VIREP|WHEN_POPUP;
|
|
}
|
|
|
|
/* if not already in the list, then allocate a new structure */
|
|
if (!scan)
|
|
{
|
|
scan = (MAP *)malloc(sizeof(MAP));
|
|
scan->len = strlen(rawin);
|
|
scan->rawin = malloc((unsigned)(scan->len + 1));
|
|
strcpy(scan->rawin, rawin);
|
|
scan->flags = when;
|
|
scan->label = name;
|
|
if (*head)
|
|
{
|
|
prev->next = scan;
|
|
}
|
|
else
|
|
{
|
|
*head = scan;
|
|
}
|
|
scan->next = (MAP *)0;
|
|
}
|
|
else /* recycle old structure */
|
|
{
|
|
_free_(scan->cooked);
|
|
}
|
|
scan->cooked = malloc((unsigned)(strlen(cooked) + 1));
|
|
strcpy(scan->cooked, cooked);
|
|
}
|
|
else /* unmapping */
|
|
{
|
|
/* if nothing to unmap, then exit silently */
|
|
if (!scan)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* unlink the structure from the list */
|
|
if (prev)
|
|
{
|
|
prev->next = scan->next;
|
|
}
|
|
else
|
|
{
|
|
*head = scan->next;
|
|
}
|
|
|
|
/* free it, and the strings that it refers to */
|
|
_free_(scan->rawin);
|
|
_free_(scan->cooked);
|
|
_free_(scan);
|
|
}
|
|
}
|
|
|
|
|
|
/* This function returns a printable version of a string. It uses tmpblk.c */
|
|
char *printable(str)
|
|
char *str; /* the string to convert */
|
|
{
|
|
char *build; /* used for building the string */
|
|
|
|
for (build = tmpblk.c; *str; str++)
|
|
{
|
|
#if AMIGA
|
|
if (*str == '\233')
|
|
{
|
|
*build++ = '<';
|
|
*build++ = 'C';
|
|
*build++ = 'S';
|
|
*build++ = 'I';
|
|
*build++ = '>';
|
|
} else
|
|
#endif
|
|
if (UCHAR(*str) < ' ' || *str == '\177')
|
|
{
|
|
*build++ = '^';
|
|
*build++ = *str ^ '@';
|
|
}
|
|
else
|
|
{
|
|
*build++ = *str;
|
|
}
|
|
}
|
|
*build = '\0';
|
|
return tmpblk.c;
|
|
}
|
|
|
|
/* This function displays the contents of either the map table or the
|
|
* abbreviation table. User commands call this function as follows:
|
|
* :map dumpkey(WHEN_VICMD, FALSE);
|
|
* :map! dumpkey(WHEN_VIREP|WHEN_VIINP, FALSE);
|
|
* :abbr dumpkey(WHEN_VIINP|WHEN_VIREP, TRUE);
|
|
* :abbr! dumpkey(WHEN_EX|WHEN_VIINP|WHEN_VIREP, TRUE);
|
|
*/
|
|
void dumpkey(when, abbr)
|
|
int when; /* WHEN_XXXX of mappings to be dumped */
|
|
int abbr; /* boolean: dump abbreviations instead of maps? */
|
|
{
|
|
MAP *scan;
|
|
char *str;
|
|
int len;
|
|
|
|
#ifndef NO_ABBR
|
|
for (scan = (abbr ? abbrs : maps); scan; scan = scan->next)
|
|
#else
|
|
for (scan = maps; scan; scan = scan->next)
|
|
#endif
|
|
{
|
|
/* skip entries that don't match "when" */
|
|
if ((scan->flags & when) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/* dump the key label, if any */
|
|
if (!abbr)
|
|
{
|
|
len = 8;
|
|
if (scan->label)
|
|
{
|
|
qaddstr(scan->label);
|
|
len -= strlen(scan->label);
|
|
}
|
|
do
|
|
{
|
|
qaddch(' ');
|
|
} while (len-- > 0);
|
|
}
|
|
|
|
/* dump the rawin version */
|
|
str = printable(scan->rawin);
|
|
qaddstr(str);
|
|
len = strlen(str);
|
|
do
|
|
{
|
|
qaddch(' ');
|
|
} while (len++ < 8);
|
|
|
|
/* dump the mapped version */
|
|
#ifndef NO_EXTENSIONS
|
|
if ((scan->flags & WHEN_INMV) && (when & (WHEN_VIINP|WHEN_VIREP)))
|
|
{
|
|
qaddstr("visual ");
|
|
}
|
|
#endif
|
|
str = printable(scan->cooked);
|
|
qaddstr(str);
|
|
addch('\n');
|
|
exrefresh();
|
|
}
|
|
}
|
|
|
|
#ifndef NO_MKEXRC
|
|
|
|
static void safequote(str)
|
|
char *str;
|
|
{
|
|
char *build;
|
|
|
|
build = tmpblk.c + strlen(tmpblk.c);
|
|
while (*str)
|
|
{
|
|
if (*str <= ' ' && *str >= 1 || *str == '|')
|
|
{
|
|
*build++ = ctrl('V');
|
|
}
|
|
*build++ = *str++;
|
|
}
|
|
*build = '\0';
|
|
}
|
|
|
|
/* This function saves the contents of either the map table or the
|
|
* abbreviation table into a file. Both the "bang" and "no bang" versions
|
|
* are saved.
|
|
* :map dumpkey(WHEN_VICMD, FALSE);
|
|
* :map! dumpkey(WHEN_VIREP|WHEN_VIINP, FALSE);
|
|
* :abbr dumpkey(WHEN_VIINP|WHEN_VIREP, TRUE);
|
|
* :abbr! dumpkey(WHEN_EX|WHEN_VIINP|WHEN_VIREP, TRUE);
|
|
*/
|
|
void
|
|
savemaps(fd, abbr)
|
|
int fd; /* file descriptor of an open file to write to */
|
|
int abbr; /* boolean: do abbr table? (else do map table) */
|
|
{
|
|
MAP *scan;
|
|
int bang;
|
|
int when;
|
|
|
|
# ifndef NO_ABBR
|
|
for (scan = (abbr ? abbrs : maps); scan; scan = scan->next)
|
|
# else
|
|
for (scan = maps; scan; scan = scan->next)
|
|
# endif
|
|
{
|
|
/* skip maps that have labels, except for function keys */
|
|
if (scan->label && *scan->label != '#')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (bang = 0; bang < 2; bang++)
|
|
{
|
|
/* decide which "when" flags we want */
|
|
# ifndef NO_ABBR
|
|
if (abbr)
|
|
when = (bang ? WHEN_EX|WHEN_VIINP|WHEN_VIREP : WHEN_VIINP|WHEN_VIREP);
|
|
else
|
|
# endif
|
|
when = (bang ? WHEN_VIREP|WHEN_VIINP : WHEN_VICMD);
|
|
|
|
/* skip entries that don't match "when" */
|
|
if ((scan->flags & when) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/* write a "map" or "abbr" command name */
|
|
# ifndef NO_ABBR
|
|
if (abbr)
|
|
strcpy(tmpblk.c, "abbr");
|
|
else
|
|
# endif
|
|
strcpy(tmpblk.c, "map");
|
|
|
|
/* maybe write a bang. Definitely write a space */
|
|
if (bang)
|
|
strcat(tmpblk.c, "! ");
|
|
else
|
|
strcat(tmpblk.c, " ");
|
|
|
|
/* write the rawin version */
|
|
# ifndef NO_FKEY
|
|
if (scan->label)
|
|
strcat(tmpblk.c, scan->label);
|
|
else
|
|
# endif
|
|
safequote(scan->rawin);
|
|
strcat(tmpblk.c, " ");
|
|
|
|
/* dump the mapped version */
|
|
# ifndef NO_EXTENSIONS
|
|
if ((scan->flags & WHEN_INMV) && (when & (WHEN_VIINP|WHEN_VIREP)))
|
|
{
|
|
strcat(tmpblk.c, "visual ");
|
|
}
|
|
# endif
|
|
safequote(scan->cooked);
|
|
strcat(tmpblk.c, "\n");
|
|
twrite(fd, tmpblk.c, strlen(tmpblk.c));
|
|
}
|
|
}
|
|
}
|
|
#endif
|