4.4BSD/usr/src/contrib/nvi/nvi/term.c
/*-
* Copyright (c) 1991, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef lint
static char sccsid[] = "@(#)term.c 8.1 (Berkeley) 6/9/93";
#endif /* not lint */
#include <sys/types.h>
#include <sys/time.h>
#include <ctype.h>
#include <curses.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "vi.h"
static void check_sigwinch __P((SCR *));
static void onwinch __P((int));
static int term_read __P((SCR *, char *, int, int));
typedef struct _klist {
char *ts; /* Key's termcap string. */
char *output; /* Corresponding vi command. */
char *name; /* Name. */
} KLIST;
static KLIST klist[] = {
{"kA", "O", "insert line"},
{"kD", "x", "delete character"},
{"kd", "j", "cursor down"},
{"kE", "D", "delete to eol"},
{"kF", "\004", "scroll down"},
{"kH", "$", "go to eol"},
{"kh", "^", "go to sol"},
{"kI", "i", "insert at cursor"},
{"kL", "dd", "delete line"},
{"kl", "h", "cursor left"},
{"kN", "\006", "page down"},
{"kP", "\002", "page up"},
{"kR", "\025", "scroll up"},
{"kS", "dG", "delete to end of screen"},
{"kr", "l", "cursor right"},
{"ku", "k", "cursor up"},
{NULL},
};
/*
* term_init --
* Initialize the special array and special keys. The special array
* has a value for each special character that we can use in a switch
* statement.
*/
int
term_init(sp)
SCR *sp;
{
KLIST *kp;
char *sbp, *t, buf[2 * 1024], sbuf[128];
/* Keys that are treated specially. */
sp->special['^'] = K_CARAT;
sp->special['\004'] = K_CNTRLD;
sp->special['\022'] = K_CNTRLR;
sp->special['\024'] = K_CNTRLT;
sp->special['\032'] = K_CNTRLZ;
sp->special[':'] = K_COLON;
sp->special['\r'] = K_CR;
sp->special['\033'] = K_ESCAPE;
sp->special['\f'] = K_FORMFEED;
sp->special['\n'] = K_NL;
sp->special[')'] = K_RIGHTPAREN;
sp->special['}'] = K_RIGHTBRACE;
sp->special['\t'] = K_TAB;
sp->special[sp->gp->original_termios.c_cc[VERASE]] = K_VERASE;
sp->special[sp->gp->original_termios.c_cc[VKILL]] = K_VKILL;
sp->special[sp->gp->original_termios.c_cc[VLNEXT]] = K_VLNEXT;
sp->special[sp->gp->original_termios.c_cc[VWERASE]] = K_VWERASE;
sp->special['0'] = K_ZERO;
/*
* Special terminal keys.
* Get the termcap entry.
*/
switch (tgetent(buf, O_STR(sp, O_TERM))) {
case -1:
msgq(sp, M_ERR, "%s tgetent: %s.",
O_STR(sp, O_TERM), strerror(errno));
return (1);
case 0:
msgq(sp, M_ERR, "%s: unknown terminal type.",
O_STR(sp, O_TERM), strerror(errno));
return (1);
}
for (kp = klist; kp->name != NULL; ++kp) {
sbp = sbuf;
if ((t = tgetstr(kp->ts, &sbp)) == NULL)
continue;
if (seq_set(sp, kp->name, t, kp->output, SEQ_COMMAND, 0))
return (1);
}
return (0);
}
/*
* term_flush_pseudo --
* Flush the pseudo keys if an error occurred during the map.
*/
void
term_flush_pseudo(sp)
SCR *sp;
{
if (sp->atkey_len != 0) {
free(sp->atkey_buf);
sp->atkey_len = 0;
}
sp->mappedkey = NULL;
}
/*
* term_more_pseudo --
* Return if there are more pseudo keys.
*/
int
term_more_pseudo(sp)
SCR *sp;
{
return (sp->atkey_len || sp->mappedkey != NULL);
}
/*
* term_waiting --
* Return keys waiting.
*/
int
term_waiting(sp)
SCR *sp;
{
struct timeval t;
if (sp->atkey_len || sp->mappedkey != NULL)
return (1);
t.tv_sec = t.tv_usec = 0;
FD_SET(STDIN_FILENO, &sp->rdfd);
return (select(32, &sp->rdfd, NULL, NULL, &t));
}
/*
* term_key --
* This function reads in a keystroke, as well as handling
* mapped keys and executed cut buffers.
*/
int
term_key(sp, flags)
SCR *sp;
u_int flags; /* TXT_MAPCOMMAND */
{
int ch;
SEQ *qp;
int ispartial, nr;
/* Sync the recovery file if necessary. */
if (F_ISSET(sp->ep, F_RCV_ALRM)) {
(void)rcv_sync(sp, sp->ep);
F_CLR(sp->ep, F_RCV_ALRM);
}
/* If in the middle of an @ macro, return the next char. */
if (sp->atkey_len) {
ch = *sp->atkey_cur++;
if (--sp->atkey_len == 0)
free(sp->atkey_buf);
goto ret;
}
/* If returning a mapped key, return the next char. */
if (sp->mappedkey) {
ch = *sp->mappedkey;
if (*++sp->mappedkey == '\0')
sp->mappedkey = NULL;
goto ret;
}
/* Read in more keys if necessary. */
if (sp->nkeybuf == 0) {
/* Read the keystrokes. */
sp->nkeybuf = term_read(sp, sp->keybuf, sizeof(sp->keybuf), 0);
sp->nextkey = 0;
/*
* If no keys read, then we've reached EOF of an ex script.
* XXX
* This is just wrong...
*/
if (sp->nkeybuf == 0) {
F_SET(sp, S_EXIT_FORCE);
return(0);
}
}
/*
* Check for mapped keys. If get a partial match, copy the current
* keys down in memory to maximize the space for new keys, and try
* to read more keys to complete the map. Max map is sizeof(keybuf)
* and probably not worth fixing.
*/
if (LF_ISSET(TXT_MAPCOMMAND)) {
retry: qp = seq_find(sp, &sp->keybuf[sp->nextkey], sp->nkeybuf,
LF_ISSET(TXT_MAPCOMMAND) ? SEQ_COMMAND : SEQ_INPUT,
&ispartial);
if (qp == NULL)
goto nomap;
if (ispartial) {
if (sizeof(sp->keybuf) == sp->nkeybuf) {
msgq(sp, M_ERR,
"Partial map is too long; keys corrupted.");
goto nomap;
} else {
memmove(&sp->keybuf[sp->nextkey],
sp->keybuf, sp->nkeybuf);
sp->nextkey = 0;
nr = term_read(sp, sp->keybuf + sp->nkeybuf,
sizeof(sp->keybuf) - sp->nkeybuf, 1);
if (nr) {
sp->nkeybuf += nr;
goto retry;
}
}
} else {
sp->nkeybuf -= qp->ilen;
sp->nextkey += qp->ilen;
if (qp->output[1] == '\0')
ch = *qp->output;
else {
sp->mappedkey = qp->output;
ch = *sp->mappedkey++;
}
goto ret;
}
}
nomap: --sp->nkeybuf;
ch = sp->keybuf[sp->nextkey++];
/*
* O_BEAUTIFY eliminates all control characters except tab,
* newline, form-feed and escape.
*/
ret: if (LF_ISSET(TXT_BEAUTIFY) && O_ISSET(sp, O_BEAUTIFY)) {
if (isprint(ch) || sp->special[ch] == K_ESCAPE ||
sp->special[ch] == K_FORMFEED || sp->special[ch] == K_NL ||
sp->special[ch] == K_TAB)
return (ch);
sp->s_bell(sp);
return (term_key(sp, flags));
}
return (ch);
}
static int __check_sig_winch; /* XXX GLOBAL */
static int __set_sig_winch; /* XXX GLOBAL */
static int
term_read(sp, buf, len, timeout)
SCR *sp;
char *buf; /* Where to store the characters. */
int len; /* Max characters to read. */
int timeout; /* If timeout set. */
{
struct timeval t, *tp;
int nr;
/* Set up SIGWINCH handler. */
if (__set_sig_winch == 0) {
(void)signal(SIGWINCH, onwinch);
__set_sig_winch = 1;
}
/*
* If reading from a file or pipe, never timeout. This
* also affects the way that EOF is detected.
*/
if (!F_ISSET(sp, S_ISFROMTTY)) {
if ((nr = read(STDIN_FILENO, buf, len)) == 0)
F_SET(sp, S_EXIT_FORCE);
return (0);
}
/* Compute the timeout value. */
if (timeout) {
t.tv_sec = O_VAL(sp, O_KEYTIME) / 10;
t.tv_usec = (O_VAL(sp, O_KEYTIME) % 10) * 100000L;
tp = &t;
FD_SET(STDIN_FILENO, &sp->rdfd);
}
/* Select until characters become available, and then read them. */
for (;;) {
if (timeout)
switch (select(32, &sp->rdfd, NULL, NULL, tp)) {
case -1: /* Error or interrupt. */
if (errno == EINTR) {
check_sigwinch(sp);
continue;
}
msgq(sp, M_ERR,
"Terminal read error: %s", strerror(errno));
return (0);
case 0: /* Timeout. */
return (0);
}
switch (nr = read(STDIN_FILENO, buf, len)) {
case -1: /* Error or interrupt. */
if (errno == EINTR) {
check_sigwinch(sp);
continue;
}
F_SET(sp, S_EXIT_FORCE);
msgq(sp, M_ERR,
"Terminal read error: %s", strerror(errno));
return (0);
case 0: /* EOF. */
F_SET(sp, S_EXIT_FORCE);
return (0);
default:
return (nr);
}
}
/* NOTREACHED */
}
/*
* onwinch --
* Handle SIGWINCH.
*/
static void
onwinch(signo)
int signo;
{
__check_sig_winch = 1;
}
/*
* check_sigwinch --
* Check for window size change event. Done here because it's
* the only place we block.
*/
static void
check_sigwinch(sp)
SCR *sp;
{
sigset_t bmask, omask;
while (__check_sig_winch == 1) {
sigemptyset(&bmask);
sigaddset(&bmask, SIGWINCH);
(void)sigprocmask(SIG_BLOCK, &bmask, &omask);
set_window_size(sp, 0);
F_SET(sp, S_RESIZE);
if (F_ISSET(sp, S_MODE_VI)) {
/*
* XXX
* This code needs an EXF structure!!
* (void)scr_update(sp);
*/
refresh();
}
__check_sig_winch = 0;
(void)sigprocmask(SIG_SETMASK, &omask, NULL);
}
}