/*
 *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
 *
 *	Terminal interface.
 */
#define raw __curses__raw__
#include <signal.h>
#include <errno.h>
#include "config.h"
#include "keymap.h"
#include "regexp.h"
#include "nn_term.h"

#ifdef RESIZING
#include <sys/ioctl.h>			/* for TIOCGWINSZ */

#ifdef SYSV_RESIZING
#include <sys/stream.h>
#include <sys/ptem.h>
#endif	/* SYSV_RESIZING */

extern int s_resized;
#endif	/* RESIZING */

#ifdef FAKE_INTERRUPT
#include <setjmp.h>
#endif

#ifdef USE_TERMINFO
# ifdef HPUX80
# include <termio.h>
# endif /* HPUX80 */
# include <curses.h>
# ifndef VINTR
#  include <termio.h>	/* some systems don't include this in curses.h */
# endif /* VINTR */
# ifndef auto_left_margin
#  include <term.h>
# endif	/* !auto_left_margin */
#else
#define USE_TERMCAP
#endif

#ifdef HAVE_TERMIO
# ifdef USE_TERMCAP
#  include <termio.h>
# endif	/* USE_TERMCAP */
#else
# include <sgtty.h>
#endif

/* SYSV curses.h clash */
#undef raw

/* term.c */

#ifdef __STDC__
static int outc __APROTO((char c));
#else
static int outc __APROTO(());
#endif
static void set_term_speed __APROTO((register unsigned long sp));
static void raw_not_ok_error __APROTO((void));
static sig_type rd_timeout __APROTO((int n));
static int read_char_kbd __APROTO((int tmo));
static void unread_char __APROTO((int c));

static sig_type catch_winch();

void visual_on();
void xterm_mouse_on();
void xterm_mouse_off();

import int data_bits;
import int batch_mode;
import char *help_directory;

extern void ding();
extern void clrmsg();
extern void gotoxy();

struct msg_list {
    char *buf;
    struct msg_list *prev;
};
static struct msg_list *msg_stack = NULL, *msg_ptr = NULL;

export int  message_history = 15;
export char *term_name = NULL;
export int  show_current_time = 1;
export int  conf_dont_sleep = 0;
export int  prompt_length;
export int  terminal_speed = 0;
export int  slow_speed = 1200;
export int  any_message = 0;
export int  flow_control = 1;
export int  use_visible_bell = 1; /* if supported by terminal */
export int  ignore_xon_xoff = 1;
export int  multi_key_guard_time = 2; /* tenths of a second */
export int  guard_double_slash = 0; /* need /// or //+ to clear line */
export char *shade_on_attr = NULL;
export char *shade_off_attr = NULL;
export int  mouse_state; /* if xterm is in mouse state */
export int  mouse_usage;  /* 0 don't set mouse, 1 only if xterm,
			    2 set mouse */

export key_type help_key = '?';
export key_type comp1_key = SP;
export key_type comp2_key = TAB;
export key_type erase_key, kill_key;
export key_type delword_key = CONTROL_('W');

static char bell_str[256] = "\007";
static appl_keypad_mode = 0;

#ifdef USE_TERMINFO

#define HAS_CAP(str) (str && *str)

extern char *tgoto();		/* some systems don't have this in term.h */

#else

char *tgoto();
char PC;
char *BC, *UP;
short ospeed;

static char XBC[64], XUP[64];
static char enter_ca_mode[64], exit_ca_mode[64];
static char cursor_home[64];
static char cursor_address[128];
static char clear_screen[64];
static char clr_eol[64];
static char clr_eos[64];
static char enter_standout_mode[64], exit_standout_mode[64];
static char enter_underline_mode[64], exit_underline_mode[64];
static char key_down[64], key_up[64], key_right[64], key_left[64];
static char keypad_local[64], keypad_xmit[64];

int  magic_cookie_glitch;	/* magic cookie size */
int  ceol_standout_glitch;	/* hp brain damage! */
int  auto_right_margin;		/* automatic right margin */
int  eat_newline_glitch;	/* newline ignored at right margin */

#define putp(str)	tputs(str, 1, outc)

#define HAS_CAP(str)	(*str)

#endif	/* USE_TERMCAP */

static char key_mouse_d1[64] = "\33[M ";
static char key_mouse_d2[64] = "\33[M!";
static char key_mouse_d3[64] = "\33[M\"";
static char key_mouse_u1[64] = "\33[M#";

/*
 * Compute the greatest multiple of p not greater than x.
 * p must be a power of 2.
 * x must be nonnegative, for portability to non-2's-complement hosts.
 */	
#define DISCARD_REMAINDER(x,p) ((x) & ~((p)-1))

#ifdef __STDC__
static int
outc(char c)
#else
static int
outc(c) char c;
#endif
{
    putchar(c);
    return 0;			/* XXX What is it supposed to return? */
}


#ifdef BROKEN_TPUTS
/* tputs is broken on UNISYS I've been told */
#define putpc(str, cnt)	tputs(str, 0, outc)
#else
#define putpc(str, cnt)	tputs(str, cnt, outc)
#endif

#undef standout

int  Lines, Columns;	/* screen size */
int  cookie_size;	/* size of magic cookie */
int  two_cookies;	/* space needed to enter&exit standout mode */
int  STANDOUT;		/* terminal got standout mode */

static int curxy_c = 0, curxy_l = -1, savxy_c = 0, savxy_l = -1;
static int just_sent_cr = 0;	/* just sent \r to avoid 'xn' term attribute */

static int *nonsp;	/* number of non-space characters on line */

#ifdef TERM_DEBUG
static char *term_debug = NULL;
extern char *getenv();
#define curxy_nonsp	(curxy_l < 0 ? -1 : nonsp[curxy_l])
#endif

#ifdef FAKE_INTERRUPT

extern jmp_buf fake_keyb_sig;
extern int arm_fake_keyb_sig;
extern char fake_keyb_siglist[];
#endif	/* FAKE_INTERRUPT */

#ifdef HAVE_TERMIO

/* This used to be 50, but there are some rather complex bugs in the SYSV */
/* TERMIO driver... */
#define KEY_BURST 10	/* read bursts of 1 char (or timeout after 100 ms) */

#undef CBREAK

static struct termio norm_tty, raw_tty;

#define	IntrC	((key_type) norm_tty.c_cc[VINTR])
#define	EraseC	((key_type) norm_tty.c_cc[VERASE])
#define KillC	((key_type) norm_tty.c_cc[VKILL])
#define SuspC	((key_type) CONTROL_('Z'))	/* norm_tty.c_cc[SWTCH] */

#else	/* V7/BSD TTY DRIVER */

static struct sgttyb norm_tty, raw_tty;
static struct tchars norm_chars;

#define	IntrC	((key_type) norm_chars.t_intrc)
#define	EraseC	((key_type) norm_tty.sg_erase)
#define KillC	((key_type) norm_tty.sg_kill)

#ifdef TIOCGLTC
static struct 	ltchars spec_chars;
#define SuspC	((key_type) spec_chars.t_suspc)
#else
#define	SuspC	((key_type) CONTROL_('Z'))
#endif

#endif

#ifdef USE_TERMCAP

opt_cap(cap, buf)
char *cap, *buf;
{
    char *tgetstr();

    *buf = NUL;
    return tgetstr(cap, &buf) != NULL;
}

