/* $Id: term.c,v 3.0 1992/02/01 03:09:32 davison Trn $
 */
/* This software is Copyright 1991 by Stan Barber. 
 *
 * Permission is hereby granted to copy, reproduce, redistribute or otherwise
 * use this software as long as: there is no monetary profit gained
 * specifically from the use or reproduction of this software, it is not
 * sold, rented, traded or otherwise marketed, and this copyright notice is
 * included prominently in any copy made. 
 *
 * The authors make no claims as to the fitness or correctness of this software
 * for any use whatsoever, and it is provided as is. Any use of this software
 * is at the user's own risk. 
 */

#include "EXTERN.h"
#include "common.h"
#include "util.h"
#include "util2.h"
#include "final.h"
#include "help.h"
#include "cache.h"
#include "intrp.h"
#include "INTERN.h"
#include "term.h"

#ifdef u3b2
#undef TIOCGWINSZ
#endif

#undef	USETITE		/* use terminal init/exit seqences (not recommended) */
#undef	USEKSKE		/* use keypad start/end sequences */

char ERASECH;		/* rubout character */
char KILLCH;		/* line delete character */
char tcarea[TCSIZE];	/* area for "compiled" termcap strings */

int upcost;

/* guarantee capability pointer != Nullch */
/* (I believe terminfo will ignore the &tmpaddr argument.) */

char *tgetstr();
#define Tgetstr(key) ((tmpstr = tgetstr(key,&tmpaddr)) ? tmpstr : nullstr)

struct keymap {
    char km_type[128];
    union km_union {
	struct keymap *km_km;
	char *km_str;
    } km_ptr[128];
};

#define KM_NOTHIN 0
#define KM_STRING 1
#define KM_KEYMAP 2
#define KM_BOGUS 3

#define KM_TMASK 3
#define KM_GSHIFT 4
#define KM_GMASK 7

typedef struct keymap KEYMAP;

KEYMAP *topmap INIT(Null(KEYMAP*));

void mac_init();
KEYMAP *newkeymap();
void show_keymap();

void line_col_calcs();

/* terminal initialization */

void
term_init()
{
    savetty();				/* remember current tty state */

#ifdef I_TERMIO
    outspeed = _tty.c_cflag & CBAUD;	/* for tputs() */
    ERASECH = _tty.c_cc[VERASE];	/* for finish_command() */
    KILLCH = _tty.c_cc[VKILL];		/* for finish_command() */
    if (GT = ((_tty.c_oflag & TABDLY) != TAB3))
	/* we have tabs, so that's OK */;
    else
	_tty.c_oflag &= ~TAB3;	/* turn off kernel tabbing -- done in rn */
#else /* !I_TERMIO */
# ifdef I_TERMIOS
    outspeed = cfgetospeed(&_tty);	/* for tputs() (output) */
    ERASECH = _tty.c_cc[VERASE];	/* for finish_command() */
    KILLCH = _tty.c_cc[VKILL];		/* for finish_command() */
#if 0
    _tty.c_oflag &= ~OXTABS;	/* turn off kernel tabbing-done in rn */
#endif
# else /* !I_TERMIOS */
    outspeed = _tty.sg_ospeed;		/* for tputs() */
    ERASECH = _tty.sg_erase;		/* for finish_command() */
    KILLCH = _tty.sg_kill;		/* for finish_command() */
    if (GT = ((_tty.sg_flags & XTABS) != XTABS))
	/* we have tabs, so that's OK */;
    else
	_tty.sg_flags &= ~XTABS;
# endif /* I_TERMIOS */
#endif /* I_TERMIO */

    /* The following could be a table but I can't be sure that there isn't */
    /* some degree of sparsity out there in the world. */

    switch (outspeed) {			/* 1 second of padding */
#ifdef BEXTA
        case BEXTA:  just_a_sec = 1920; break;
#else
#ifdef B19200
        case B19200: just_a_sec = 1920; break;
#endif
#endif
        case B9600:  just_a_sec =  960; break;
        case B4800:  just_a_sec =  480; break;
        case B2400:  just_a_sec =  240; break;
        case B1800:  just_a_sec =  180; break;
        case B1200:  just_a_sec =  120; break;
        case B600:   just_a_sec =   60; break;
	case B300:   just_a_sec =   30; break;
	/* do I really have to type the rest of this??? */
        case B200:   just_a_sec =   20; break;
        case B150:   just_a_sec =   15; break;
        case B134:   just_a_sec =   13; break;
        case B110:   just_a_sec =   11; break;
        case B75:    just_a_sec =    8; break;
        case B50:    just_a_sec =    5; break;
        default:     just_a_sec =  960; break;
					/* if we are running detached I */
    }					/*  don't want to know about it! */
}

/* set terminal characteristics */

