565 lines
10 KiB
C
565 lines
10 KiB
C
/* tio.c */
|
|
|
|
/* Author:
|
|
* Steve Kirkendall
|
|
* 16820 SW Tallac Way
|
|
* Beaverton, OR 97006
|
|
* kirkenda@jove.cs.pdx.edu, or ...uunet!tektronix!psueea!jove!kirkenda
|
|
*/
|
|
|
|
|
|
/* This file contains terminal I/O functions */
|
|
|
|
#include <sys/types.h>
|
|
#include <signal.h>
|
|
#include "vi.h"
|
|
|
|
|
|
/* This function reads in a line from the terminal. */
|
|
int vgets(prompt, buf, bsize)
|
|
char 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 */
|
|
|
|
/* show the prompt */
|
|
move(LINES - 1, 0);
|
|
tab = 0;
|
|
if (prompt)
|
|
{
|
|
addch(prompt);
|
|
tab = 1;
|
|
}
|
|
clrtoeol();
|
|
refresh();
|
|
|
|
/* read in the line */
|
|
quoted = len = 0;
|
|
for (;;)
|
|
{
|
|
ch = getkey(quoted ? 0 : WHEN_EX);
|
|
|
|
/* some special conversions */
|
|
if (ch == ctrl('D') && len == 0)
|
|
ch = ctrl('[');
|
|
|
|
/* 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('['):
|
|
return -1;
|
|
|
|
case '\n':
|
|
case '\r':
|
|
clrtoeol();
|
|
goto BreakBreak;
|
|
|
|
case '\b':
|
|
if (len > 0)
|
|
{
|
|
len--;
|
|
addstr("\b\b\b\b\b\b\b\b" + 8 - widths[len]);
|
|
if (mode == MODE_EX)
|
|
{
|
|
clrtoeol();
|
|
}
|
|
tab -= widths[len];
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* strip off quotation bit */
|
|
if (ch & 256)
|
|
{
|
|
ch &= ~256;
|
|
quoted = FALSE;
|
|
qaddch(' ');
|
|
qaddch('\b');
|
|
}
|
|
/* add & echo the char */
|
|
if (len < bsize - 1)
|
|
{
|
|
if (ch == '\t')
|
|
{
|
|
widths[len] = *o_tabstop - (tab % *o_tabstop);
|
|
addstr(" " + 8 - widths[len]);
|
|
tab += widths[len];
|
|
}
|
|
else if (ch < ' ')
|
|
{
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
BreakBreak:
|
|
refresh();
|
|
buf[len] = '\0';
|
|
return len;
|
|
}
|
|
|
|
|
|
/* ring the terminal's bell */
|
|
beep()
|
|
{
|
|
if (*o_vbell)
|
|
{
|
|
tputs(VB, 1, faddch);
|
|
refresh();
|
|
}
|
|
else
|
|
{
|
|
write(1, "\007", 1);
|
|
}
|
|
}
|
|
|
|
static manymsgs; /* This variable keeps msgs from overwriting each other */
|
|
|
|
/* Write a message in an appropriate way. This should really be a varargs
|
|
* function, but there is no such thing as vwprintw. Hack!!! Also uses a
|
|
* little sleaze in the way it saves messages for repetition later.
|
|
*
|
|
* msg((char *)0) - repeats the previous message
|
|
* msg("") - clears the message line
|
|
* msg("%s %d", ...) - does a printf onto the message line
|
|
*/
|
|
msg(fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7)
|
|
char *fmt;
|
|
long arg1, arg2, arg3, arg4, arg5, arg6, arg7;
|
|
{
|
|
static char pmsg[80]; /* previous message */
|
|
char *start; /* start of current message */
|
|
|
|
if (mode != MODE_VI)
|
|
{
|
|
wprintw(stdscr, fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
|
|
addch('\n');
|
|
exrefresh();
|
|
}
|
|
else
|
|
{
|
|
/* redrawing previous message? */
|
|
if (!fmt)
|
|
{
|
|
move(LINES - 1, 0);
|
|
standout();
|
|
qaddch(' ');
|
|
addstr(pmsg);
|
|
qaddch(' ');
|
|
standend();
|
|
clrtoeol();
|
|
return;
|
|
}
|
|
|
|
/* just blanking out message line? */
|
|
if (!*fmt)
|
|
{
|
|
if (!*pmsg) return;
|
|
*pmsg = '\0';
|
|
move(LINES - 1, 0);
|
|
clrtoeol();
|
|
return;
|
|
}
|
|
|
|
/* wait for keypress between consecutive msgs */
|
|
if (manymsgs)
|
|
{
|
|
qaddstr("[More...]");
|
|
wqrefresh(stdscr);
|
|
getkey(0);
|
|
}
|
|
|
|
/* real message */
|
|
move(LINES - 1, 0);
|
|
standout();
|
|
qaddch(' ');
|
|
start = stdscr;
|
|
wprintw(stdscr, fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
|
|
strcpy(pmsg, start);
|
|
qaddch(' ');
|
|
standend();
|
|
clrtoeol();
|
|
refresh();
|
|
}
|
|
manymsgs = TRUE;
|
|
}
|
|
|
|
|
|
/* This function calls refresh() if the option exrefresh is set */
|
|
exrefresh()
|
|
{
|
|
/* If this ex command wrote ANYTHING set exwrote so vi's : command
|
|
* can tell that it must wait for a user keystroke before redrawing.
|
|
*/
|
|
if (stdscr > kbuf)
|
|
{
|
|
exwrote = TRUE;
|
|
}
|
|
|
|
/* now we do the refresh thing */
|
|
if (*o_exrefresh)
|
|
{
|
|
refresh();
|
|
}
|
|
else
|
|
{
|
|
wqrefresh(stdscr);
|
|
}
|
|
manymsgs = FALSE;
|
|
}
|
|
|
|
|
|
/* This variable holds a single ungotten key, or 0 for no key */
|
|
static int ungotten;
|
|
ungetkey(key)
|
|
int key;
|
|
{
|
|
ungotten = key;
|
|
}
|
|
|
|
/* This array describes mapped key sequences */
|
|
static struct _keymap
|
|
{
|
|
char *name; /* name of the key, or NULL */
|
|
char rawin[LONGKEY]; /* the unmapped version of input */
|
|
char cooked[80]; /* the mapped version of input */
|
|
int len; /* length of the unmapped version */
|
|
int when; /* when is this key mapped? */
|
|
}
|
|
mapped[MAXMAPS];
|
|
|
|
/* This function reads in a keystroke for VI mode. It automatically handles
|
|
* key mapping.
|
|
*/
|
|
static int dummy(){} /* for timeout */
|
|
int getkey(when)
|
|
int when; /* which bits must be ON? */
|
|
{
|
|
static char keybuf[100]; /* array of already-read keys */
|
|
static int nkeys; /* total number of keys in keybuf */
|
|
static int next; /* index of next key to return */
|
|
static char *cooked; /* rawin, or pointer to converted key */
|
|
register char *kptr; /* &keybuf[next] */
|
|
register struct _keymap *km; /* used to count through keymap */
|
|
register int i, j, k;
|
|
|
|
/* if this key is needed for delay between multiple error messages,
|
|
* then reset the manymsgs flag and abort any mapped key sequence.
|
|
*/
|
|
if (manymsgs)
|
|
{
|
|
manymsgs = FALSE;
|
|
cooked = (char *)0;
|
|
ungotten = 0;
|
|
}
|
|
|
|
/* if we have an ungotten key, use it */
|
|
if (ungotten != 0)
|
|
{
|
|
k = ungotten;
|
|
ungotten = 0;
|
|
return k;
|
|
}
|
|
|
|
/* if we're doing a mapped key, get the next char */
|
|
if (cooked && *cooked)
|
|
{
|
|
return *cooked++;
|
|
}
|
|
|
|
/* if keybuf is empty, fill it */
|
|
if (next == nkeys)
|
|
{
|
|
/* redraw if getting a VI command, refresh always */
|
|
if (when & WHEN_VICMD)
|
|
{
|
|
redraw(cursor, FALSE);
|
|
}
|
|
refresh();
|
|
|
|
/* read the rawin keystrokes */
|
|
while ((nkeys = read(0, keybuf, sizeof keybuf)) < 0)
|
|
{
|
|
/* terminal was probably resized */
|
|
*o_lines = LINES;
|
|
*o_columns = COLS;
|
|
if (when & (WHEN_VICMD|WHEN_VIINP|WHEN_VIREP))
|
|
{
|
|
redraw(MARK_UNSET, FALSE);
|
|
redraw(cursor, (when & WHEN_VICMD) == 0);
|
|
refresh();
|
|
}
|
|
}
|
|
next = 0;
|
|
}
|
|
|
|
/* see how many mapped keys this might be */
|
|
kptr = &keybuf[next];
|
|
for (i = j = 0, k = -1, km = mapped; i < MAXMAPS; i++, km++)
|
|
{
|
|
if ((km->when & when) && km->len > 0 && *km->rawin == *kptr)
|
|
{
|
|
if (km->len > nkeys - next
|
|
&& !strncmp(km->rawin, kptr, nkeys - next))
|
|
{
|
|
j++;
|
|
}
|
|
else if (km->len <= nkeys - next
|
|
&& !strncmp(km->rawin, kptr, km->len))
|
|
{
|
|
j++;
|
|
k = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* if more than one, try to read some more */
|
|
if (j > 1 && *o_keytime > 0)
|
|
{
|
|
signal(SIGALRM, dummy);
|
|
alarm((unsigned)*o_keytime);
|
|
k = read(0, keybuf + nkeys, sizeof keybuf - nkeys);
|
|
alarm(0);
|
|
|
|
/* if we couldn't read any more, pretend 0 mapped keys */
|
|
if (k < 1)
|
|
{
|
|
j = 0;
|
|
}
|
|
else /* else we got some more - try again */
|
|
{
|
|
nkeys += k;
|
|
for (i = j = 0, km = mapped; i < MAXMAPS; i++, km++)
|
|
{
|
|
if ((km->when & when)
|
|
&& km->len <= nkeys - next
|
|
&& *km->rawin == *kptr
|
|
&& !strncmp(km->rawin, kptr, km->len))
|
|
{
|
|
j++;
|
|
k = i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* if unambiguously mapped key, use it! */
|
|
if (j == 1 && k >= 0)
|
|
{
|
|
next += mapped[k].len;
|
|
cooked = mapped[k].cooked;
|
|
return *cooked++;
|
|
}
|
|
else
|
|
/* assume key is unmapped, but still translate weird erase key to '\b' */
|
|
if (keybuf[next] == ERASEKEY && when != 0)
|
|
{
|
|
next++;
|
|
return '\b';
|
|
}
|
|
else
|
|
{
|
|
return keybuf[next++];
|
|
}
|
|
}
|
|
|
|
|
|
mapkey(rawin, cooked, when, name)
|
|
char *rawin; /* the input key sequence, before mapping */
|
|
char *cooked;/* after mapping */
|
|
short when; /* bitmap of when mapping should happen */
|
|
char *name; /* name of the key, if any */
|
|
{
|
|
int i, j;
|
|
|
|
/* see if the key sequence was mapped before */
|
|
j = strlen(rawin);
|
|
for (i = 0; i < MAXMAPS; i++)
|
|
{
|
|
if (mapped[i].len == j && !strncmp(mapped[i].rawin, rawin, j))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* if not, then try to find a new slot to use */
|
|
if (i == MAXMAPS)
|
|
{
|
|
for (i = 0; i < MAXMAPS && mapped[i].len > 0; i++)
|
|
{
|
|
}
|
|
}
|
|
|
|
/* no room for the new key? */
|
|
if (i == MAXMAPS)
|
|
{
|
|
msg("No room left in the key map table");
|
|
return;
|
|
}
|
|
|
|
if (cooked && *cooked)
|
|
{
|
|
/* Map the key */
|
|
mapped[i].len = j;
|
|
strncpy(mapped[i].rawin, rawin, j);
|
|
strcpy(mapped[i].cooked, cooked);
|
|
mapped[i].when = when;
|
|
mapped[i].name = name;
|
|
}
|
|
else
|
|
{
|
|
mapped[i].len = 0;
|
|
}
|
|
}
|
|
|
|
|
|
dumpkey()
|
|
{
|
|
int i;
|
|
char *scan;
|
|
|
|
for (i = 0; i < MAXMAPS; i++)
|
|
{
|
|
/* skip unused entries */
|
|
if (mapped[i].len <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/* dump the key label, if any */
|
|
if (mapped[i].name)
|
|
{
|
|
qaddstr(mapped[i].name);
|
|
}
|
|
qaddch('\t');
|
|
|
|
/* write a '!' if defined with map! */
|
|
if (mapped[i].when & (WHEN_EX|WHEN_VIINP))
|
|
{
|
|
qaddch('!');
|
|
}
|
|
else
|
|
{
|
|
qaddch(' ');
|
|
}
|
|
qaddch(' ');
|
|
|
|
/* dump the raw version */
|
|
for (scan = mapped[i].rawin; scan < mapped[i].rawin + mapped[i].len; scan++)
|
|
{
|
|
if (*scan >= 0 && *scan < ' ' || *scan == '\177')
|
|
{
|
|
qaddch('^');
|
|
qaddch(*scan ^ '@');
|
|
}
|
|
else
|
|
{
|
|
qaddch(*scan);
|
|
}
|
|
}
|
|
|
|
qaddch('\t');
|
|
|
|
/* dump the mapped version */
|
|
for (scan = mapped[i].cooked; *scan; scan++)
|
|
{
|
|
if (*scan >= 0 && *scan < ' ' || *scan == '\177')
|
|
{
|
|
qaddch('^');
|
|
qaddch(*scan ^ '@');
|
|
}
|
|
else
|
|
{
|
|
qaddch(*scan);
|
|
}
|
|
}
|
|
|
|
addch('\n');
|
|
}
|
|
exrefresh();
|
|
}
|
|
|
|
|
|
|
|
/* This function saves the current configuration of mapped keys to a file */
|
|
savekeys(fd)
|
|
int fd; /* file descriptor to save them to */
|
|
{
|
|
int i;
|
|
|
|
/* HACK! refresh the screen now, so the output buffer is empty. */
|
|
refresh();
|
|
|
|
/* now write a map command for each key other thna the arrows */
|
|
for (i = 0; i < MAXMAPS; i++)
|
|
{
|
|
/* ignore keys mapped from termcap */
|
|
if (mapped[i].name)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/* If this isn't used, ignore it */
|
|
if (mapped[i].len <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/* write the map command */
|
|
wprintw(stdscr, "map%c %.*s %s",
|
|
(mapped[i].when & (WHEN_EX|WHEN_VIINP)) ? '!' : ' ',
|
|
mapped[i].len, mapped[i].rawin,
|
|
mapped[i].cooked);
|
|
qaddch('\n');
|
|
}
|
|
|
|
/* now write the buffer to the file */
|
|
if (stdscr != kbuf)
|
|
{
|
|
write(fd, kbuf, (stdscr - kbuf));
|
|
stdscr = kbuf;
|
|
}
|
|
}
|