get_cap(cap, buf)
char *cap, *buf;
{
    if (!opt_cap(cap, buf))
	nn_exitmsg(1, "TERMCAP entry for %s has no '%s' capability",
		   term_name, cap);
}

#endif  /* USE_TERMCAP */

/*
 * timeout in n/10 seconds via SIGALRM
 */

void micro_alarm(n)
int n;
{
#ifdef HAVE_UALARM
    ualarm(n<=1 ? 100000 : n*100000, 0); /* 4.3 BSD ualarm() */
#else
#ifdef MICRO_ALARM
    if (n <= 0) n = 1;
    MICRO_ALARM(n);			/* System specific timeout */
#else
    alarm(n <= 10 ? 1 : (n+9)/10);	/* Standard alarm() call */
#endif	/* MICRO_ALARM */
#endif	/* HAVE_UALARM */
}

static int multi_keys = 0;

static struct multi_key {
    key_type *cur_key;
    key_type *keys;
    key_type code;
} multi_key_list[MULTI_KEYS];

void enter_multi_key(code, keys)
int code;
key_type *keys;
{
    register i;

    if (strlen((char *)keys) == 1)
	/* will ignore arrow keys overlaying these keys */
	if (*keys == NL || *keys == CR ||
	    *keys == erase_key || *keys == kill_key ||
	    *keys == IntrC) return;

    /* lookup code to see if it is already defined... */
    for (i = 0; i < multi_keys; i++)
	if (multi_key_list[i].code == (key_type)code)
	    goto replace_key;

    i = multi_keys++;

    /* now i points to matching or empty slot */
    if (i >= MULTI_KEYS) {
	/* should never happen */
	log_entry('E', "too many multi keys");
	return;
    }

 replace_key:

    multi_key_list[i].keys = keys;
    multi_key_list[i].code = code;
}

void dump_multi_keys()
{
    register i;
    register key_type *cp;

    clrdisp();
    pg_init(0, 1);

    for (i = 0; i < multi_keys; i++) {
	if (pg_next() < 0) break;
	tprintf("%d\t%s\t", i, key_name(multi_key_list[i].code));
	for (cp = multi_key_list[i].keys; *cp; cp++)
	    tprintf(" %s", key_name(*cp));
    }

    pg_end();
}


#ifdef RESIZING

static sig_type catch_winch(n)
int n;
{
    struct winsize winsize;
    int i;

    (void) signal(SIGWINCH, catch_winch);
    if (ioctl(0, TIOCGWINSZ, &winsize) >= 0
	&& (winsize.ws_row != Lines || winsize.ws_col != Columns)) {
	nonsp = resizeobj(nonsp, int, winsize.ws_row);
	if ((int)winsize.ws_row > Lines)
	    for (i = Lines; i < (int)winsize.ws_row; i++) nonsp[i] = winsize.ws_col;
	if ((int)winsize.ws_col < Columns)
	    for (i = 0; i < (int)winsize.ws_row; i++)
		if (nonsp[i] > (int)winsize.ws_col) nonsp[i] = winsize.ws_col;
	Lines = winsize.ws_row;
	Columns = winsize.ws_col;
	curxy_l = -1;
	s_redraw = 1;
	s_resized = 1;
    }
#ifdef FAKE_INTERRUPT
    if (fake_keyb_siglist[n] && arm_fake_keyb_sig)
	longjmp(fake_keyb_sig, 1);
#endif	/* FAKE_INTERRUPT */
}
#endif /* RESIZING */

#ifdef SV_INTERRUPT
#ifdef NO_SIGINTERRUPT
static siginterrupt(signo, on)
{
  struct sigvec sv;
  sv.sv_handler = signal (signo, SIG_DFL);
  sv.sv_mask = 0;
  sv.sv_flags = on ? SV_INTERRUPT : 0;
  sigvec (signo, &sv, 0);
}
#endif	/* NO_SIGINTERRUPT */
#endif	/* SV_INTERRUPT */

#ifdef FAKE_INTERRUPT
#define SV_INTERRUPT
static siginterrupt (signo, on)
{
    fake_keyb_siglist[signo] = on;
}
#endif	/* FAKE_INTERRUPT */

static unsigned sp_table[] = {
    B9600, 960,
#ifdef B19200
    B19200, 1920,
#else
#ifdef EXTA
    EXTA, 1920,
#endif	/* EXTA */
#endif	/* B19200 */
#ifdef B38400
    B38400, 3840,
#else
#ifdef EXTB
    EXTB, 3840,
#endif	/* EXTB */
#endif	/* B38400 */
    B1200, 120,
    B2400, 240,
    B4800, 480,
    B300,   30,
    0,	0
};

static  void set_term_speed(sp)
register unsigned long sp;
{
    register unsigned *tp;

    for (tp = sp_table; *tp; tp += 2)
	if (*tp == sp) {
	    terminal_speed = tp[1];
	    return;
	}

    terminal_speed = 30;
}

static void raw_not_ok_error()
{
    if (batch_mode) return;
    nn_exitmsg(1, "Not prepared for terminal i/o");
    /*NOTREACHED*/
}


#define	RAW_CHECK	if (terminal_speed == 0) {raw_not_ok_error(); return 0;}
#define	RAW_CHECK_V	if (terminal_speed == 0) {raw_not_ok_error(); return;}
#define BATCH_CHECK	if (terminal_speed == 0) return 0
#define BATCH_CHECK_V	if (terminal_speed == 0) return