void
term_set(tcbuf)
char *tcbuf;		/* temp area for "uncompiled" termcap entry */
{
    char *tmpaddr;			/* must not be register */
    register char *tmpstr;
    char *s;
    int status;
#ifdef TIOCGWINSZ
    struct winsize winsize;
#endif

#ifdef PENDING
#if ! defined (FIONREAD) && ! defined (HAS_RDCHK)
    /* do no delay reads on something that always gets closed on exit */

    devtty = fileno(stdin);
    if (isatty(devtty)) {
	devtty = open("/dev/tty",0);
	if (devtty < 0) {
	    printf(cantopen,"/dev/tty") FLUSH;
	    finalize(1);
	}
	fcntl(devtty,F_SETFL,O_NDELAY);
    }
#endif
#endif
    
    /* get all that good termcap stuff */

#ifdef HAS_TERMLIB
    s = getenv("TERM");
    status = tgetent(tcbuf,s? s : "dumb");	/* get termcap entry */
    if (status < 1) {
#ifdef VERBOSE
	printf("No termcap %s found.\n", status ? "file" : "entry") FLUSH;
#else
	fputs("Termcap botch\n",stdout) FLUSH;
#endif
	finalize(1);
    }
    tmpaddr = tcarea;			/* set up strange tgetstr pointer */
    s = Tgetstr("pc");			/* get pad character */
    PC = *s;				/* get it where tputs wants it */
    if (!tgetflag("bs")) {		/* is backspace not used? */
	BC = Tgetstr("bc");		/* find out what is */
	if (BC == nullstr) 		/* terminfo grok's 'bs' but not 'bc' */
	    BC = Tgetstr("le");
    } else
	BC = "\b";			/* make a backspace handy */
    UP = Tgetstr("up");			/* move up a line */
    if (!*UP)				/* no UP string? */
	marking = 0;			/* disable any marking */
    if (muck_up_clear)			/* this is for weird HPs */
	CL = "\n\n\n\n";
    else
	CL = Tgetstr("cl");		/* get clear string */
    CE = Tgetstr("ce");			/* clear to end of line string */
    TI = Tgetstr("ti");			/* initialize display */
    TE = Tgetstr("te");			/* reset display */
    KS = Tgetstr("ks");			/* enter `keypad transmit' mode */
    KE = Tgetstr("ke");			/* exit `keypad transmit' mode */
    HO = Tgetstr("ho");			/* home cursor */
    IL = Tgetstr("al");			/* insert (add) line */
    CM = Tgetstr("cm");			/* cursor motion */
    if (*CM || *HO)
	can_home = TRUE;
#ifdef CLEAREOL
    CD = Tgetstr("cd");			/* clear to end of display */
    if (!*CE || !*CD || !can_home)	/* can we CE, CD, and home? */
	can_home_clear = FALSE;		/*  no, so disable use of clear eol */
    if (!*CE)
	CE = CD;
#endif /* CLEAREOL */
    upcost = strlen(UP);
    SO = Tgetstr("so");			/* begin standout */
    SE = Tgetstr("se");			/* end standout */
    if ((SG = tgetnum("sg"))<0)
	SG = 0;				/* blanks left by SG, SE */
    US = Tgetstr("us");			/* start underline */
    UE = Tgetstr("ue");			/* end underline */
    if ((UG = tgetnum("ug"))<0)
	UG = 0;				/* blanks left by US, UE */
    if (*US)
	UC = nullstr;			/* UC must not be NULL */
    else
	UC = Tgetstr("uc");		/* underline a character */
    if (!*US && !*UC) {			/* no underline mode? */
	US = SO;			/* substitute standout mode */
	UE = SE;
	UG = SG;
    }
    LINES = tgetnum("li");		/* lines per page */
    COLS = tgetnum("co");		/* columns on page */

#ifdef TIOCGWINSZ
    { struct winsize ws;
	if (ioctl(0, TIOCGWINSZ, &ws) >= 0 && ws.ws_row > 0 && ws.ws_col > 0) {
	    LINES = ws.ws_row;
	    COLS = ws.ws_col;
	}
    }
#endif
	
    AM = tgetflag("am");		/* terminal wraps automatically? */
    XN = tgetflag("xn");		/* then eats next newline? */
    VB = Tgetstr("vb");
    if (!*VB)
	VB = "\007";
    CR = Tgetstr("cr");
    if (!*CR) {
	if (tgetflag("nc") && *UP) {
	    CR = safemalloc((MEM_SIZE)strlen(UP)+2);
	    sprintf(CR,"%s\r",UP);
	}
	else
	    CR = "\r";
    }
#ifdef TIOCGWINSZ
	if (ioctl(1, TIOCGWINSZ, &winsize) >= 0) {
		if (winsize.ws_row > 0)
		    LINES = winsize.ws_row;
		if (winsize.ws_col > 0)
		    COLS = winsize.ws_col;
	}
#endif
#else
    ??????				/* Roll your own... */
#endif
    termlib_init();
    line_col_calcs();
    noecho();				/* turn off echo */
    crmode();				/* enter cbreak mode */

    mac_init(tcbuf);
}

