/*
 *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
 *
 *	Terminal interface.
 */

#include <signal.h>
#include <errno.h>
#include "config.h"
#include "term.h"
#include "keymap.h"
#include "regexp.h"

#ifdef RESIZING
#include <sys/ioctl.h>			/* for TIOCGWINSZ */
#ifdef SYSV_RESIZING
#include <sys/stream.h>
#include <sys/ptem.h>
#endif

extern int s_resized;
#endif

import int data_bits;
import int batch_mode;

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 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

#include <curses.h>
#ifndef auto_left_margin
#include <term.h>
#endif

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

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

#else

#define USE_TERMCAP

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! */

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

#define HAS_CAP(str)	(*str)

static outc(c)
{
    putchar(c);
}

#endif	/* USE_TERMCAP */

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 */

#ifdef FAKE_INTERRUPT
#include <setjmp.h>

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

#ifdef HAVE_TERMIO

#define KEY_BURST 50	/* read bursts of 50 chars (or timeout after 100 ms) */

#ifdef USE_TERMCAP
#include <termio.h>
#endif

#undef CBREAK

static struct termio norm_tty, raw_tty;

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

#else	/* V7/BSD TTY DRIVER */

#include <sgtty.h>

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

#define	IntrC	norm_chars.t_intrc
#define	EraseC	norm_tty.sg_erase
#define KillC	norm_tty.sg_kill

#ifdef TIOCGLTC
static struct 	ltchars spec_chars;
#define SuspC	spec_chars.t_suspc
#else
#define	SuspC	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))
	user_error("TERMCAP entry for %s has no '%s' capability",
		   term_name, cap);
}

#endif  /* USE_TERMCAP */

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

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
#endif
}

static int multi_keys = 0;

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

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;
}

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;
	printf("%d\t%s\t", i, key_name(multi_key_list[i].code));
	for (cp = multi_key_list[i].keys; *cp; cp++) {
	    putchar(SP);
	    fputs(key_name(*cp), stdout);
	}
    }

    pg_end();
}


#ifdef RESIZING

sig_type catch_winch(n)
{
    struct winsize winsize;

    (void) signal(SIGWINCH, catch_winch);
    if (ioctl(0, TIOCGWINSZ, &winsize) >= 0
	&& (winsize.ws_row != Lines || winsize.ws_col != Columns)) {
	Lines = winsize.ws_row;
	Columns = winsize.ws_col;
	s_redraw = 1;
	s_resized = 1;
    }
#ifdef FAKE_INTERRUPT
    if (fake_keyb_siglist[n] && arm_fake_keyb_sig)
	longjmp(fake_keyb_sig, 1);
#endif
}
#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
#endif

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

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

static 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 raw_not_ok_error()
{
    if (batch_mode) return 0;
    user_error("Not prepared for terminal i/o");
}

#define	RAW_CHECK	if (terminal_speed == 0) return raw_not_ok_error()
#define BATCH_CHECK	if (terminal_speed == 0) return 0

init_term(full)
int full;
{
#ifdef USE_TERMCAP
    char tbuf[1024];
#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)
	    user_error("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

#ifdef USE_TERMINFO
    setupterm((char *)NULL, 1, (int *)NULL);
    Columns = columns;
    Lines = lines;
    cookie_size = magic_cookie_glitch;
    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)
	user_error("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);
    get_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);

    cookie_size = tgetnum("sg");

    ceol_standout_glitch = tgetflag("xs");

    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 */

    STANDOUT = HAS_CAP(enter_standout_mode);
    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[VEOF] = KEY_BURST;
    raw_tty.c_cc[VEOL] = ((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

    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
    raw_tty.sg_flags |= CBREAK;
#else
    raw_tty.sg_flags |= RAW;
#endif

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

    erase_key = (key_type)EraseC;
    kill_key  = (key_type)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);

    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] != '[');
    }

    visual_on();
}

home()
{
    BATCH_CHECK;

    putp(cursor_home);
}

static int curxy_c = -1, curxy_l, savxy_c = -1, savxy_l;

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

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

gotoxy(c, l)
int c, l;
{
    BATCH_CHECK;

    curxy_c = c; curxy_l = l;
    putp(tgoto(cursor_address, c, l));
}