void init_term(full)
int full;
{
#ifdef USE_TERMCAP
    char tbuf[1024];
#endif
    int i;

#ifdef TERM_DEBUG
    term_debug = getenv("TERM_DEBUG");
    if (term_debug) fprintf (stderr, "init_term(%d)\n", full);
#endif

    if (batch_mode) {
	term_name = "batch";
	close(0);
	open("/dev/null", 0);
	STANDOUT = 0;
	cookie_size = 1;
	return;
    }

    if ((term_name = getenv("TERM")) == NULL) {
	if (full)
	    nn_exitmsg(1, "No TERM variable in environment");
	else
	    term_name = "unknown";
    }

    if (!full) return;

#ifdef HAVE_TERMIO
    ioctl(0, TCGETA, &norm_tty);
#else
    ioctl(0, TIOCGETP, &norm_tty);
#endif	/* HAVE_TERMIO */

#ifdef USE_TERMINFO
    setupterm((char *)NULL, 1, (int *)NULL);
    Columns = columns;
    Lines = lines;
    if (use_visible_bell && HAS_CAP(flash_screen))
	strcpy(bell_str, flash_screen);
    else if (HAS_CAP(bell))
	strcpy(bell_str, bell);
    if (! HAS_CAP(cursor_home))
	cursor_home = copy_str(tgoto(cursor_address, 0, 0));
#else

    if (tgetent(tbuf, term_name) <= 0)
	nn_exitmsg(1, "Unknown terminal type: %s", term_name);

    if (opt_cap("bc", XBC)) BC = XBC;
    if (opt_cap("up", XUP)) UP = XUP;
    opt_cap("pc", cursor_address);	/* temp. usage */
    PC = cursor_address[0];

    get_cap("cm", cursor_address);
    if (!opt_cap("ho", cursor_home))
	strcpy(cursor_home, tgoto(cursor_address, 0, 0));

    get_cap("cl", clear_screen);
    opt_cap("ce", clr_eol);
    opt_cap("cd", clr_eos);

    Lines = tgetnum("li");
    Columns = tgetnum("co");

    opt_cap("so", enter_standout_mode);
    opt_cap("se", exit_standout_mode);

    opt_cap("us", enter_underline_mode);
    opt_cap("ue", exit_underline_mode);

    opt_cap("kd", key_down);
    opt_cap("ku", key_up);
    opt_cap("kr", key_right);
    opt_cap("kl", key_left);

    magic_cookie_glitch = tgetnum("sg");

    ceol_standout_glitch = tgetflag("xs");
    auto_right_margin = tgetflag("am");
    eat_newline_glitch = tgetflag("xn");

    opt_cap("ti", enter_ca_mode);
    opt_cap("te", exit_ca_mode);
    opt_cap("ks", keypad_xmit);		/* used to turn "application cursor */
    opt_cap("ke", keypad_local);	/* key" mode on and off (sometimes) */

    if (!use_visible_bell || !opt_cap("vb", bell_str))
	if (!opt_cap("bl", bell_str))
	    strcpy(bell_str, "\007");

#endif /* USE_TERMINFO */

#ifdef RESIZING
    {
	struct winsize winsize;

	if (ioctl(0, TIOCGWINSZ, &winsize) >= 0
	    && winsize.ws_row != 0 && winsize.ws_col != 0) {
	    Lines = winsize.ws_row;
	    Columns = winsize.ws_col;
	    (void) signal(SIGWINCH, catch_winch);
#ifdef SV_INTERRUPT
	    siginterrupt(SIGWINCH, 1);	/* make read from tty interruptable */
#endif /* SV_INTERRUPT */
	}
    }
#endif /* RESIZING */

    /* Stop NN from blowing up if on a *really* dumb terminal, like "dumb" */
    if (Lines < 1)
      Lines = 24;
    if (Columns < 1)
      Columns = 80;

    nonsp = newobj(int, Lines);
    for (i = 0; i < Lines; i++) nonsp[i] = Columns;
    
    STANDOUT = HAS_CAP(enter_standout_mode);
    cookie_size = magic_cookie_glitch;
    if (STANDOUT) {
	if (cookie_size < 0) cookie_size = 0;
	two_cookies = 2 * cookie_size;
    } else
	cookie_size = two_cookies = 0;

    raw_tty = norm_tty;

#ifdef HAVE_TERMIO
    raw_tty.c_iflag &= ~(BRKINT|INLCR|ICRNL|IGNCR|ISTRIP);
    raw_tty.c_iflag |= IGNBRK|IGNPAR;
    raw_tty.c_oflag &= ~OPOST;
    raw_tty.c_lflag &= ~(ISIG|ICANON|XCASE|ECHO|NOFLSH);

    /* read a maximum of 10 characters in one burst; timeout in 1-200 ms */
    raw_tty.c_cc[VMIN] = KEY_BURST;
    raw_tty.c_cc[VTIME] = ((int)(raw_tty.c_cflag & CBAUD) > B1200) ? 1 : 2;
    set_term_speed((unsigned long)(raw_tty.c_cflag & CBAUD));
#else
    ioctl(0, TIOCGETC, &norm_chars);

#ifdef TIOCGLTC
    ioctl(0, TIOCGLTC, &spec_chars);
#endif	/* TIOCGLTC */

    ospeed = norm_tty.sg_ospeed;
    set_term_speed((unsigned long)ospeed);

    raw_tty.sg_flags &= ~(ECHO | CRMOD);
#ifdef CBREAK
#ifdef SV_INTERRUPT			/* make read from tty interruptable */
    siginterrupt(SIGTSTP, 1);		/* this is necessary to redraw screen */
#endif	/* SV_INTERRUPT */
    raw_tty.sg_flags |= CBREAK;
#else
    raw_tty.sg_flags |= RAW;
#endif	/* CBREAK */

#ifdef SV_INTERRUPT
    siginterrupt(SIGALRM, 1);		/* make read from tty interruptable */
#endif	/* SV_INTERRUPT */
#endif	/* HAVE_TERMIO */

    erase_key = EraseC;
    kill_key  = KillC;

    if (HAS_CAP(key_down))
	enter_multi_key(K_down_arrow, (key_type *)key_down);
    if (HAS_CAP(key_up))
	enter_multi_key(K_up_arrow, (key_type *)key_up);
    if (HAS_CAP(key_right))
	enter_multi_key(K_right_arrow, (key_type *)key_right);
    if (HAS_CAP(key_left))
	enter_multi_key(K_left_arrow, (key_type *)key_left);

    enter_multi_key(K_m_d1, (key_type *)key_mouse_d1);
    enter_multi_key(K_m_d2, (key_type *)key_mouse_d2);
    enter_multi_key(K_m_d3, (key_type *)key_mouse_d3);
    enter_multi_key(K_m_u1, (key_type *)key_mouse_u1);

    appl_keypad_mode = (HAS_CAP(keypad_xmit) && HAS_CAP(keypad_local));
    if (!HAS_CAP(key_up)) appl_keypad_mode = 0;	/* no cursor keys */
    if (appl_keypad_mode) {
	/* Use of ks/ke isn't consistent, so we must guess what to do. */
	/* If termcap expects keys to send ESC [, don't switch */
	appl_keypad_mode = (key_up[0] != '\033' || key_up[1] != '[');
    }
    if ((mouse_usage == 2) || ((mouse_usage == 1) 
				    && !strncmp("xterm",term_name,5))) {
	mouse_state = 1;
	flow_control = 0;
    } else {
	mouse_state = 0;
    }
    visual_on();
}

void
home()
{
    BATCH_CHECK_V;

#ifdef TERM_DEBUG
    if (term_debug) fprintf(stderr, "home\n");
#endif

    putp(cursor_home);
    curxy_c = curxy_l = 0;
}

void
save_xy()
{
    savxy_c = curxy_c; savxy_l = curxy_l;
}

void
restore_xy()
{
    if (savxy_l < 0) return;
    gotoxy(savxy_c, savxy_l); fl;
}

void 
gotoxy(c, l)
int c, l;
{
    BATCH_CHECK_V;

#ifdef TERM_DEBUG
    if (term_debug)
	fprintf(stderr, "gotoxy %d %d -> %d %d\n", curxy_c, curxy_l, c, l);
#endif

    if (Columns <= (unsigned)c || Lines <= (unsigned)l)
	return;

    if (!(c | l))
	putp(cursor_home);
    else
        putp(tgoto(cursor_address, c, l));
    curxy_c = c; curxy_l = l;
}

void
clrdisp()
{
    int i;

    BATCH_CHECK_V;

#ifdef TERM_DEBUG
    if (term_debug) fprintf(stderr, "clrdisp\n");
#endif

    putpc(clear_screen, Lines);
    curxy_c = curxy_l = 0;
    savxy_l = -1;
    for (i = 0; i < Lines; i++) nonsp[i] = 0;
    msg_ptr = msg_stack;
}

void     clrline_noflush();

void
clrline()
{
    BATCH_CHECK_V;

    /* If we moved the cursor left to avoid weird effects, don't clear. */
    if (just_sent_cr)
	return;

    clrline_noflush();
    fl;
}