void
set_macro(seq,def)
char *seq;	/* input sequence of keys */
char *def;	/* definition */
{
    mac_line(def,seq,0);
    /* check for common (?) brain damage: ku/kd/etc sequence may be the
     * cursor move sequence instead of the input sequence.
     * (This happens on the local xterm definitions.)
     * Try to recognize and adjust for this case.
     */
    if (seq[0] == '\033' && seq[1] == '[' && seq[2]) {
	char lbuf[LBUFLEN];	/* copy of possibly non-writable string */
	strcpy(lbuf,seq);
	lbuf[1] = 'O';
	mac_line(def,lbuf,0);
    }
    if (seq[0] == '\033' && seq[1] == 'O' && seq[2]) {
	char lbuf[LBUFLEN];	/* copy of possibly non-writable string */
	strcpy(lbuf,seq);
	lbuf[1] = '[';
	mac_line(def,lbuf,0);
    }
}

/* Turn the arrow keys into macros that do some basic trn functions.
** Code provided by Clifford Adams.
*/
void
arrow_macros(tmpbuf)
char *tmpbuf;
{
    char lbuf[256];			/* should be long enough */
    char *tmpaddr = tmpbuf;
    register char *tmpstr;

    /* If arrows are defined as single keys, we probably don't
     * want to redefine them.  (The tvi912c defines kl as ^H)
     */
    strcpy(lbuf,Tgetstr("ku"));		/* up */
    /* '(' at article or pager, '[' in thread sel, 'p' otherwise */
    if ((int)strlen(lbuf) > 1)
	set_macro(lbuf,"%(%m=[ap]?\\(:%(%m=t?[:p))");

    strcpy(lbuf,Tgetstr("kd"));		/* down */
    /* ')' at article or pager, ']' in thread sel, 'n' otherwise */
    if ((int)strlen(lbuf) > 1)
	set_macro(lbuf,"%(%m=[ap]?\\):%(%m=t?]:n))");

    strcpy(lbuf,Tgetstr("kl"));		/* left */
    /* '[' at article or pager, '<' otherwise */
    if ((int)strlen(lbuf) > 1)
	set_macro(lbuf,"%(%m=[ap]?\\[:<)");

    strcpy(lbuf,Tgetstr("kr"));		/* right */
    /* CR at newsgroups, ']' at article or pager, '>' otherwise */
    if ((int)strlen(lbuf) > 1)
	set_macro(lbuf,"%(%m=n?^j:%(%m=[ap]?\\]:>))");
}

void
mac_init(tcbuf)
char *tcbuf;
{
    char tmpbuf[1024];

    /* set up the Xterm mouse sequence */
    sprintf(tmpbuf,"\033[M");	/* enter mouse escape sequence */
    set_macro(tmpbuf,"\003");

    if (auto_arrow_macros)
	arrow_macros(tmpbuf);
    if (use_threads)
	tmpfp = fopen(filexp(getval("TRNMACRO",TRNMACRO)),"r");
    else
	tmpfp = Nullfp;
    if (!tmpfp)
	tmpfp = fopen(filexp(getval("RNMACRO",RNMACRO)),"r");
    if (tmpfp) {
	while (fgets(tcbuf,1024,tmpfp) != Nullch) {
	    mac_line(tcbuf,tmpbuf,(sizeof tmpbuf));
	}
	fclose(tmpfp);
    }
}

void
mac_line(line,tmpbuf,tbsize)
char *line;
char *tmpbuf;
int tbsize;
{
    register char *s, *m;
    register KEYMAP *curmap;
    register int ch;
    register int garbage = 0;
    static char override[] = "\nkeymap overrides string\n";

    if (topmap == Null(KEYMAP*))
	topmap = newkeymap();
    if (*line == '#' || *line == '\n')
	return;
    if (line[ch = strlen(line)-1] == '\n')
	line[ch] = '\0';
    /* A 0 length signifies we already parsed the macro into tmpbuf,
    ** so line is just the definition. */
    if (tbsize)
	m = dointerp(tmpbuf,tbsize,line," \t");
    else
	m = line;
    if (!*m)
	return;
    while (*m == ' ' || *m == '\t') m++;
    for (s=tmpbuf,curmap=topmap; *s; s++) {
	ch = *s & 0177;
	if (s[1] == '+' && isdigit(s[2])) {
	    s += 2;
	    garbage = (*s & KM_GMASK) << KM_GSHIFT;
	}
	else
	    garbage = 0;
	if (s[1]) {
	    if ((curmap->km_type[ch] & KM_TMASK) == KM_STRING) {
		if (tbsize)
		    fputs(override,stdout) FLUSH;
		free(curmap->km_ptr[ch].km_str);
		curmap->km_ptr[ch].km_str = Nullch;
	    }
	    curmap->km_type[ch] = KM_KEYMAP + garbage;
	    if (curmap->km_ptr[ch].km_km == Null(KEYMAP*))
		curmap->km_ptr[ch].km_km = newkeymap();
	    curmap = curmap->km_ptr[ch].km_km;
	}
	else {
	    if (tbsize && (curmap->km_type[ch] & KM_TMASK) == KM_KEYMAP)
		fputs(override,stdout) FLUSH;
	    else {
		curmap->km_type[ch] = KM_STRING + garbage;
		curmap->km_ptr[ch].km_str = savestr(m);
	    }
	}
    }
}

