/* An ircII-like split-screen front end
   Copyright (C) 1995 Roger Espel Llima

   Started: 17 Feb 95 by orabidoo <roger.espel.llima@ens.fr>
   Latest modification: 7 June 97

   To compile: gcc ssfe.c -o ssfe -ltermcap

   If it doesn't work, try gcc ssfe.c -o ssfe -lcurses
   or try cc, acc or c89 instead of gcc, or -lncurses.

   Use: ssfe [options] program arguments

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation. See the file LICENSE for details.
*/

#include <sys/time.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

#ifdef USE_SGTTY
#include <sgtty.h>
#else
#include <termios.h>
#endif

#include <sys/ioctl.h>

#ifdef _AIX
#include <sys/select.h>
#endif

#define BUF_SIZE 512
#define MAX_COLS 512

unsigned char *statusline;
int ystatus, yinput;     /* line number of the status line, input line */

int ttyfd;
#ifdef TIOCGWINSZ
struct winsize wsz;
#endif

#ifdef USE_SGTTY
struct sgttyb term, term0;
struct tchars tch, tch0;
struct ltchars lch, lch0;
#else
struct termios term, term0;
#endif

int pid, mypid;
int i;
int cols, lines;
int readfd, writefd, errfd;

unsigned char *t, *w;
unsigned char tmpstr[BUF_SIZE], extrainput[BUF_SIZE+20], readbuf[2*BUF_SIZE],
	      *input, *writebuf, o_buffer[BUF_SIZE];
int bold=0, inv=0, under=0, wherex=0, wherey=0, donl=0;
int hold_mode=0, hold_lines=0, ctrlx=0, beep=0, flow=0;

unsigned char defprompt[]="> ",
	 nullstring[]="",
	 *prompt;
int plen=0, specialprompt=0, modified=1, no_echo=0;

#define MAX_TAB_LINES 20
struct tabinfo {
  unsigned char string[BUF_SIZE];
  struct tabinfo *prev, *next;
};
int tablines=0;
struct tabinfo *curtabt=NULL, *curtabr=NULL, *oldest=NULL;

#define MAX_HIST_LINES 50
struct histinfo {
  unsigned char string[BUF_SIZE+20];
  int len, plen;
  struct histinfo *prev, *next;
};
int histlines=0;
struct histinfo *histcurrent=NULL, *histoldest=NULL;

char ctrl_t[128] = "/next\n";

unsigned char id[]="`#ssfe#", *inid=id, protcmd[BUF_SIZE], *wpc=protcmd;
int idstatus=0;  /* 0 looking for/in the word, 1 in the arguments */
#define ID_BACK "@ssfe@"

int rc, rrc, inputcursor, inputlast, inputofs, inarrow=0, quote=0;
int cursorwhere;     /* 0 = up, 1 = down, 2 = undef */
int dispmode=1; /* 0=raw, 1=wordwrap, 2=process ^b^v^_ */
int printmode=0;
int cutline=0;

char *termtype, termcap[1024], *tc, capabilities[2048];
char *t_cm, *t_cl, *t_mr, *t_md, *t_me, *t_cs, *t_ce, *t_us;
int ansi_cs = 0;

fd_set ready, result;

#ifdef __GNUC__
extern unsigned char *tgoto(unsigned char *cm, int col, int line);
#else
extern unsigned char *tgoto();
#endif