void
clrline_noflush()
{
    int oldxy_c, oldxy_l, spcnt;

    BATCH_CHECK_V;

#ifdef TERM_DEBUG
    if (term_debug)
	fprintf(stderr, "clrline %d %d [%d]",curxy_c, curxy_l, curxy_nonsp);
#endif

    if (curxy_l < 0)
	return;

    /* remainder of line already blank? */

    if (curxy_c >= nonsp[curxy_l]) {
#ifdef TERM_DEBUG
	if (term_debug) fprintf(stderr, " ignored\n");
#endif
        return;
    }

    if (HAS_CAP(clr_eol)) {
#ifdef TERM_DEBUG
        if (term_debug) fprintf(stderr, "\n");
#endif
        putp(clr_eol);
    } else {
	oldxy_c = curxy_c; oldxy_l = curxy_l;

	spcnt = nonsp[curxy_l] - curxy_c;

	/* guard against scroll */

	if (auto_right_margin && curxy_l == Lines-1 && curxy_c+spcnt == Columns)
	    spcnt--;

#ifdef TERM_DEBUG
	if (term_debug) fprintf(stderr," %d\n", spcnt);
#endif /* TERM_DEBUG */

#ifdef TERM_DEBUG
	if (term_debug && *term_debug) {
	    while (spcnt--) tputc(*term_debug);
  	    spcnt = 0;
	}
#endif
	/* clear out line */

	while (spcnt--) tputc(SP);

	if (curxy_c != oldxy_c || curxy_l != oldxy_l)
	    gotoxy(oldxy_c, oldxy_l);
    }
    nonsp[curxy_l] = curxy_c;
}

void
clrpage()
{
    int oldxy_c, oldxy_l, i;

    BATCH_CHECK_V;

#ifdef TERM_DEBUG
    if (term_debug)
	fprintf(stderr, "clrpage %d %d [%d]\n", curxy_c, curxy_l, curxy_nonsp);
#endif

    if (curxy_l < 0)
	return;

    oldxy_c = curxy_c; oldxy_l = curxy_l;

    /* code below only handles curxy_c == 0 */

    if (curxy_c != 0) {
	clrline();
	if (curxy_l < Lines-1) gotoxy(0, curxy_l+1);
    }

    /* clear to end of screen */

    if (curxy_c == 0) {
        if (HAS_CAP(clr_eos)) {
	    putpc(clr_eos, Lines - curxy_l);
	    for (i = curxy_l; i < Lines; i++) nonsp[i] = 0;
        } else if (curxy_l == 0) {
	    putpc(clear_screen, Lines);
	    for (i = 0; i < Lines; i++) nonsp[i] = 0;
        } else {
	    for (i = curxy_l; i < Lines; i++) {
		if (nonsp[i] != 0) {
		    gotoxy(0, i);
    	            clrline_noflush();
		}
	    }
	}
    }

    if (curxy_c != oldxy_c || curxy_l != oldxy_l)
	gotoxy(oldxy_c, oldxy_l);

    fl;
    msg_ptr = msg_stack;
}

static char buf[512];

/*VARARGS*/
void tprintf(va_alist)
va_dcl
{
    use_vararg;

    start_vararg;
    tvprintf(va_args1toN);
    end_vararg;
}

void tvprintf(va_tail)
va_tdcl
{
    char *fmt, *str;

    fmt = va_arg1(char *);
    vsprintf(buf, fmt, va_args2toN);

    for (str = buf; *str; str++)
        tputc(*str);
}

void tputc(c)
int c;
{
    int i;

#ifdef TERM_DEBUG
    if (term_debug) {
	fprintf (stderr, "tputc %d %d [%d] ", curxy_c, curxy_l, curxy_nonsp);
	if (c < ' ' || c > '~')
	else
	    fprintf(stderr, "'%c'", c);
    }
#endif

    just_sent_cr = 0;

    putchar(c);

    switch (c) {

    case '\n':
	curxy_c = 0;
	if (curxy_l >= 0) curxy_l++;
	break;

    case '\r':
	curxy_c = 0;
    	break;

    case '\t':
	curxy_c = DISCARD_REMAINDER(curxy_c + 8, 8);
 	break;

    case '\b':
	curxy_c--;
	break;

    case ' ':
	curxy_c++;
	if (curxy_l >= 0 && nonsp[curxy_l] == curxy_c)
	    nonsp[curxy_l]--;
	break;

    default:
	curxy_c++;
	if (curxy_l >= 0 && nonsp[curxy_l] < curxy_c)
	     nonsp[curxy_l] = curxy_c;
	break;
       
    }

#ifdef TERM_DEBUG
    if (term_debug)
	fprintf(stderr, " -> %d %d [%d]",curxy_c,curxy_l,curxy_nonsp);
#endif

    /* account for right margin */

    if (curxy_c == Columns) {
#ifdef TERM_DEBUG
	if (term_debug) fprintf(stderr, " margin");
#endif
	if (auto_right_margin) {
	    if (eat_newline_glitch) {
		putchar(CR);
                just_sent_cr = 1;
		curxy_c = 0;
	    } else {
		curxy_c = 0;
		if (curxy_l >= 0) curxy_l++;
	    }
	} else
	    curxy_c--;
    }

    /* account for vertical scroll */

    if (curxy_l == Lines) {
#ifdef TERM_DEBUG
	if (term_debug) fprintf(stderr, " scroll");
#endif
	for (i = 1; i < Lines; i++)
	    nonsp[i-1] = nonsp[i];
	nonsp[--curxy_l] = 0;
    }

#ifdef TERM_DEBUG
    if (term_debug) fprintf(stderr, "\n");
#endif
}

static char so_buf[512], *so_p;
static int so_c, so_l, so_b, so_active = 0;

int so_gotoxy(c, l, blank)
int c, l, blank;
{
    if (!STANDOUT && c >= 0) {
	if (c>= 0 && l >= 0) gotoxy(c, l);
	return 0;
    }

    so_active++;
    so_c = c;
    so_l = l;
    so_b = blank;
    so_p = so_buf;
    *so_p = NUL;

    return 1;	/* not really true if not standout & c < 0 */
}

/*VARARGS*/
void so_printf(va_alist)
va_dcl
{
    use_vararg;

    start_vararg;
    so_vprintf(va_args1toN);
    end_vararg;
}

void so_vprintf(va_tail)
va_tdcl
{
    char *fmt;

    if (!so_active) {
	if (ceol_standout_glitch) highlight(0); /* xxx why? */
	tvprintf(va_args1toN);
	return;
    }

    fmt = va_arg1(char *);
    vsprintf(so_p, fmt, va_args2toN);
    while (*so_p) so_p++;
}

void so_end()
{
    int len;

    if (!so_active) return;

    if (so_l >= 0) {

	len = so_p - so_buf + two_cookies;

	if (so_c < 0)
	    so_c = Columns - len - 2;
	if (so_c < 0) so_c = 0;

	if (len + so_c >= Columns) {
	    len = Columns - so_c - two_cookies;
	    so_buf[len] = NUL;
	}

	if (cookie_size) {
	    gotoxy(so_c + len - cookie_size, so_l);
	    standout(0);
	}

	gotoxy(so_c, so_l);

    }

    if ((so_b & 1) && (!STANDOUT || !cookie_size)) tputc(SP);

    if (STANDOUT) {
	if (ceol_standout_glitch) clrline();
	standout(1);
    }

    tprintf("%s", so_buf);

    if (STANDOUT) standout(0);

    if ((so_b & 2) && (!STANDOUT || !cookie_size)) tputc(SP);

    so_active = 0;
}