KEYMAP*
newkeymap()
{
    register int i;
    register KEYMAP *map;

#ifndef lint
    map = (KEYMAP*)safemalloc(sizeof(KEYMAP));
#else
    map = Null(KEYMAP*);
#endif /* lint */
    for (i=127; i>=0; --i) {
	map->km_ptr[i].km_km = Null(KEYMAP*);
	map->km_type[i] = KM_NOTHIN;
    }
    return map;
}

void
show_macros()
{
    char prebuf[64];

    if (topmap != Null(KEYMAP*)) {
	print_lines("Macros:\n",STANDOUT);
	*prebuf = '\0';
	show_keymap(topmap,prebuf);
    }
    else {
	print_lines("No macros defined.\n", NOMARKING);
    }
}

void
show_keymap(curmap,prefix)
register KEYMAP *curmap;
char *prefix;
{
    register int i;
    register char *next = prefix + strlen(prefix);
    register int kt;

    for (i=0; i<128; i++) {
	if (kt = curmap->km_type[i]) {
	    if (i < ' ')
		sprintf(next,"^%c",i+64);
	    else if (i == ' ')
		strcpy(next,"\\040");
	    else if (i == 127)
		strcpy(next,"^?");
	    else
		sprintf(next,"%c",i);
	    if ((kt >> KM_GSHIFT) & KM_GMASK) {
		sprintf(cmd_buf,"+%d", (kt >> KM_GSHIFT) & KM_GMASK);
		strcat(next,cmd_buf);
	    }
	    switch (kt & KM_TMASK) {
	    case KM_NOTHIN:
		sprintf(cmd_buf,"%s	%c\n",prefix,i);
		print_lines(cmd_buf,NOMARKING);
		break;
	    case KM_KEYMAP:
		show_keymap(curmap->km_ptr[(char)i].km_km, prefix);
		break;
	    case KM_STRING:
		sprintf(cmd_buf,"%s	%s\n",prefix,curmap->km_ptr[i].km_str);
		print_lines(cmd_buf,NOMARKING);
		break;
	    case KM_BOGUS:
		sprintf(cmd_buf,"%s	BOGUS\n",prefix);
		print_lines(cmd_buf,STANDOUT);
		break;
	    }
	}
    }
}

/* routine to pass to tputs */

char
putchr(ch)
register char_int ch;
{
    putchar(ch);
#ifdef lint
    ch = Null(char);
    ch = ch;
#endif
    return((char) 0);
}

int not_echoing = 0;

void
hide_pending()
{
    not_echoing = 1;
    pushchar(0200);
}

bool
macro_pending()
{
    if (nextout != nextin) {
	if (circlebuf[nextout] == '\200') {
	    switch (not_echoing) {
	    case 0:
		break;
	    case 1:
		nextout++;
		nextout %= PUSHSIZE;
		not_echoing = 0;
		break;
	    default:
		circlebuf[nextout] = '\n';
		not_echoing = 0;
		break;
	    }
	    return nextout != nextin;
	}
	return 1;
    }
    return 0;
}

/* input the 2nd and succeeding characters of a multi-character command */
/* returns TRUE if command finished, FALSE if they rubbed out first character */

int buflimit = LBUFLEN;