clrdisp()
{
    BATCH_CHECK;

#ifdef USE_TERMINFO
    putp(clear_screen);		/* tputs is broken on UNISYS I've been told */
#else
    tputs(clear_screen, Lines, outc);
#endif
    curxy_c = savxy_c = -1;
    msg_ptr = msg_stack;
}

clrline()
{
    BATCH_CHECK;

    putp(clr_eol);
    fl;
}

clrline_noflush()
{
    BATCH_CHECK;

    putp(clr_eol);
}

clrpage(lineno)
register int lineno;
{
    register int olineno= lineno;

    BATCH_CHECK;

    if (HAS_CAP(clr_eos)) {
#ifdef USE_TERMINFO
	putp(clr_eos);
#else
	tputs(clr_eos, Lines - lineno, outc);
#endif
    } else {
	clrline();
	lineno++;
	for (; lineno < Lines; lineno++) {
		gotoxy(0, lineno);
		putp(clr_eol);
	}
	gotoxy(0, olineno);
	fl;
    }
    msg_ptr = msg_stack;
}

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

so_gotoxy(c, l, blank)
{
    if (!STANDOUT && c >= 0) {
	if (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*/
so_printf(va_alist)
va_dcl
{
    use_vararg;

    start_vararg;
    so_vprintf(va_args1toN);
    end_vararg;
}

so_vprintf(va_tail)
va_tdcl
{
    char *fmt;

    fmt = va_arg1(char *);

    if (!so_active) {
	if (ceol_standout_glitch) highlight(0);
	vprintf(fmt, va_args2toN);
	return;
    }

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

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);
	    putp(exit_standout_mode);
	}

	gotoxy(so_c, so_l);

    }

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

    if (STANDOUT) {
	if (ceol_standout_glitch) clrline();
	putp(enter_standout_mode);
    }

    fputs(so_buf, stdout);

    if (STANDOUT) putp(exit_standout_mode);

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

    so_active = 0;
}


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

    start_vararg;

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

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

    end_vararg;
}

underline(on)
{
    if (cookie_size) return 0;
    if (! HAS_CAP(enter_underline_mode)) return 0;
    putp(on ? enter_underline_mode : exit_underline_mode);
    return 1;
}