/*VARARGS*/
void so_printxy(va_alist)
va_dcl
{
    int c, l;
    use_vararg;

    start_vararg;

    c = va_arg1(int);
    l = va_arg2(int);

    so_gotoxy(c, l, 0);
    so_vprintf(va_args3toN);
    so_end();

    end_vararg;
}

    
int underline(on)
int on;
{
    if (cookie_size) return 0;
#ifdef TERM_DEBUG
    if (term_debug)
	fprintf(stderr, "underline %d %d [%d] %d", curxy_c, curxy_l, curxy_nonsp, on);
#endif
    if (! HAS_CAP(enter_underline_mode)) return 0;
    putp(on ? enter_underline_mode : exit_underline_mode);
    return 1;
}

int highlight(on)
int on;
{
    if (cookie_size) return 0;
#ifdef TERM_DEBUG
    if (term_debug)
	fprintf(stderr, "highlight %d %d [%d] %d", curxy_c, curxy_l, curxy_nonsp, on);
#endif
    if (! HAS_CAP(enter_standout_mode)) return 0;
    putp(on ? enter_standout_mode : exit_standout_mode);
    return 1;
}

int shadeline(on)
int on;
{
    if (cookie_size) return 0;
#ifdef TERM_DEBUG
    if (term_debug)
	fprintf(stderr, "shadeline %d %d [%d] %d", curxy_c, curxy_l, curxy_nonsp, on);
#endif
    if (shade_on_attr && shade_off_attr) {
	putp(on ? shade_on_attr : shade_off_attr);
	return 1;
    } else
	return underline(1);
}
    
int standout(on)
int on;
{
#ifdef TERM_DEBUG
    if (term_debug)
	fprintf(stderr, "standout %d %d [%d] %d", curxy_c, curxy_l, curxy_nonsp, on);
#endif
    if (! HAS_CAP(enter_standout_mode)) return 0;
    putp(on ? enter_standout_mode : exit_standout_mode);
    curxy_c += cookie_size;
    if (curxy_l >= 0 && curxy_c > nonsp[curxy_l])
	nonsp[curxy_l] = curxy_c;
#ifdef TERM_DEBUG
    if (term_debug)
	fprintf(stderr, " -> %d %d [%d]\n", curxy_c, curxy_l, curxy_nonsp);
#endif
    return 1;
}

static int is_visual = 0;
static int is_raw = 0;

#ifdef HAVE_TERMIO
#define RAW_MODE_ON    ioctl(0, TCSETAW, &raw_tty)
#define RAW_MODE_OFF   ioctl(0, TCSETAW, &norm_tty)
#else
#define RAW_MODE_ON    ioctl(0, TIOCSETP, &raw_tty)
#define RAW_MODE_OFF   ioctl(0, TIOCSETP, &norm_tty)
#endif	/* HAVE_TERMIO */

void
xterm_mouse_on()
{
	putp("\33[?1000h"); 
}

void
xterm_mouse_off()
{
	putp("\33[?1000l"); 
}

void
visual_on()
{
    BATCH_CHECK_V;

    if (terminal_speed == 0) return;

#ifdef TERM_DEBUG
    if (term_debug) fprintf(stderr, "visual_on\n");
#endif

    if (!is_visual) {
	if (HAS_CAP(enter_ca_mode)) putp(enter_ca_mode);
	if (appl_keypad_mode) putp(keypad_xmit);
	if (mouse_state)
	    xterm_mouse_on();
	fl;
    }
    is_visual = 1;
}

int visual_off()
{
    int was_raw = is_raw;

    if (terminal_speed == 0) return 0;

#ifdef TERM_DEBUG
    if (term_debug) fprintf(stderr, "visual_off\n");
#endif

    if (is_visual) {
	if (appl_keypad_mode) putp(keypad_local);
	if (HAS_CAP(exit_ca_mode)) putp(exit_ca_mode);
	if (mouse_state)
	    xterm_mouse_off();
	fl;
    }
    is_visual = 0;

    is_raw = 1;
    unset_raw();

    return was_raw;
}


#ifdef CBREAK
void
nn_raw()
{
    RAW_CHECK_V;

    if (is_raw == 1)
	return;
    is_raw = 1;
    RAW_MODE_ON;
}

int
no_raw()
{
    return 0;
}

int
unset_raw()
{
    if (is_raw == 0)
	return 0;
    RAW_CHECK;
    RAW_MODE_OFF;
    is_raw = 0;
    return 1;
}

#else /* not CBREAK */
static int must_set_raw = 1;

#undef raw
void
nn_raw()
{
    RAW_CHECK_V;

    if (!flow_control) {
	if (!must_set_raw) return;
	must_set_raw = 0;
    }

    if (is_raw) return;

    RAW_MODE_ON;
    is_raw++;
}

int no_raw()
{
    if (!flow_control) return 0;

    if (!is_raw) return 0;

    RAW_CHECK;

    RAW_MODE_OFF;

    is_raw = 0;

    return 1;
}

int unset_raw()
{
    int was_raw = is_raw;

    if (is_raw) {
	RAW_CHECK;
	RAW_MODE_OFF;
	is_raw = 0;
    }

    if (!flow_control)
	must_set_raw = 1;
    return was_raw;
}

#endif /* CBREAK */

#ifndef KEY_BURST
#define KEY_BURST 32
#endif	/* KEY_BURST */
#define RD_PUSHBACK 10
static char rd_buffer[KEY_BURST+RD_PUSHBACK];	/* Holds stuff from read */
static char *rd_ptr;
static int rd_count = 0, rd_alarm = 0;
#ifdef FAKE_INTERRUPT
static jmp_buf fake_alarm_sig;
#endif	/* FAKE_INTERRUPT */

static sig_type rd_timeout(n)
int n;
{
    rd_alarm = 1;
#ifdef FAKE_INTERRUPT
    longjmp(fake_alarm_sig, 1);
#endif	/* FAKE_INTERRUPT */
}

#define RD_TIMEOUT	0x1000
#define RD_INTERRUPT	0x1001

static int read_char_kbd(tmo)
int tmo;	/* timeout if no input arrives */
{
    if (rd_count <= 0) {
	if (tmo) {
#ifdef FAKE_INTERRUPT
	    if (setjmp(fake_alarm_sig)) goto tmout;
#endif	/* FAKE_INTERRUPT */
	    rd_alarm = 0;
	    signal(SIGALRM, rd_timeout);
	    micro_alarm(multi_key_guard_time);
	}
	rd_ptr = rd_buffer + RD_PUSHBACK;
	rd_count = read(0, rd_ptr, KEY_BURST);
	if (tmo) {
	    if (rd_alarm) goto tmout;
	    alarm(0);
	}
	if (rd_count < 0) {
	    if (errno != EINTR) s_hangup++;
	    return RD_INTERRUPT;
	}
    }
    --rd_count;
    return *rd_ptr++;
    
 tmout:
    rd_count = 0;
    return RD_TIMEOUT;
}

static void unread_char(c)
int c;
{
    if (rd_ptr == rd_buffer) return;
    rd_count++;
    *--rd_ptr = c;
}

void
flush_input()
{
#ifndef HAVE_TERMIO
#ifdef FREAD
    int arg;
#endif
#endif

    BATCH_CHECK_V;

#ifdef HAVE_TERMIO
    ioctl(0, TCFLSH, 0);
#else
#ifdef FREAD
    arg = FREAD;
    ioctl(0, TIOCFLUSH, &arg);
#else
    ioctl(0, TIOCFLUSH, 0);
#endif	/* FREAD */
#endif	/* HAVE_TERMIO */
    rd_count = 0;
}