bool
finish_command(donewline)
int donewline;
{
    register char *s;
    register bool quoteone = FALSE;

    s = buf;
    if (s[1] != FINISHCMD)		/* someone faking up a command? */
	return TRUE;
    if (not_echoing)
	not_echoing = 2;
    do {
      top:
	if (not_echoing)
	    ;
	else if (*(unsigned char *)s < ' ') {
	    putchar('^');
	    putchar(*s | 64);
	}
	else if (*s == '\177') {
	    putchar('^');
	    putchar('?');
	}
	else
	    putchar(*s);		/* echo previous character */
	s++;
	if (s - buf == buflimit)
	    break;
re_read:
	fflush(stdout);
	getcmd(s);
	if (quoteone) {
	    quoteone = FALSE;
	    continue;
	}
	if (errno || *s == '\f') {
	    *s = Ctl('r');		/* force rewrite on CONT */
	}
	if (*s == '\033') {		/* substitution desired? */
#ifdef ESCSUBS
	    char tmpbuf[4], *cpybuf;

	    tmpbuf[0] = '%';
	    read_tty(&tmpbuf[1],1);
#ifdef RAWONLY
	    tmpbuf[1] &= 0177;
#endif
	    tmpbuf[2] = '\0';
	    if (tmpbuf[1] == 'h') {
		(void) help_subs();
		*s = '\0';
		reprint();
		goto re_read;
	    }
	    else if (tmpbuf[1] == '\033') {
		*s = '\0';
		cpybuf = savestr(buf);
		interp(buf, (sizeof buf), cpybuf);
		free(cpybuf);
		s = buf + strlen(buf);
		reprint();
		goto re_read;
	    }
	    else {
		interp(s,(sizeof buf) - (s-buf),tmpbuf);
		fputs(s,stdout);
		s += strlen(s);
	    }
	    goto re_read;
#else
	    notincl("^[");
	    *s = '\0';
	    reprint();
	    goto re_read;
#endif
	}
	else if (*s == ERASECH) {	/* they want to rubout a char? */
	    rubout();
	    s--;			/* discount the char rubbed out */
	    if (*(unsigned char *)s < ' ' || *s == '\177')
		rubout();
	    if (s == buf) {		/* entire string gone? */
		fflush(stdout);		/* return to single char command mode */
		return FALSE;
	    }
	    else
		goto re_read;
	}
	else if (*s == KILLCH) {	/* wipe out the whole line? */
	    while (s-- != buf) {	/* emulate that many ERASEs */
		rubout();
		if (*(unsigned char *)s < ' ' || *s == '\177')
		    rubout();
	    }
	    fflush(stdout);
	    return FALSE;		/* return to single char mode */
	}
#ifdef WORDERASE
	else if (*s == Ctl('w')) {	/* wipe out one word? */
	    *s-- = ' ';
	    while (!isspace(*s) || isspace(s[1])) {
		rubout();
		if (s-- == buf) {
		    fflush(stdout);
		    return FALSE;	/* return to single char mode */
		}
		if (*(unsigned char *)s < ' ' || *s == '\177')
		    rubout();
	    }
	    s++;
	    goto re_read;
	}
#endif
	else if (*s == Ctl('r')) {
	    *s = '\0';
	    reprint();
	    goto re_read;
	}
	else if (*s == Ctl('v')) {
	    putchar('^');
	    backspace();
	    fflush(stdout);
	    getcmd(s);
	    goto top;
	}
	else if (*s == '\\') {
	    quoteone = TRUE;
	}
#if defined(__bsdi__) || defined(cray)
    } while (*s != '\r' && *s != '\n'); /* until CR or NL (not echoed) */
#else
    } while (*s != '\n');		/* until a newline (not echoed) */
#endif
    *s = '\0';				/* terminate the string nicely */
    if (donewline)
	putchar('\n') FLUSH;
    return TRUE;			/* say we succeeded */
}

bool
finish_dblchar()
{
    bool ret;
    int buflimit_save = buflimit;
    int not_echoing_save = not_echoing;
    buflimit = 2;
    ret = finish_command(FALSE);
    buflimit = buflimit_save;
    not_echoing = not_echoing_save;
    return ret;
}

/* discard any characters typed ahead */

void
eat_typeahead()
{
    if (!typeahead && !macro_pending()) { /* cancel only keyboard stuff */
#ifdef PENDING
	while (input_pending())
	    read_tty(buf,sizeof(buf));
#else /* this is probably v7 */
# ifdef I_SGTTY
	ioctl(_tty_ch,TIOCSETP,&_tty);
# else
#  ifdef I_TERMIO
	ioctl(_tty_ch,TCSETAW,&_tty);
#  else
	tcsetattr(_tty_ch,TCSAFLUSH,&_tty);
#  endif
# endif
#endif
    }
}

void
save_typeahead(buf, len)
char *buf;
int len;
{
    int cnt;

    while (input_pending()) {
	cnt = read_tty(buf, len);
	buf += cnt;
	len -= cnt;
    }
    *buf = '\0';
}

void
settle_down()
{
    dingaling();
    fflush(stdout);
    /*sleep(1);*/
    nextout = nextin;			/* empty circlebuf */
    eat_typeahead();
}

/* read a character from the terminal, with multi-character pushback */

int
read_tty(addr,size)
char *addr;
int size;
{
    if (macro_pending()) {
	*addr = circlebuf[nextout++];
	nextout %= PUSHSIZE;
	return 1;
    }
    else {
	size = read(0,addr,size);
#ifdef RAWONLY
	*addr &= 0177;
#endif
	return size;
    }
}

#ifdef PENDING
# if !defined (FIONREAD) && !defined (HAS_RDCHK)
int
circfill()
{
    register int Howmany;

    errno = 0;
    Howmany = read(devtty,circlebuf+nextin,1);

    if (Howmany < 0 && (errno == EAGAIN || errno == EINTR))
	Howmany = 0;
    if (Howmany) {
	nextin += Howmany;
	nextin %= PUSHSIZE;
    }
    return Howmany;
}
# endif /* PENDING */
#endif /* FIONREAD */