highlight(on)
{
    if (cookie_size) return 0;
    if (! HAS_CAP(enter_standout_mode)) return 0;
    putp(on ? enter_standout_mode : exit_standout_mode);
    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

visual_on()
{
    BATCH_CHECK;

    if (HAS_CAP(enter_ca_mode)) {
	putp(enter_ca_mode);
	is_visual = 1;
    }
    if (appl_keypad_mode) {
	putp(keypad_xmit);
	is_visual = 1;
    }
}

visual_off()
{
    int was_raw = is_raw;

    if (terminal_speed == 0) return 0;

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

    is_raw = 1;
    unset_raw();

    return was_raw;
}

#ifdef CBREAK
raw()
{
    RAW_CHECK;

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

no_raw()
{
    return 0;
}

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;

raw()
{
    RAW_CHECK;

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

    if (is_raw) return;

    RAW_MODE_ON;

    is_raw++;
}

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

    if (!is_raw) return 0;

    RAW_CHECK;

    RAW_MODE_OFF;

    is_raw = 0;

    return 1;
}

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
#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

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

#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
	    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 unread_char(c)
char c;
{
    if (rd_ptr == rd_buffer) return;
    rd_count++;
    *--rd_ptr = c;
}

flush_input()
{
    int arg;
    
    BATCH_CHECK;

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

int enable_stop = 1;

static int do_macro_processing = 1;

get_c()
{
    key_type c, first_key;
    int key_cnt, mc, n;
    register struct multi_key *mk, *multi_match;
    register int i;

 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

    if (batch_mode)
	user_error("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

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

     case RD_INTERRUPT:
#ifdef CBREAK
	if (s_redraw) goto redraw;
#endif
#ifdef RESIZING
	if (s_resized) goto redraw;
#endif
	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;
	    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
    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
    return (int)c;

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

 redraw:
#ifdef RESIZING
    s_resized = 0;
#endif
    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 buf[GET_S_BUFFER];
    register char *cp;
    register int i, c, lastc;
    char *ret_val = (char *)buf;
    int comp_used, comp_len;
    int ostop, max, did_help;
    int hit_count;

    switch (m_gets(buf)) {
     case 0:
	break;
     case 1:
	return (char *)buf;
     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;

	    putchar(c);
	    buf[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) {
		    putchar(c);
		    buf[i] = c;
		    i++;
		}
		fl;
		dflt = NULL;
		continue;
	    }
	    if (cp = break_chars) {
		while (*cp)
		    if (*cp++ == c) {
			buf[0] = c;
			buf[1] = NUL;
			goto out;
		    }
	    }
	}

	if (completion != NULL_FCT) {
	    if (comp_used && c == erase_key) {
		if (comp_len) {
		    i -= comp_len;
		    while (--comp_len >= 0) putchar(BS);
		    clrline();
		}
		if (!CALL(completion)(buf, -(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)) {
		    buf[i] = NUL;
		    if ((comp_used = CALL(completion)(buf, 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) putchar(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) putchar(BS);
		    clrline();
		    /* fall thru */

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

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

     no_completion:

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

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

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

	if (c == kill_key) {
	    while (i > 0) { putchar(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 && buf[i-1] == '/' && (c == '/' || c == '+')) {
	    if (c != '/' || !guard_double_slash || (i > 1 && buf[i-2] == '/')) {
		extern int file_completion();

		if (completion == file_completion) {
		    while (i > 0) { putchar(BS); i--; }
		    clrline();
		}
	    }
	}

	putchar(c);
	fl;

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

export int list_offset = 0;

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(line);
	return 1;
    }

    str += list_offset;

    for (;;) {
	cols -= strlen(str);
	if (cols >= 0) {
	    printf("%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--;
    }
}

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) {
	    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) {
	    fputs(" y=YES n=NO", stdout); fl;
	    prompt_length += 11;
	    help = 0;
	}
    }

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

    return c;
}


ding()
{
    BATCH_CHECK;

    putp(bell_str);
    fl;
}


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

    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)
	printf("\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
	    no_conf = 0;
	    if (c == '\1') {
		if (STANDOUT) {
		    putp(stand_on ? exit_standout_mode : enter_standout_mode);
		    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') {
		printf("%s", version_id);
		continue;
	    }

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

	    if (hdline) {
		puts(headline);
		putchar(CR);
		hdline = 0;
		linecnt--;
	    }

	    putchar(c);
	    if (c == NL) {
		putchar(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);
	fclose(f);
    }

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

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


/*VARARGS*/
user_error(va_alist)
va_dcl
{
    char *fmt;
    use_vararg;

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

    start_vararg;
    fmt = va_arg1(char *);

    vprintf(fmt, va_args2toN);
    putchar(NL);

    end_vararg;
    nn_exit(1);
}

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

    start_vararg;
    vmsg(va_args1toN);
    end_vararg;
}

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;
}
    
vmsg(va_tail)
va_tdcl
{
    char *errmsg, *fmt;
    
    fmt = va_arg1(char *);

    if (fmt) {
	char buf[512];
	
	vsprintf(buf, fmt, va_args2toN);
	push_msg(buf);
    }
    
    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) {
	printf("%s\n", errmsg);
	fl;
	return;
    }
    
    gotoxy(0, Lines-1);
    fputs(errmsg, stdout);
    clrline();
    any_message = 1;

    gotoxy(prompt_length, prompt_line);
    fl;
}

clrmsg(col)
int col;
{
    BATCH_CHECK;

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


/*VARARGS*/
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;

    start_vararg;

    fmt = va_arg1(char *);

    if (fmt == P_VERSION) {
	gotoxy(0, prompt_line + 1);
	printf("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);

    putchar(CR);

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

	    t = tick_usage();

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

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

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

	    continue;
	}

	putchar(*cp);
	prompt_length ++;
    }
    if (stand_on) {
	putp(exit_standout_mode);
	prompt_length += cookie_size;
    }

    clrline();

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

 out:
    end_vararg;
}


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

    BATCH_CHECK;

    was_raw = is_raw;
    if (!is_raw) 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;

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);
}

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

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;
		putchar(CR);
		putchar('/');
		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_fline);
	    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;
}

pg_indent(pos)
int pos;
{
    BATCH_CHECK;

    gotoxy(pg_col + pos, pg_line);
}

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;
}


user_delay(ticks)
int ticks;
{
    BATCH_CHECK;

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