int enable_stop = 1;

static int do_macro_processing = 1;
export int mouse_x,mouse_y = -1;
static int mouse_last = 0;
int get_c()
{
    key_type c, first_key;
    int key_cnt, mc, n;
    register struct multi_key *mk, *multi_match;
    register int i;

    multi_match = NULL;
    first_key = 0;
    mouse_y = -1;

 next_key:
    if (s_hangup) return K_interrupt;

    if (do_macro_processing)
	switch (m_getc(&mc)) {
	 case 0: break;
	 case 1: return mc;
	 case 2: return K_interrupt;
	}

#ifdef RESIZING
    if (s_resized) goto redraw;
#endif	/* RESIZING */

    if (batch_mode)
	nn_exitmsg(1, "Attempt to read keyboard input in batch mode");

    for (i = multi_keys, mk = multi_key_list; --i >= 0; mk++)
	mk->cur_key = mk->keys;
    key_cnt = 0;

#ifdef FAKE_INTERRUPT
    if (setjmp(fake_keyb_sig)) goto intr;
    arm_fake_keyb_sig = 1;
#endif	/* FAKE_INTERRUPT */

 multi:
    switch (n = read_char_kbd(key_cnt)) {

     case RD_INTERRUPT:
#ifdef CBREAK
	if (s_redraw) goto redraw;
#endif	/* CBREAK */
#ifdef RESIZING
	if (s_resized) goto redraw;
#endif	/* RESIZING */
	goto intr;

     case RD_TIMEOUT:
	while (--key_cnt > 0) unread_char(multi_match->keys[key_cnt]);
	c = first_key;
	goto got_char;
	
     default:
	c = (key_type)n;
	if (data_bits < 8) c &= 0x7f;
	if (ignore_xon_xoff)
	    if (c == CONTROL_('Q') || c == CONTROL_('S')) goto multi;
	break;
    }

    multi_match = NULL;
    for (i = multi_keys, mk = multi_key_list; --i >= 0; mk++) {
	if (mk->cur_key == NUL) continue;
	if (*(mk->cur_key)++ != c) {
	    mk->cur_key = NUL;
	    continue;
	}
	if (*(mk->cur_key) == NUL) {
	    c = mk->code;

            /* xterm mouse specific code, translate cursor position
	       into static variables, synthesize key up events */
	    if ((c >= K_m_d1) && (c <= K_m_u1)) {
		mouse_x = read_char_kbd(key_cnt) - '!';
		mouse_y = read_char_kbd(key_cnt) - '!';
		if (c == K_m_u1) {
		    if (mouse_last == K_m_d2) {
			c = K_m_u2;
		    } else if (mouse_last == K_m_d3) {
			c = K_m_u3;
		    }
		}
		mouse_last = c;
	    }
	    goto got_char;
	}
	multi_match = mk;
    }

    if (multi_match) {
	if (key_cnt == 0) first_key = c;
	key_cnt++;
	goto multi;
    }
    if (key_cnt) {
	if (key_cnt == 1 && first_key == 033) {
	    unread_char(c);
	    c = 033;
	    goto got_char;
	}
	ding();
	flush_input();
	goto next_key;
    }

 got_char:
#ifdef FAKE_INTERRUPT
    arm_fake_keyb_sig = 0;
#endif	/* FAKE_INTERRUPT */
    c = global_key_map[c];

#ifndef CBREAK
    if (c == IntrC) return K_interrupt;	/* don't flush */
    if (c == SuspC) {
	if (enable_stop && suspend_nn()) goto redraw;
	goto next_key;
    }
#endif	/* CBREAK */
    return (int)c;

 intr:
#ifdef FAKE_INTERRUPT
    arm_fake_keyb_sig = 0;
#endif	/* FAKE_INTERRUPT */
    rd_count = 0;
    return K_interrupt;

 redraw:
#ifdef RESIZING
    s_resized = 0;
#endif	/* RESIZING */
    s_redraw = 0;
    return GETC_COMMAND | K_REDRAW;
}


/*
 * read string with completion, pre-filling, and break on first char
 *
 *	dflt		is a string that will be use as default value if the
 *			space bar is hit as the first character.
 *
 *	prefill		pre-fill the buffer with .... and print it
 *
 *	break_chars	return immediately if one of these characters
 *			is entered as the first character.
 *
 *	completion 	is a function that will fill the buffer with a value
 *			see the group_completion and file_completion routines
 *			for examples.
 */

char *get_s(dflt, prefill, break_chars, completion)
char *dflt, *prefill, *break_chars;
fct_type completion;
{
    static key_type lbuf[GET_S_BUFFER];
    register char *cp;
    register int i, c, lastc;
    char *ret_val = (char *)lbuf;
    int comp_used, comp_len;
    int ostop, max, did_help;
    int hit_count;

    switch (m_gets((char *)lbuf)) {
     case 0:
	break;
     case 1:
	return (char *)lbuf;
     case 2:
	return NULL;
    }

    ostop = enable_stop;
    enable_stop = 0;
    do_macro_processing = 0;
    hit_count = 0;

    max = Columns - prompt_length;

    if (max >= FILENAME) max = FILENAME-1;

    i = comp_len = comp_used = did_help = 0;

    if (prefill && prefill[0]) {
	while ((c = *prefill++)) {
	    if (i == max) break;

	    tputc(c);
	    lbuf[i] = c;
	    i++;
	}
	fl;
    }

    if (dflt && *dflt == NUL)
	dflt = NULL;

    if (break_chars && *break_chars == NUL)
	break_chars = NULL;

    c = NUL;
    for(;;) {
	lastc = c;
	c = get_c();
	if (c & GETC_COMMAND) continue;

     kill_prefill_hack:

	hit_count++;

	if (i == 0) {
	    if (c == comp1_key && dflt) {
		while ((c = *dflt++) != NUL && i < max) {
		    tputc(c);
		    lbuf[i] = c;
		    i++;
		}
		fl;
		dflt = NULL;
		continue;
	    }
	    if ((cp = break_chars)) { /* Stupid ^@$# GCC!!! */
		while (*cp)
		    if (*cp++ == c) {
			lbuf[0] = c;
			lbuf[1] = NUL;
			goto out;
		    }
	    }
	}

	if (completion != NULL_FCT) {
	    if (comp_used && c == erase_key) {
		if (comp_len) {
		    i -= comp_len;
		    while (--comp_len >= 0) tputc(BS);
		    clrline();
		}
		if (!CALL(completion)(lbuf, -(i+1)) && did_help)
		    clrmsg(i);
		did_help = 0;
		comp_len = comp_used = 0;
		if (lastc == help_key) goto no_completion;
		continue;
	    }

	    if (c == comp1_key || c == comp2_key || c == help_key) {
		if (!comp_used || c == comp2_key ||
		    (c == help_key && lastc != c)) {
		    lbuf[i] = NUL;
		    if ((comp_used = CALL(completion)(lbuf, i)) == 0) {
			ding();
			continue;
		    }
		    if (comp_used < 0) {
			comp_used = 0;
			goto no_completion;
		    }
		    comp_len = 0;
		}
		if (c == help_key) {
		    if (CALL(completion)((char *)NULL, 1)) {
			gotoxy(prompt_length+i, prompt_line); fl;
			did_help = 1;
		    }
		    continue;
		}

		if (comp_len) {
		    i -= comp_len;
		    while (--comp_len >= 0) tputc(BS);
		    clrline();
		    comp_len = 0;
		}

		switch ( CALL(completion)((char *)NULL, 0) ) {

		 case 0:	/* no possible completion */
		    comp_used = 0;
		    ding();
		    continue;

		 case 2:	/* whole new alternative */
		    while (--i >= 0) tputc(BS);
		    clrline();

		    /*FALLTHRU*/
		 case 1:	/* completion */
		    comp_len = i;
		    while ((c = lbuf[i])) {
			if (i == max) break;
			tputc(c);
			i++;
		    }
		    fl;
		    comp_len = i - comp_len;
		    continue;
		}
	    }

	    if (comp_used) {
		if (!CALL(completion)(lbuf, -(i+1)) && did_help)
		    clrmsg(i);
		did_help = 0;
		comp_len = comp_used = 0;
	    }
	}

     no_completion:

	if (c == CR || c == NL) {
	    lbuf[i] = NUL;
	    break;
	}

	if (c == erase_key) {
	    if (i <= 0) continue;
	    i--;
	    tputc(BS);
	    tputc(' ');
	    tputc(BS);
	    fl;
	    continue;
	}

	if (c == delword_key) {
	    if (i <= 0) continue;
	    lbuf[i-1] = 'X';
	    while (i > 0 && isalnum(lbuf[i-1])) { tputc(BS); i--; }
	    clrline();
	    continue;
	}

	if (c == kill_key) {
	    while (i > 0) { tputc(BS); i--; }
	    clrline();
	    if (hit_count == 1 && dflt) {
		c = comp1_key;
		goto kill_prefill_hack;
	    }
	    continue;
	}

	if (c == K_interrupt) {
	    ret_val = NULL;
	    break;
	}

	if (data_bits == 8) {
	    if (!iso8859(c)) continue;
	} else
	if (!isascii(c) || !isprint(c)) continue;

	if (i == max) continue;

	if (i > 0 && lbuf[i-1] == '/' && (c == '/' || c == '+')) {
	    if (c != '/' || !guard_double_slash || (i > 1 && lbuf[i-2] == '/')) {
		if (completion == file_completion) {
		    while (i > 0) { tputc(BS); i--; }
		    clrline();
		}
	    }
	}

	tputc(c);
	fl;

	lbuf[i] = c;
	i++;
    }
 out:
    enable_stop = ostop;
    do_macro_processing = 1;
    return ret_val;
}