void
pushchar(c)
char_int c;
{
    nextout--;
    if (nextout < 0)
	nextout = PUSHSIZE - 1;
    if (nextout == nextin) {
	fputs("\npushback buffer overflow\n",stdout) FLUSH;
	sig_catcher(0);
    }
    circlebuf[nextout] = c;
}

/* print an underlined string, one way or another */

void
underprint(s)
register char *s;
{
    assert(UC);
    if (*UC) {		/* char by char underline? */
	while (*s) {
	    if (*(unsigned char *)s < ' ') {
		putchar('^');
		backspace();/* back up over it */
		underchar();/* and do the underline */
		putchar(*s+64);
		backspace();/* back up over it */
		underchar();/* and do the underline */
	    }
	    else {
		putchar(*s);
		backspace();/* back up over it */
		underchar();/* and do the underline */
	    }
	    s++;
	}
    }
    else {		/* start and stop underline */
	underline();	/* start underlining */
	while (*s) {
	    if (*(unsigned char *)s < ' ') {
		putchar('^');
		putchar(*s+64);
	    }
	    else
		putchar(*s);
	    s++;
	}
	un_underline();	/* stop underlining */
    }
}

/* keep screen from flashing strangely on magic cookie terminals */

#ifdef NOFIREWORKS
void
no_sofire()
{
    if (*UP && *SE) {		/* should we disable fireworks? */
	putchar('\n');
	un_standout();
	up_line();
	carriage_return();
    }
}

void
no_ulfire()
{
    if (*UP && *US) {		/* should we disable fireworks? */
	putchar('\n');
	un_underline();
	up_line();
	carriage_return();
    }
}
#endif

/* get a character into a buffer */

void
getcmd(whatbuf)
register char *whatbuf;
{
    register KEYMAP *curmap;
    register int i;
    bool no_macros; 
    int times = 0;			/* loop detector */
    char scrchar;

tryagain:
    curmap = topmap;
#ifdef OLD_RN_WAY
    no_macros = (whatbuf != buf && !macro_pending()); 
#else
    no_macros = (whatbuf != buf); 
#endif
    for (;;) {
	int_count = 0;
	errno = 0;
	if (read_tty(whatbuf,1) < 0){
	    if (!errno)
	        errno = EINTR;
	    if (errno == EINTR)
		return;
	    perror(readerr);
	    sig_catcher(0);
	}
	lastchar = *whatbuf;
	if (*whatbuf & 0200 || no_macros) {
	    *whatbuf &= 0177;
	    goto got_canonical;
	}
	if (curmap == Null(KEYMAP*))
	    goto got_canonical;
	for (i = (curmap->km_type[*whatbuf] >> KM_GSHIFT) & KM_GMASK; i; --i){
	    read_tty(&scrchar,1);
	}
	switch (curmap->km_type[*whatbuf] & KM_TMASK) {
	case KM_NOTHIN:			/* no entry? */
	    if (curmap == topmap)	/* unmapped canonical */
		goto got_canonical;
	    settle_down();
	    goto tryagain;
	case KM_KEYMAP:			/* another keymap? */
	    curmap = curmap->km_ptr[*whatbuf].km_km;
	    assert(curmap != Null(KEYMAP*));
	    break;
	case KM_STRING:			/* a string? */
	    pushstring(curmap->km_ptr[*whatbuf].km_str,0200);
	    if (++times > 20) {		/* loop? */
		fputs("\nmacro loop?\n",stdout);
		settle_down();
	    }
	    no_macros = FALSE;
	    goto tryagain;
	}
    }

got_canonical:
#ifdef I_SGTTY
    if (*whatbuf == '\r')
	*whatbuf = '\n';
#endif
    if (whatbuf == buf)
	whatbuf[1] = FINISHCMD;		/* tell finish_command to work */
}

void
pushstring(str,bits)
char *str;
char_int bits;
{
    register int i;
    char tmpbuf[PUSHSIZE];
    register char *s = tmpbuf;

    assert(str != Nullch);
    interp(tmpbuf,PUSHSIZE,str);
    for (i = strlen(s)-1; i >= 0; --i)
	pushchar(s[i] ^ bits);
}

int
get_anything()
{
    char tmpbuf[2];

reask_anything:
    unflush_output();			/* disable any ^O in effect */
    standout();
#ifdef VERBOSE
    IF(verbose)
	fputs("[Type space to continue] ",stdout);
    ELSE
#endif
#ifdef TERSE
	fputs("[MORE] ",stdout);
#endif
    un_standout();
    fflush(stdout);
    eat_typeahead();
    if (int_count) {
	return -1;
    }
    cache_until_key();
    getcmd(tmpbuf);
    if (errno || *tmpbuf == '\f') {
	putchar('\n') FLUSH;		/* if return from stop signal */
	goto reask_anything;		/* give them a prompt again */
    }
    if (*tmpbuf == 'h') {
#ifdef VERBOSE
	IF(verbose)
	    fputs("\nType q to quit or space to continue.\n",stdout) FLUSH;
	ELSE
#endif
#ifdef TERSE
	    fputs("\nq to quit, space to continue.\n",stdout) FLUSH;
#endif
	goto reask_anything;
    }
    else if (*tmpbuf != ' ' && *tmpbuf != '\n') {
	carriage_return();
	erase_eol();	/* erase the prompt */
	carriage_return();
	return *tmpbuf == 'q' ? -1 : *tmpbuf;
    }
    if (*tmpbuf == '\n') {
	page_line = LINES - 1;
	carriage_return();
	erase_eol();
	carriage_return();
    }
    else {
	page_line = 1;
	if (erase_screen)		/* -e? */
	    clear();			/* clear screen */
	else {
	    carriage_return();
	    erase_eol();		/* erase the prompt */
	    carriage_return();
	}
    }
    return 0;
}