#ifdef __GNUC__
int myputchar(int c) {
#else
int myputchar(c) {
#endif
  unsigned char cc=(unsigned char)c;
  return(write(1, &cc, 1));
}

#ifdef __GNUC__
int addchar(int c) {
#else
int addchar(c) {
#endif
  (*w++)=(unsigned char)c;
}

#ifdef __GNUC__
void putcap(unsigned char *s) {
#else
void putcap(s)
unsigned char *s; {
#endif
  tputs(s, 0, myputchar);
}

#ifdef __GNUC__
int do_cs(int y1, int y2) {
#else
int do_cs(y1, y2) {
#endif
  static char temp[16];
  if (ansi_cs) {
    sprintf(temp, "\e[%d;%dr", y1, y2);
    write(1, temp, strlen(temp));
  } else putcap((char *)tgoto(t_cs, y2-1, y1-1));
}

#ifdef __GNUC__
void writecap(unsigned char *s) {
#else
void writecap(s)
unsigned char *s; {
#endif
  tputs(s, 0, addchar);
}

#ifdef __GNUC__
void gotoxy(int x, int y) {
#else
void gotoxy(x, y) {
#endif
/* left upper = 0, 0 */
  putcap(tgoto(t_cm, x, y));
}

#define clearscreen() (putcap(t_cl))
#define cleareol() (putcap(t_ce))
#define fullscroll() (do_cs(0, 0))
#define winscroll() (do_cs(1, lines-2))
#define setbold() (putcap(t_md))
#define setunder() (putcap(t_us))
#define setinv() (putcap(t_mr))
#define normal() (putcap(t_me))

#ifdef __GNUC__
void ofsredisplay(int x);
void inschar(unsigned char t);
void dokbdchar(unsigned char t);
#else
void ofsredisplay();
void inschar();
void dokbdchar();
#endif
void displaystatus();

#ifdef __GNUC__
void cleanupexit(int n, unsigned char *error) {
#else
void cleanupexit(n, error)
int n;
unsigned char *error; {
#endif
  normal();
  fullscroll();
  gotoxy(0, lines-1);
  cleareol();
#ifdef USE_SGTTY
  ioctl(ttyfd, TIOCSETP, &term0);
  ioctl(ttyfd, TIOCSETC, &tch0);
  ioctl(ttyfd, TIOCSLTC, &lch0);
#else
  tcsetattr(ttyfd, TCSADRAIN, &term0);
#endif
  close(ttyfd);
  if (error!=NULL)
    fprintf(stderr, "%s\n", error);
  exit(n);
}

void allsigs();

void interrupted() {
  cleanupexit(1, "interrupted");
}

void sigpipe() {
  cleanupexit(1, "program died");
}

void sigcont() {
  allsigs();
#ifdef USE_SGTTY
  ioctl(ttyfd, TIOCSETP, &term);
  ioctl(ttyfd, TIOCSETC, &tch);
  ioctl(ttyfd, TIOCSLTC, &lch);
#else
  tcsetattr(ttyfd, TCSANOW, &term);
#endif
  wherex=0;
  wherey=ystatus-1;
  displaystatus();
  ofsredisplay(0);
}

void suspend() {
  normal();
  fullscroll();
  gotoxy(0, ystatus);
  cleareol();
#ifdef USE_SGTTY
  ioctl(ttyfd, TIOCSETP, &term0);
  ioctl(ttyfd, TIOCSETC, &tch0);
  ioctl(ttyfd, TIOCSLTC, &lch0);
#else
  tcsetattr(ttyfd, TCSANOW, &term0);
#endif
  kill(pid, SIGCONT);
  signal(SIGTSTP, SIG_DFL);
  signal(SIGCONT, sigcont);
  kill(mypid, SIGTSTP);
}

void sigwinch() {
#ifdef TIOCGWINSZ
  signal(SIGWINCH, sigwinch);
  if (ioctl(ttyfd, TIOCGWINSZ, &wsz)>=0 && wsz.ws_row>0 && wsz.ws_col>0) {
    lines=wsz.ws_row;
    cols=wsz.ws_col;
    cursorwhere=2;
    ystatus=lines-2;
    yinput=lines-1;
    wherex=0;
    wherey=ystatus-1;
    displaystatus();
    if (inputlast>cols-8) {
      inputcursor=cols-9;
      inputofs=inputlast-cols+9;
    } else {
      inputofs=0;
      inputcursor=inputlast;
    }
    ofsredisplay(0);
  }
#endif
}

void allsigs() {
  signal(SIGHUP, interrupted);
  signal(SIGINT, interrupted);
  signal(SIGQUIT, SIG_IGN);
  signal(SIGPIPE, sigpipe);
  signal(SIGTSTP, suspend);
  signal(SIGCONT, sigcont);
#ifdef TIOCGWINSZ
  signal(SIGWINCH, sigwinch);
#endif
}

#ifdef __GNUC__
void setstatus(unsigned char *title) {
#else
void setstatus(title)
unsigned char *title; {
#endif
  unsigned char *t=title;
  for (;*t;t++) if (*t<' ') (*t)+='@';
  memset(statusline, ' ', MAX_COLS-1);
  memcpy(statusline, title, strlen(title)<MAX_COLS ? strlen(title) : MAX_COLS);
}

void displaystatus() {
  normal();
  fullscroll();
  gotoxy(0, ystatus);
  setinv();
  write(1, statusline, cols-1);
  if (hold_mode) {
    gotoxy(cols-4, ystatus);
    write(1, "(h)", 3);
  }
  cursorwhere=2;
  normal();
  cleareol();
}

#ifdef __GNUC__
int casecmp(unsigned char *s, unsigned char *t) {
#else
int casecmp(s, t)
unsigned char *s, *t; {
#endif
  while (((*s>='a' && *s<='z')?(*s)-32:*s)==
         ((*t>='a' && *t<='z')?(*t)-32:*t)) {
    if (*s=='\0') return 1;
    s++; t++;
  }
  return 0;
}

#ifdef __GNUC__
void addtab(unsigned char *line) {
#else
void addtab(line)
unsigned char *line; {
#endif
  struct tabinfo *nt;

  nt=oldest;
  if (tablines) do {
    if (casecmp(nt->string, line)) {
      strcpy(nt->string, line);
      if (nt==oldest) oldest=nt->prev;
      else {
	nt->prev->next=nt->next;
	nt->next->prev=nt->prev;
	nt->prev=oldest;
	nt->next=oldest->next;
	oldest->next=nt;
	nt->next->prev=nt;
      }
      curtabt=oldest->next;
      curtabr=oldest;
      return;
    }
    nt=nt->next;
  } while (nt!=oldest);

  if (!tablines) {
    nt=(struct tabinfo *)malloc(sizeof (struct tabinfo));
    nt->prev=nt->next=curtabt=curtabr=oldest=nt;
    tablines++;
  } else if (tablines<MAX_TAB_LINES) {
    nt=(struct tabinfo *)malloc(sizeof (struct tabinfo));
    nt->prev=oldest;
    nt->next=oldest->next;
    oldest->next=nt;
    nt->next->prev=nt;
    tablines++;
  } else {
    nt=oldest;
    oldest=nt->prev;
  }
  strcpy(nt->string, line);
  oldest=nt->prev;
  curtabt=oldest->next;
  curtabr=oldest;
}

void doprotcommand() {
  unsigned char *tmp;

  switch (protcmd[0]) {
    case 'i' : dispmode=2;	/* set irc mode, ack */
    	       bold=inv=under=0;
	       write(writefd, "@ssfe@i\n", 8);
	       break;
    case 'c' : dispmode=1;	/* set cooked mode, ack */
	       write(writefd, "@ssfe@c\n", 8);
    	       break;
    case 's' : setstatus(protcmd+1); /* set status */
    	       displaystatus();
    	       break;
    case 'T' : strncpy(ctrl_t, protcmd+1, 127); /* set ^t's text */
	       ctrl_t[126] = '\0';
	       strcat(ctrl_t, "\n");
	       break;
    case 't' : addtab(protcmd+1); /* add tabkey entry */
    	       break;
    case 'l' : fullscroll(); /* clear screen */
	       normal();
	       clearscreen();
	       bold=inv=under=wherex=wherey=donl=0;
	       displaystatus();
	       ofsredisplay(0);
	       break;

    case 'P' : no_echo = 1;		    /* password prompt */
    case 'p' : if (strlen(protcmd+1)<=8) {  /* prompt something */
		 fullscroll();
		 if (!specialprompt) {
		   histcurrent->len=inputlast;
		   histcurrent->plen=plen;
		 }
		 input=extrainput;
		 strcpy(input, protcmd+1);
		 plen=strlen(input);
		 inputofs=0;
		 modified=specialprompt=1;
		 inputlast=inputcursor=plen;
		 ofsredisplay(0);
	       }
	       break;
    case 'n' : if (cursorwhere!=1) { /* type text */
		 normal();
		 fullscroll();
		 gotoxy(inputcursor, yinput);
		 cursorwhere=1;
	       }
	       for (tmp=protcmd+1; *tmp; tmp++) {
		 inschar(*tmp);
	       }
	       break;
    case 'o' : strcpy(o_buffer, protcmd+1);
    	       break;
  }
}

void newline() {
  unsigned char t;
  hold_lines++;
  if (hold_mode && hold_lines>lines-4) {
    normal();
    fullscroll();
    gotoxy(cols-4, ystatus);
    setinv();
    write(1, "(H)", 3);
    while(1) {
      read(0, &t, 1);
      if (t==9) break;
      dokbdchar(t);
    }
    normal();
    fullscroll();
    gotoxy(cols-4, ystatus);
    setinv();
    write(1, "(h)", 3);
    hold_lines=0;
    normal();
    winscroll();
    gotoxy(cols-1, wherey);
    if (bold) setbold();
    if (under) setunder();
    if (inv) setinv();
  }
}

#ifdef __GNUC__
void formatter(unsigned char *readbuf, int rc) {
#else
void formatter(readbuf, rc)
unsigned char *readbuf;
int rc; {
#endif

  unsigned char t, *r, *lwr, *lww;
  int lwrc, lwbold, lwunder, lwinv, lwx;

  if (cursorwhere!=0) {
    winscroll();
    gotoxy(wherex, wherey);
    cursorwhere=0;
  }
  if (donl) {
    newline();
    write(1, "\r\n", 2);
    normal();
    wherex=0;
    bold=inv=under=lwbold=lwinv=lwunder=0;
    if (wherey<ystatus-1) wherey++;
  } else if (dispmode>1) {
    if (bold) setbold();
    if (under) setunder();
    if (inv) setinv();
    lwbold=bold;
    lwinv=inv;
    lwunder=under;
  }
  if (rc && readbuf[rc-1]=='\n') {
    rc--;
    donl=1; cutline=0;
  } else {
    donl=0;
    if (dispmode==0) cutline=1;
  }
  if (dispmode==0) {
    if (rc) write(1, readbuf, rc);
    normal();
    return;
  }
  lww=w=writebuf;
  lwr=r=readbuf;
  lwrc=rc;
  lwx=wherex;
  while(rc-->0) {
    t=(*r++);
    if (t=='\r') continue;
    if (wherex>cols-2 || (t==9 && wherex>(cols-2)&0xfff8)) {
      if (t==' ' || t==9) ;
      else if (lww>writebuf+cols/2) {
	wherex=lwx; r=lwr; w=lww; rc=lwrc;
	bold=lwbold; inv=lwinv; under=lwunder; wherex=lwx;
      } else {
	rc++; r--;
      }
      write(1, writebuf, w-writebuf);
      newline();
      write(1, "\r\n           ", 13);
      w=writebuf;
      lwr=r; lww=w; lwrc=rc;
      lwbold=bold; lwinv=inv; lwunder=under;
      lwx=wherex=11;
      if (wherey<ystatus-1) wherey++;
      rc--; t=(*r++);
    }
    if (t=='\n') {
      if (w!=writebuf) write(1, writebuf, w-writebuf);
      newline();
      write(1, "\r\n", 2);
      normal();
      w=writebuf;
      lwr=r; lww=w; lwrc=rc;
      lwbold=bold=lwinv=inv=lwunder=under=lwx=wherex=0;
      if (wherey<ystatus-1) wherey++;
    } else if (dispmode>1 &&
               ((t==2 && bold) || (t==22 && inv) || (t==31 && under))) {
      writecap(t_me);
      bold=under=inv=0;
    } else if (dispmode>1 && t==2) {
      writecap(t_md);
      bold=1;
    } else if (dispmode>1 && t==22) {
      writecap(t_mr);
      inv=1;
    } else if (dispmode>1 && t==31) {
      writecap(t_us);
      under=1;
    } else if (dispmode>1 && t==15) {
      if (bold || inv || under) writecap(t_me);
      bold=under=inv=0;
    } else if (t==9) {
      (*w++)=t;
      wherex=(wherex & 0xfff8)+8;
    } else if (t<' ' && (t!=7 || !beep)) {
      wherex++;
      if (inv) {
	writecap(t_me);
	(*w++)=(t+'@');
      } else {
	writecap(t_mr);
	(*w++)=(t+'@');
	writecap(t_me);
      }
      if (bold) writecap(t_md);
      if (inv) writecap(t_mr);
      if (under) writecap(t_us);
    } else {
      if (t!=7) wherex++;
      (*w++)=t;
    }
    if (t==' ' || t==9) {
      lwr=r; lww=w; lwrc=rc;
      lwbold=bold; lwinv=inv; lwunder=under;
      lwx=wherex;
    }
  }
  if (w!=writebuf) write(1, writebuf, w-writebuf);
}

#ifdef __GNUC__
void doprogramline(unsigned char *readbuf, int rc) {
#else
void doprogramline(readbuf, rc)
unsigned char *readbuf;
int rc; {
#endif

  unsigned char *w, *r, *r2, t;
  if (dispmode==0) {
    formatter(readbuf, rc);
    return;
  }
  w=r=readbuf;
  while(rc-->0) {
    t=(*r++);
    if (idstatus==0)
      if (*inid=='\0') {
	idstatus=1;
	wpc=protcmd;
	inid=id;
      } else if (*inid==t && (inid!=id || r==(readbuf+1) || *(r-2)=='\n')) {
	inid++;
	(*wpc++)=t;
      } else {
        r2=protcmd;
        while (r2!=wpc) (*w++)=(*r2++);
	(*w++)=t;
	wpc=protcmd;
	inid=id;
      }
    if (idstatus==1)
      if (t=='\n')  {
        *wpc='\0';
        doprotcommand();
	inid=id;
	wpc=protcmd;
	idstatus=0;
      } else (*wpc++)=t;
  }
  if (w!=readbuf) formatter(readbuf, w-readbuf);
}

#ifdef __GNUC__
void write1(unsigned char t, int pos) {
#else
void write1(t, pos)
unsigned char t;
int pos; {
#endif
  if (no_echo && pos>=plen) {
    write(1, "*", 1);
  } else if (t>=' ')
      write(1, &t, 1);
  else {
      setinv();
      t+='@';
      write(1, &t, 1);
      normal();
  }
}

#ifdef __GNUC__
void ofsredisplay(int x) {
#else
void ofsredisplay(x) {
#endif
/* redisplays starting at x */
  unsigned char *w;
  int i;
  gotoxy(x, yinput);
  if (inputlast-inputofs>=x) {
    i=((inputlast-inputofs>cols-1 ? cols-1-x : inputlast-inputofs-x));
    for (w=input+inputofs+x; i--; w++) write1(*w, w-input);
  }
  cleareol();
  gotoxy(inputcursor, yinput);
  cursorwhere=1;
}

#ifdef __GNUC__
void delempty(struct histinfo *leavealone) {
#else
void delempty(leavealone)
struct histinfo *leavealone; {
#endif
  struct histinfo *h, *h2;
  int cont=0;
  h=histoldest;
  do {
    cont=0;
    if ((h->len<=h->plen) && (h!=leavealone)) {
      histlines--;
      h->next->prev=h->prev;
      h->prev->next=h->next;
      h2=h->prev;
      free(h);
      if (h==histoldest) {
        histoldest=h2;
	cont=1;
      }
      h=h2;
    } else h=h->prev;
  } while ((h!=histoldest || cont) && histlines>0);
  if (!histlines) {
    histoldest=NULL;
    return;
  }
}

struct histinfo *makenew() {
  struct histinfo *nh;
  if (!histlines) {
    nh=(struct histinfo *)malloc(sizeof (struct histinfo));
    nh->prev=nh->next=histoldest=nh;
    histlines++;
  } else if (histlines<MAX_HIST_LINES) {
    nh=(struct histinfo *)malloc(sizeof (struct histinfo));
    nh->prev=histoldest;
    nh->next=histoldest->next;
    histoldest->next=nh;
    nh->next->prev=nh;
    histlines++;
  } else {
    nh=histoldest;
    histoldest=nh->prev;
  }
  return nh;
}

#ifdef __GNUC__
void sendline(int yank) {
#else
void sendline(yank) {
#endif
  if (!specialprompt) {
    histcurrent->len=inputlast;
    histcurrent->plen=plen;
  }
  if (!yank) {
    input[inputlast]='\n';
    if (printmode) formatter(input, inputlast+1);
    if (write(writefd, input+plen, inputlast+1-plen)<inputlast+1-plen)
      cleanupexit(1, "write error");
  }
  input[inputlast]='\0';
  delempty(NULL);
  histcurrent=makenew();
  input=histcurrent->string;
  strcpy(input, prompt);
  plen=strlen(prompt);
  inputofs=specialprompt=0;
  modified=1;
  inputcursor=inputlast=plen;
  ofsredisplay(0);
  no_echo=0;
}

void modify() {
  struct histinfo *h;
  if (!modified) {
    if (inputlast>plen) {
      h=histcurrent;
      delempty(h);
      histcurrent=makenew();
      strcpy(histcurrent->string, h->string);
      input=histcurrent->string;
    }
    modified=1;
  }
}

void fixpos() {
  if (inputcursor<8 && inputofs>0) {
    inputofs-=cols-16;
    inputcursor+=cols-16;
    if (inputofs<0) {
      inputcursor+=inputofs;
      inputofs=0;
    }
    ofsredisplay(0);
  } else if (inputcursor>cols-8) {
    inputofs+=cols-16;
    inputcursor-=cols-16;
    ofsredisplay(0);
  }
}

void reshow() {
  if (inputlast>cols-8) {
    inputcursor=cols-9;
    inputofs=inputlast-cols+9;
  } else {
    inputofs=0;
    inputcursor=inputlast;
  }
  ofsredisplay(0);
}

#ifdef __GNUC__
void inschar(unsigned char t) {
#else
void inschar(t)
unsigned char t; {
#endif

  unsigned char *tmp;

  if (inputlast<BUF_SIZE-4) {
    modify();
    if (inputofs+inputcursor==inputlast) {
      write1(t, inputlast);
      input[inputlast++]=t;
      input[inputlast]='\0';
      inputcursor++;
    } else {
      tmp=input+inputlast;
      while (tmp>=input+inputofs+inputcursor)
	*(tmp+1)=(*tmp--);
      input[inputofs+(inputcursor++)]=t;
      inputlast++;
      ofsredisplay(inputcursor-1);
    }
    fixpos();
  }
}

#ifdef __GNUC__
void dokbdchar(unsigned char t) {
#else
void dokbdchar(t)
unsigned char t; {
#endif

  unsigned char *tmp;

  if (inarrow==1) {
    if (t=='[' || t=='O') {
      inarrow++;
      return;
    }
    inarrow=0;
  } else if (inarrow==2) {
    inarrow=0;
    if (t=='D') t=2;
    else if (t=='C') t=6;
    else if (t=='A') t=16;
    else if (t=='B') t=14;
    else return;
  }
  if (ctrlx && !quote) {
    ctrlx=0;
    t|=0x20;
    if (dispmode>0 && ((t=='h' && !hold_mode) || t=='y')) {
      hold_mode=1;
      hold_lines=0;
      if (cursorwhere!=1) fullscroll();
      cursorwhere=2;
      normal();
      gotoxy(cols-4, ystatus);
      setinv();
      write(1, "(h)", 3);
      normal();
    } else if (dispmode>0 && ((t=='h' && hold_mode) || t=='n')) {
      hold_mode=0;
      if (cursorwhere!=1) fullscroll();
      cursorwhere=2;
      normal();
      gotoxy(cols-4, ystatus);
      setinv();
      write(1, "   ", 3);
      normal();
    } else if (dispmode>0 && t=='i') {
      dispmode=3-dispmode;
      bold=inv=under=0;
    } else if (dispmode>0 && t=='b') {
      beep=!beep;
    } else if (t=='c') cleanupexit(1, "exiting");
    return;
  }
  if (cutline) donl=1;
  if (cursorwhere!=1) {
    normal();
    fullscroll();
    gotoxy(inputcursor, yinput);
    cursorwhere=1;
  }
  if (t==24 && !quote) {
    ctrlx=1;
    return;
  } else ctrlx=0;
  if (t==27 && !quote) {
    inarrow=1;
  } else if ((t==10 || t==13) && !quote) {  /* return, newline */
    sendline(0);
    if (tablines) {
      curtabr=oldest;
      curtabt=oldest->next;
    }
  } else if (t==25 && !quote) {	  /* ^y */
    if (!specialprompt) {
      sendline(1);
      if (tablines) {
	curtabr=oldest;
	curtabt=oldest->next;
      }
    }
  } else if (t==21 && !quote) {   /* ^u */
    modify();
    input[plen]='\0';
    inputcursor=inputlast=plen;
    inputofs=0;
    ofsredisplay(0);
  } else if ((t==8 || t==0x7f) && !quote) {  /* ^h, ^? */
    if (inputcursor>plen) {
      modify();
      tmp=input+inputcursor+inputofs;
      while (tmp<input+inputlast)
	*(tmp-1)=(*tmp++);
      input[--inputlast]='\0';
      gotoxy(--inputcursor, yinput);
      ofsredisplay(inputcursor);
      fixpos();
    }
  } else if (t==4 && !quote) {  /* ^d */
    if (inputcursor+inputofs<inputlast) {
      modify();
      tmp=input+inputcursor+inputofs+1;
      while (tmp<input+inputlast)
	*(tmp-1)=(*tmp++);
      input[--inputlast]='\0';
      gotoxy(inputcursor, yinput);
      ofsredisplay(inputcursor);
    }
  } else if (t==11 && !quote) {  /* ^k */
    if (inputcursor+inputofs<inputlast) {
      modify();
      input[inputlast=inputofs+inputcursor]='\0';
      ofsredisplay(inputcursor);
    }
  } else if (t==2 && !quote) {  /* ^b */
    if (inputcursor>0 && (inputcursor>plen || inputofs>0)) {
      gotoxy(--inputcursor, yinput);
      fixpos();
    }
  } else if (t==6 && !quote) {  /* ^f */
    if (inputcursor+inputofs<inputlast) {
      gotoxy(++inputcursor, yinput);
      fixpos();
    }
  } else if (t==1 && !quote) { /* ^a */
    if (inputcursor+inputofs>plen) {
      if (inputofs==0)
	gotoxy((inputcursor=plen), yinput);
      else {
	inputofs=0;
	inputcursor=plen;
	ofsredisplay(0);
      }
    }
  } else if (t==5 && !quote) { /* ^e */
    if (inputcursor+inputofs<inputlast) {
      if (inputlast-inputofs<cols-3) {
	gotoxy((inputcursor=inputlast-inputofs), yinput);
      } else if (inputlast>cols-8) {
	inputcursor=cols-9;
	inputofs=inputlast-cols+9;
	ofsredisplay(0);
      } else {
	inputofs=0;
	inputcursor=inputlast;
	ofsredisplay(0);
      }
    }
  } else if (t==12 && !quote) { /* ^l */
    displaystatus();
    ofsredisplay(0);
  } else if (t==9 && !quote) { /* TAB */
    if (tablines) {
      modify();
      strcpy(input+plen, curtabt->string);
      curtabr=curtabt->prev;
      curtabt=curtabt->next;
      inputlast=strlen(input);
      reshow();
    }
  } else if (t==18 && !quote) { /* ^r */
    if (tablines) {
      modify();
      strcpy(input+plen, curtabr->string);
      curtabt=curtabr->next;
      curtabr=curtabr->prev;
      inputlast=strlen(input);
      reshow();
    }
  } else if (t==16 && !quote) { /* ^p */
    if (histlines>1 && !specialprompt) {
      histcurrent->plen=plen;
      histcurrent->len=inputlast;
      histcurrent=histcurrent->next;
      plen=histcurrent->plen;
      inputlast=histcurrent->len;
      input=histcurrent->string;
      modified=0;
      reshow();
    }
  } else if (t==14 && !quote) { /* ^n */
    if (histlines>1 && !specialprompt) {
      histcurrent->plen=plen;
      histcurrent->len=inputlast;
      histcurrent=histcurrent->prev;
      plen=histcurrent->plen;
      inputlast=histcurrent->len;
      input=histcurrent->string;
      modified=0;
      reshow();
    }
  } else if (t==15 &&!quote) { /* ^o */
    if (strlen(o_buffer)) modify();
    for (tmp=o_buffer; *tmp; tmp++) inschar(*tmp);
  } else if (t==20 && !quote) { /* ^t */
    write(writefd, ctrl_t, strlen(ctrl_t));
  } else if (t==22 && !quote) { /* ^v */
    quote++;
    return;
#ifdef CONTROL_W
  } else if (t==23 && !quote) { /* ^w */
    fullscroll();
    normal();
    clearscreen();
    bold=inv=under=wherex=wherey=donl=0;
    displaystatus();
    ofsredisplay(0);
#endif
  } else inschar(t);
  quote=0;
}

#ifdef __GNUC__
void barf(unsigned char *m) {
#else
void barf(m)
unsigned char *m; {
#endif
  fprintf(stderr, "%s\n", m);
  exit(1);
}

char *myname;

void use() {
  fprintf(stderr, "Use: %s [options] program [program's options]\n", myname);
  fprintf(stderr, "Options are:\n");
  fprintf(stderr, "   -raw, -cooked, -irc  : set display mode\n");
  fprintf(stderr, "   -print               : print your input lines\n");
  fprintf(stderr, "   -prompt <prompt>     : specify a command-line prompt\n");
  fprintf(stderr, "   -hold                : pause after each full screen (for cooked/irc mode)\n");
  fprintf(stderr, "   -beep                : let beeps through (for cooked/irc mode)\n");
  fprintf(stderr, "   -flow                : leave ^S/^Q alone for flow control\n");
  exit(1);
}

#ifdef __GNUC__
int main(int argc, char *argv[]) {
#else
int main(argc, argv)
int argc;
char *argv[]; {
#endif

  char *vr;
  int pfds0[2], pfds1[2], pfds2[2];

  myname=(*argv);
  prompt=nullstring;
  while (argc>1) {
    if (strcmp(argv[1], "-raw")==0) {
      dispmode=0;
      argv++; argc--;
    } else if (strcmp(argv[1], "-cooked")==0) {
      dispmode=1;
      argv++; argc--;
    } else if (strcmp(argv[1], "-irc")==0) {
      dispmode=2;
      argv++; argc--;
    } else if (strcmp(argv[1], "-hold")==0) {
      hold_mode=1;
      argv++; argc--;
    } else if (strcmp(argv[1], "-print")==0) {
      argv++; argc--;
      if (prompt==nullstring) prompt=defprompt;
      printmode=1;
    } else if (strcmp(argv[1], "-beep")==0) {
      beep=1;
      argv++; argc--;
    } else if (strcmp(argv[1], "-flow")==0) {
      flow=1;
      argv++; argc--;
    } else if (strcmp(argv[1], "-prompt")==0) {
      if (argc>2) prompt=(unsigned char *)argv[2];
      if (strlen(prompt)>8) barf("Prompt too long");
      argv+=2; argc-=2;
    } else break;
  }
  if (argc<2) use();
  if (!isatty(0)) barf("I can only run on a tty, sorry");
  if ((termtype=getenv("TERM"))==NULL) barf("No terminal type set");
  if (tgetent(termcap, termtype)<1) barf("No termcap info for your terminal");
  tc=capabilities;
  if ((t_cm=(char *)tgetstr("cm", &tc))==NULL)
    barf("Can't find a way to move the cursor around with your terminal");
  if ((t_cl=(char *)tgetstr("cl", &tc))==NULL)
    barf("Can't find a way to clear the screen with your terminal");
  if ((t_ce=(char *)tgetstr("ce", &tc))==NULL)
    barf("Can't find a way to clear to end of line with your terminal");
  if ((t_cs=(char *)tgetstr("cs", &tc))==NULL) {
    if (strncmp(termtype, "xterm", 5)==0 || strncmp(termtype, "vt100", 5)==0)
      ansi_cs=1;
    else
      barf("Can't find a way to set the scrolling region with your terminal");
  }
  if ((t_me=(char *)tgetstr("me", &tc))!=NULL) {
    if ((t_mr=(char *)tgetstr("mr", &tc))==NULL) t_mr=t_me;
    if ((t_md=(char *)tgetstr("md", &tc))==NULL) t_md=t_me;
    if ((t_us=(char *)tgetstr("us", &tc))==NULL) t_us=t_me;
  } else if ((t_me=(char *)tgetstr("se", &tc))!=NULL &&
	     (t_mr=(char *)tgetstr("so", &tc))!=NULL) {
    t_md=t_mr;
    t_us=tc;
    (*tc++)='\0';
  } else {
    t_me=t_md=t_mr=t_us=tc;
    (*tc++)='\0';
  }

/*
  if ((ttyfd=open("/dev/tty", O_RDWR))<0 &&
      (ttyfd=open("/dev/tty", O_RDONLY))<0) barf("Can't open terminal!");
    */
    ttyfd = 0;

#ifdef TIOCGWINSZ
  if (ioctl(ttyfd, TIOCGWINSZ, &wsz)<0 || wsz.ws_row<1 || wsz.ws_col<1) {
#endif
    lines=((vr=getenv("LINES"))?atoi(vr):0);
    cols=((vr=getenv("COLUMNS"))?atoi(vr):0);
    if (lines<1 || cols<1) {
      if ((lines=tgetnum("li"))<1 || (cols=tgetnum("co"))<1) {
	lines=24; cols=80;
      }
    }
#ifdef TIOCGWINSZ
  } else {
    lines=wsz.ws_row;
    cols=wsz.ws_col;
  }
#endif

  if (pipe(pfds0)<0 || pipe(pfds1)<0 || pipe(pfds2)<0) {
    perror("pipe");
    exit(1);
  }
  mypid=getpid();
  switch (pid=fork()) {
    case -1:
      perror("fork");
      exit(1);
    case 0:
      if (pfds0[0]!=0) dup2(pfds0[0], 0);
      if (pfds1[1]!=1) dup2(pfds1[1], 1);
      if (pfds2[1]!=2) dup2(pfds2[1], 2);
      if (pfds0[0]>2) close(pfds0[0]);
      if (pfds0[1]>2) close(pfds0[1]);
      if (pfds1[0]>2) close(pfds1[0]);
      if (pfds1[1]>2) close(pfds1[1]);
      if (pfds2[0]>2) close(pfds2[0]);
      if (pfds2[1]>2) close(pfds2[1]);
      /* okay we can read from 0 and write to 1 and 2, now.. it seems */
      execvp(argv[1], argv+1);
      perror("exec");
      sleep(1);
      exit(1);
    default:
      close(pfds0[0]);
      close(pfds1[1]);
      close(pfds2[1]);
      readfd=pfds1[0];
      writefd=pfds0[1];
      errfd=pfds2[0];
  }

#ifdef USE_SGTTY

  if (ioctl(ttyfd, TIOCGETP, &term)<0 || ioctl(ttyfd, TIOCGETC, &tch)<0 ||
      ioctl(ttyfd, TIOCGLTC, &lch)<0) {
    perror("sgtty get ioctl");
    exit(1);
  }
  term0=term;
  tch0=tch;
  lch0=lch;
  term.sg_flags|=CBREAK;
  term.sg_flags&= ~ECHO & ~CRMOD;

  memset(&tch, -1, sizeof(tch));
  memset(&lch, -1, sizeof(lch));
  tch.t_intrc=(char)28;
  tch.t_quitc=(char)3;
  if (flow) {
    tch.t_startc=(char)17;
    tch.t_stopc=(char)19;
  }
  lch.t_suspc=(char)26;

  if (ioctl(ttyfd, TIOCSETP, &term)<0 || ioctl(ttyfd, TIOCSETC, &tch)<0 ||
      ioctl(ttyfd, TIOCSLTC, &lch)<0) {
    perror("sgtty set ioctl");
    exit(1);
  }

#else
  if (tcgetattr(ttyfd, &term)<0) {
    perror("tcgetattr");
    exit(1);
  }
  term0=term;

  term.c_lflag &= ~ECHO & ~ICANON;
  term.c_cc[VTIME]=(char)0;
  term.c_cc[VMIN]=(char)1;
  if (!flow) {
    term.c_cc[VSTOP]=(char)0;
    term.c_cc[VSTART]=(char)0;
  }
  term.c_cc[VQUIT]=(char)3;
  term.c_cc[VINTR]=(char)28; /* reverse ^c and ^\ */
  term.c_cc[VSUSP]=(char)26;
#ifdef VREPRINT
  term.c_cc[VREPRINT]=(char)0;
#endif
#ifdef VDISCARD
  term.c_cc[VDISCARD]=(char)0;
#endif
#ifdef VLNEXT
  term.c_cc[VLNEXT]=(char)0;
#endif
#ifdef VDSUSP
  term.c_cc[VDSUSP]=(char)0;
#endif

  if (tcsetattr(ttyfd, TCSANOW, &term)<0) {
    perror("tcsetattr");
    exit(1);
  }
#endif

  allsigs();

  ystatus=lines-2;
  yinput=lines-1;

  if (lines>255) barf("Screen too big");
  if (ystatus<=2 || cols<20) barf("Screen too small");

  statusline=(unsigned char *)malloc(MAX_COLS);
  writebuf=(unsigned char *)malloc(20*BUF_SIZE);
  strcpy(tmpstr, " ");
  for (i=1; i<argc; i++)
    if (strlen(tmpstr)+strlen(argv[i])<cols-1) {
      strcat(tmpstr, argv[i]);
      strcat(tmpstr, " ");
    }
  setstatus(tmpstr);

  if (dispmode==0) wherey=ystatus-1;
  clearscreen();
  displaystatus();

  histoldest=histcurrent=(struct histinfo *)malloc(sizeof (struct histinfo));
  input=histcurrent->string;
  histcurrent->prev=histcurrent->next=histcurrent;
  histlines=1;
  plen=strlen(prompt);
  inputlast=inputcursor=plen;
  strcpy(input, prompt);
  ofsredisplay(0);
  *protcmd='\0';
  *o_buffer='\0';
  cursorwhere=1;

  FD_ZERO(&ready);
  FD_SET(ttyfd, &ready);
  FD_SET(readfd, &ready);
  FD_SET(errfd, &ready);

  while(1) {
    result=ready;
    if (select(64, &result, NULL, NULL, NULL)<=0)
      if (errno==EINTR) continue;
      else cleanupexit(1, "select error");

    if (FD_ISSET(readfd, &result))
      if ((rc=read(readfd, readbuf, BUF_SIZE))>0)
        doprogramline(readbuf, rc);
      else
        cleanupexit(1, "program terminated");
    if (FD_ISSET(errfd, &result))
      if ((rc=read(errfd, readbuf, BUF_SIZE))>0)
        doprogramline(readbuf, rc);
      else
        cleanupexit(1, "program terminated");
    if (FD_ISSET(ttyfd, &result))
      if ((rrc=read(0, readbuf, BUF_SIZE))>0)
        for (t=readbuf; rrc>0; rrc--) dokbdchar(*(t++));
      else
	cleanupexit(1, "read error from keyboard");
  }
}