export int list_offset = 0;

int list_completion(str)
char *str;
{
    static int cols, line;

    if (str == NULL) {
	cols = Columns;
	line = prompt_line + 1;
	if (line == Lines - 1) cols--;

	gotoxy(0, line);
	clrpage();
	return 1;
    }

    str += list_offset;

    for (;;) {
	cols -= strlen(str);
	if (cols >= 0) {
	    tprintf("%s%s", str, cols > 0 ? " " : "");
	    cols--;
	    return 1;
	}
	if (line >= Lines - 1) return 0;
	line++;
	cols = Columns;
	gotoxy(0, line);
	if (line == Lines - 1) cols--;
    }
}

int yes(must_answer)
int must_answer;
{
    int c, help = 1, in_macro = 0;

    switch (m_yes()) {
     case 0:
	break;
     case 1:
	return 0;
     case 2:
	return 1;
     case 3:
	do_macro_processing = 0;
    	in_macro++;
	break;
    }
    fl;

    for (;;) {
	if (!is_raw) {
	    nn_raw();
	    c = get_c();
	    unset_raw();
	} else
	    c = get_c();

	if (c == 'y' || c == 'Y') {
	    c = 1;
	    break;
	}

	if (must_answer == 0 && (c == SP || c == CR || c == NL)) {
	    c = 1;
	    break;
	}

	if (c == 'n' || c == 'N') {
	    c = 0;
	    break;
	}
	if (c == K_interrupt) {
	    c = -1;
	    break;
	}
	if (help) {
	    tprintf(" y=YES n=NO"); fl;
	    prompt_length += 11;
	    help = 0;
	}
    }

    if (in_macro) {
	if (c < 0) m_break();
	do_macro_processing = 1;
    }

    return c;
}

void 
ding()
{
    BATCH_CHECK_V;

    putp(bell_str);
    fl;
}


void display_file(name, modes)
char *name;
int modes;
{
    FILE *f;
    register c, stand_on;
    int linecnt, headln_cnt, hdline, no_conf;
    char headline[128];

    headline[0] = 0;
    hdline = 0;
    no_conf = 0;

    headln_cnt = -1;

    if (modes & CLEAR_DISPLAY) {
	gotoxy(0,0);
	clrdisp();
    }

    linecnt = Lines - 1;

chain:

    if (*name != '/') name = relative(help_directory, name);
    f = open_file(name, OPEN_READ);
    if (f == NULL)
	tprintf("\r\n\nFile %s is not available\n\n", name);
    else {
	stand_on = 0;

	while ((c = getc(f)) != EOF) {
#ifdef HAVE_JOBCONTROL
	    if (s_redraw) {
		no_conf = 1;
		break;
	    }
#endif	/* HAVE_JOBCONTROL */
	    no_conf = 0;
	    if (c == '\1') {
		if (STANDOUT) {
		    putp(stand_on ? exit_standout_mode : enter_standout_mode);
		    curxy_c += cookie_size;
		    stand_on = !stand_on;
		}
		continue;
	    }
	    if (c == '\2') {
		headln_cnt = 0;
		continue;
	    }
	    if (c == '\3') {
		headln_cnt = 0;
		while ((c = getc(f)) != EOF && c != NL)
		    headline[headln_cnt++] = c;
		headline[headln_cnt++] = NUL;
		name = headline;
		fclose(f);
		goto chain;
	    }
	    if (c == '\4') {
		tprintf("%s", version_id);
		continue;
	    }

	    if (headln_cnt >= 0)
		headline[headln_cnt++] = c;

	    if (hdline) {
		tprintf("%s\r", headline);
		hdline = 0;
		linecnt--;
	    }

	    tputc(c);
	    if (c == NL) {
		tputc(CR);
		if (headln_cnt >= 0) {
		    headline[--headln_cnt] = 0;
		    headln_cnt = -1;
		}
		if (--linecnt == 0) {
		    no_conf = 1;
		    if (any_key(0) == K_interrupt)
			break;
		    linecnt = Lines - 1;
		    if (modes & CLEAR_DISPLAY) {
			gotoxy(0,0);
			clrdisp();
		    }
		    hdline = headline[0];
		}
	    }
	}

	if (stand_on) {
	    putp(exit_standout_mode);
	    curxy_c += cookie_size;
	}
	fclose(f);
    }

    prompt_line = Lines-1;	/* move prompt to last line */

    if (!no_conf && (modes & CONFIRMATION))
	any_key(prompt_line);
}


/*VARARGS*/
void nn_exitmsg(va_alist)
va_dcl
{
    char *fmt;
    int n;
    use_vararg;

    if (terminal_speed != 0) {
	clrdisp();
	visual_off();
    }

    start_vararg;
    n = va_arg1(int);
    fmt = va_arg2(char *);
    vprintf(fmt, va_args3toN);
    putchar(NL);
    end_vararg;

    nn_exit(n);
    /*NOTREACHED*/
}