int
pause_getcmd()
{
    unflush_output();			/* disable any ^O in effect */
    standout();
#ifdef VERBOSE
    IF(verbose)
	fputs("[Type space or a command] ",stdout);
    ELSE
#endif
#ifdef TERSE
	fputs("[CMD] ",stdout);
#endif
    un_standout();
    fflush(stdout);
    eat_typeahead();
    if (int_count)
	return -1;
    cache_until_key();
    getcmd(buf);
    if (errno || *buf == '\f')
	return 0;			/* if return from stop signal */
    else if (*buf != ' ') {
	carriage_return();
	erase_eol();	/* erase the prompt */
	carriage_return();
	return *buf;
    }
    return 0;
}

void
in_char(prompt, newmode, dflt)
char *prompt;
char_int newmode;
char *dflt;
{
    char oldmode = mode;

reask_in_char:
    unflush_output();			/* disable any ^O in effect */
    printf("%s [%s] ", prompt, dflt);
    fflush(stdout);
    eat_typeahead();
    mode = newmode;
    getcmd(buf);
    if (errno || *buf == '\f') {
	putchar('\n') FLUSH;		/* if return from stop signal */
	goto reask_in_char;		/* give them a prompt again */
    }
    setdef(buf,dflt);
    mode = oldmode;
}

void
in_answer(prompt, newmode)
char *prompt;
char_int newmode;
{
    char oldmode = mode;

reask_in_answer:
    unflush_output();			/* disable any ^O in effect */
    fputs(prompt,stdout);
    fflush(stdout);
    eat_typeahead();
    mode = newmode;
reinp_in_answer:
    getcmd(buf);
    if (errno || *buf == '\f') {
	putchar('\n') FLUSH;		/* if return from stop signal */
	goto reask_in_answer;		/* give them a prompt again */
    }
    if (*buf != ' ' && *buf != '\n') 
	if (!finish_command(TRUE))
	    goto reinp_in_answer;
    mode = oldmode;
}

int
print_lines(what_to_print,hilite)
char *what_to_print;
int hilite;
{
    register char *s;
    register int i;

    if (page_line < 0)			/* they do not want to see this? */
	return -1;
    for (s=what_to_print; *s; ) {
	if (page_line >= LINES || int_count) {
	    if (i = -1, int_count || (i = get_anything())) {
		page_line = -1;		/* disable further print_lines */
		return i;
	    }
	}
	page_line++;
	if (hilite == STANDOUT) {
#ifdef NOFIREWORKS
	    if (erase_screen)
		no_sofire();
#endif
	    standout();
	}
	else if (hilite == UNDERLINE) {
#ifdef NOFIREWORKS
	    if (erase_screen)
		no_ulfire();
#endif
	    underline();
	}
	for (i=0; i<COLS; i++) {
	    if (!*s)
		break;
	    if (*(unsigned char *)s >= ' ')
		putchar(*s);
	    else if (*s == '\t') {
		putchar(*s);
		i = ((i+8) & ~7) - 1; 
	    }
	    else if (*s == '\n') {
		i = 32000;
	    }
	    else {
		i++;
		putchar('^');
		putchar(*s + 64);
	    }
	    s++;
	}
	if (i) {
	    if (hilite == STANDOUT)
		un_standout();
	    else if (hilite == UNDERLINE)
		un_underline();
	    if (AM && i == COLS)
		fflush(stdout);
	    else
		putchar('\n') FLUSH;
	}
    }
    return 0;
}

void
page_init()
{
    page_line = 1;
    if (erase_screen)
	clear();
    else
	putchar('\n') FLUSH;
}

void
pad(num)
int num;
{
    register int i;

    for (i = num; i; --i)
	putchar(PC);
    fflush(stdout);
}

/* echo the command just typed */

#ifdef VERIFY
void
printcmd()
{
    if (verify && buf[1] == FINISHCMD) {
	if (*(unsigned char *)buf < ' ') {
	    putchar('^');
	    putchar(*buf | 64);
	    backspace();
	    backspace();
	}
	else {
	    putchar(*buf);
	    backspace();
	}
	fflush(stdout);
    }
}
#endif