/*VARARGS*/
void msg(va_alist)
va_dcl
{
    use_vararg;

    start_vararg;
    vmsg(va_args1toN);
    end_vararg;
}

void push_msg(str)
char *str;
{
    register struct msg_list *mp, *newmsg;
    static int slots = 0;
    
    if (str != NULL) {
	if (slots > message_history) {
	    for (mp = newmsg = msg_stack; mp->prev != NULL; mp = mp->prev)
		newmsg = mp;
	    if (newmsg == mp)
		msg_stack = NULL;
	    else {
		newmsg->prev = NULL;
		newmsg = mp;
	    }
	    freeobj(newmsg->buf);
	} else {
	    slots++;
	    newmsg = newobj(struct msg_list, 1);
	}
	newmsg->buf = copy_str(str);
	newmsg->prev = msg_stack;
	msg_stack = newmsg;
    }
    msg_ptr = msg_stack;
}
    
void vmsg(va_tail)
va_tdcl
{
    char *errmsg, *fmt;
    
    fmt = va_arg1(char *);

    if (fmt) {
	char lbuf[512];
	
	vsprintf(lbuf, fmt, va_args2toN);
	push_msg(lbuf);
    }
    
    if (msg_ptr) {
	errmsg = msg_ptr->buf;
	msg_ptr = msg_ptr->prev;
    } else {
	errmsg = "(no more messages)";
	msg_ptr = msg_stack;
    }

    if (terminal_speed == 0) {
	tprintf("%s\n", errmsg);
	fl;
	return;
    }
    
    gotoxy(0, Lines-1);
    tprintf("%s", errmsg);
    clrline();
    any_message = 1;

    if (prompt_line != Lines-1)
        gotoxy(prompt_length, prompt_line);
    fl;
}

void
clrmsg(col)
int col;
{
    BATCH_CHECK_V;

    gotoxy(0, prompt_line + 1);
    clrpage();
    if (col >= 0)
	gotoxy(prompt_length + col, prompt_line);
    fl;
    any_message = 0;
}


/*VARARGS*/
void
prompt(va_alist)
va_dcl
{
    register char *cp;
    int stand_on;
    char *fmt;
    static char cur_p[FILENAME];
    static char saved_p[FILENAME];
    use_vararg;

    BATCH_CHECK_V;

    start_vararg;

    fmt = va_arg1(char *);

    if (fmt == P_VERSION) {
	gotoxy(0, prompt_line + 1);
	tprintf("Release %s ", version_id);
	clrline();
	any_message++;

	if (prompt_line >= 0)
	    gotoxy(prompt_length, prompt_line);
	goto out;
    }

    if (fmt == P_SAVE) {
	strcpy(saved_p, cur_p);
	goto out;
    }

    if (fmt == P_RESTORE)
	strcpy(cur_p, saved_p);

    if (prompt_line >= 0)
	gotoxy(0, prompt_line);

    if (fmt == P_MOVE) {
	clrline();
	goto out;
    }

    if (fmt != P_REDRAW && fmt != P_RESTORE)
	vsprintf(cur_p, fmt, va_args2toN);

    tputc(CR);

    for (cp = cur_p, stand_on = 0, prompt_length = 0; *cp; cp++) {
	if (*cp == '\1') {
	    if (cp[1] != '\1') {
		if (STANDOUT) {
		    stand_on = !stand_on;
		    standout(stand_on);
		    prompt_length += cookie_size;
		}
		continue;
	    }
	    cp++;
	} else
	if (*cp == '\2') {
	    time_t t;
	    char   *timestr;

	    t = tick_usage();

	    if (show_current_time) {
		timestr = ctime(&t) + 11;
		timestr[5] = NUL;

		tprintf("-- %s ", timestr);
		prompt_length += 9;
	    }

	    if (unread_mail(t)) {
		tprintf("Mail ");
		prompt_length += 5;
	    }

	    continue;
	}

	tputc(*cp);
	prompt_length ++;
    }
    if (stand_on) {
	standout(0);
	prompt_length += cookie_size;
    }

    clrline();

    if (fmt == P_RESTORE)
	restore_xy();
#if notdef
    else
	curxy_c = -1;
#endif

 out:
    end_vararg;
}


int any_key(line)
int line;
{
    int was_raw, c, dmp;

    BATCH_CHECK;

    was_raw = is_raw;
    if (!is_raw) nn_raw();
    if (line == 0)
	line = -1;
    else
	if (line < 0)
	    line = Lines + line;

    if (line != 10000)
	so_printxy(0, line, "Hit any key to continue");

    clrline();

    dmp = do_macro_processing;
    do_macro_processing = 0;
    c = get_c();
    if (c == 'q' || c == 'Q') c = K_interrupt;
    do_macro_processing = dmp;

    if (!was_raw) unset_raw();

    return c;
}


static pg_fline, pg_width, pg_maxw, pg_line, pg_col, pg_quit;
export regexp *pg_regexp = NULL;
export int pg_new_regexp = 0;

void pg_init(first_line, cols)
int first_line, cols;
{
    if (pg_regexp) {
	freeobj(pg_regexp);
	pg_regexp = NULL;
    }
    pg_new_regexp = 0;

    pg_fline = first_line;
    pg_line = pg_fline - 1;
    pg_quit = pg_col = 0;
    pg_width = Columns / cols;
    pg_maxw = pg_width * (cols - 1);
}

int pg_scroll(n)
int n;
{
    pg_line += n;
    if (pg_line >= (Lines - 1)) {
	pg_line = 0;
	if (any_key(0) == K_interrupt)
	    return 1;
	tputc(CR);
	clrline();
    }
    return 0;
}

int pg_next()
{
    int c;

    if (batch_mode) {
	putchar(NL);
	return 0;
    }

    pg_line++;
    if (pg_line < Lines) {
	gotoxy(pg_col, pg_line);
	if (pg_line == Lines - 1 && pg_col == pg_maxw) {
	    c = any_key(0);
	    if (c == '/') {
		char *expr;
		tputc(CR);
		tputc('/');
		clrline();
		expr = get_s((char *)NULL, (char *)NULL, (char *)NULL, NULL_FCT);
		if (expr != NULL && *expr != NUL) {
		    freeobj(pg_regexp);
		    pg_regexp = regcomp(expr);
		    pg_new_regexp = 1;
		}
	    }
	    gotoxy(0, pg_fline);
	    clrpage();
	    pg_col = 0;
	    pg_line = pg_fline;
	    if (c == K_interrupt) {
		pg_quit = 1;
		return -1;
	    }
	    return 1;
	}
    } else {
	pg_line = pg_fline;
	pg_col += pg_width;
	gotoxy(pg_col, pg_line);
    }
    return 0;
}
void
pg_indent(pos)
int pos;
{
    BATCH_CHECK_V;

    gotoxy(pg_col + pos, pg_line);
}

int pg_end()
{
    int c;

    if (pg_quit == 0 && pg_next() == 0)
	c = any_key(0);
    else
	c = K_interrupt;

    if (pg_regexp) {
	freeobj(pg_regexp);
	pg_regexp = NULL;
    }

    return c == K_interrupt ? -1 : 0;
}

void
user_delay(ticks)
int ticks;
{
    BATCH_CHECK_V;

    if (ticks <= 0 || conf_dont_sleep) {
	tprintf(" <>");
	any_key(10000);
    } else {
	fl;
	sleep((unsigned)ticks);
    }
}