void
rubout()
{
    backspace();			/* do the old backspace, */
    putchar(' ');			/*   space, */
    backspace();			/*     backspace trick */
}

void
reprint()
{
    register char *s;

    fputs("^R\n",stdout) FLUSH;
    for (s = buf; *s; s++) {
	if (*(unsigned char *)s < ' ') {
	    putchar('^');
	    putchar(*s | 64);
	}
	else
	    putchar(*s);
    }
}

void
home_cursor()
{
    char *tgoto();

    if (!*HO) {			/* no home sequence? */
	if (!*CM) {		/* no cursor motion either? */
	    fputs("\n\n\n", stdout);
	    return;		/* forget it. */
	}
	tputs(tgoto(CM, 0, 0), 1, putchr);	/* go to home via CM */
	return;
    }
    else {			/* we have home sequence */
	tputs(HO, 1, putchr);	/* home via HO */
    }
}

void
goto_line(from,to)	/* assumes caller is already at beginning of line */
int from,to;
{
    char *tgoto(), *str;
    int cmcost;

    if (from == to)
	return;
    if (*CM && !muck_up_clear) {
	str = tgoto(CM,0,to);
	cmcost = strlen(str);
    } else {
	str = Nullch;
	cmcost = 9999;
    }
    if (to > from) {
      go_down:
	if (to - from <= cmcost) {
	    while(from++ < to)
		putchar('\n');
	    return;
	}
    } else if(*UP) {
	if ((from - to) * upcost <= cmcost) {
	    while(from-- > to)
		tputs(UP,1,putchr);
	    return;
	}
    } else if (cmcost == 9999) {
	home_cursor();
	from = 0;
	goto go_down;
    }
    tputs(str,1,putchr);
}

void
line_col_calcs()
{
     if (LINES > 0) {			/* is this a crt? */
	  if (!initlines || !initlines_specified)
	       /* no -i or unreasonable value for initlines */
	       if (outspeed >= B9600) 	/* whole page at >= 9600 baud */
		    initlines = LINES;
	       else if (outspeed >= B4800)/* 16 lines at 4800 */
		    initlines = 16;
	       else			/* otherwise just header */
		    initlines = 8;
     }
     else {				/* not a crt */
	  LINES = 30000;		/* so don't page */
	  CL = "\n\n";			/* put a couple of lines between */
	  if (!initlines || !initlines_specified)
	       /* make initlines reasonable */
	       initlines = 8;
     }
     if (COLS <= 0)
	  COLS = 80;
}


#ifdef SIGWINCH
Signal_t
winch_catcher(dummy)
int dummy;
{
    /* Reset signal in case of System V dain bramage */
    sigset(SIGWINCH, winch_catcher);

    /* Come here if window size change signal received */
#ifdef TIOCGWINSZ
    {	struct winsize ws;
	char lines[10], cols[10];
	if (ioctl(0, TIOCGWINSZ, &ws) >= 0 && ws.ws_row > 0 && ws.ws_col > 0) {
	    if (LINES != ws.ws_row || COLS != ws.ws_col) {
		LINES = ws.ws_row;
		COLS = ws.ws_col;
		line_col_calcs();
		sprintf(lines, "%d", LINES);
		sprintf(cols, "%d", COLS);
		export("LINES",lines);
		export("COLUMNS",cols);
		if (mode == 't' || mode == 'a' || mode == 'p')
		    forceme("\f");	/* cause a refresh */
					/* (defined only if TIOCSTI defined) */
	    }
	}
    }
#else
    /* Well, if SIGWINCH is defined, but TIOCGWINSZ isn't, there's    */
    /* almost certainly something wrong.  Figure it out for yourself, */
    /* because I don't know how to deal with it :-)                   */
    ERROR!
#endif
}
#endif

void
termlib_init()
{
#ifdef USETITE
    if (TI && *TI) {
	tputs(TI,1,putchr);
	fflush(stdout);
    }
#endif
#ifdef USEKSKE
    if (KS && *KS) {
	tputs(KS,1,putchr);
	fflush(stdout);
    }
#endif
}

void
termlib_reset()
{
#ifdef USETITE
    if (TE && *TE) {
	tputs(TE,1,putchr);
	fflush(stdout);
    }
#endif
#ifdef USEKSKE
    if (KE && *KE) {
	tputs(KE,1,putchr);
	fflush(stdout);
    }
#endif
}

void
xmouse_on()
{
    if (use_xterm_mouse && !xmouse_status) {
	printf("\033[?1001s");   /* save old highlight mouse tracking */
	printf("\033[?1000h");   /* enable mouse tracking */
	xmouse_status = TRUE;
    }
}

void
xmouse_off()
{
    if (use_xterm_mouse && xmouse_status) {
	printf("\033[?10001");   /* disable mouse tracking */
	printf("\033[?1001r");   /* restore old highlight mouse tracking */
	xmouse_status = FALSE;
    }
}
