/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*          Copyright (c) 1982-2009 AT&T Intellectual Property          *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                    by AT&T Intellectual Property                     *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                  David Korn <dgk@research.att.com>                   *
*                                                                      *
***********************************************************************/
#pragma prototyped
/*
 *  edit.c - common routines for vi and emacs one line editors in shell
 *
 *   David Korn				P.D. Sullivan
 *   AT&T Labs
 *
 *   Coded April 1983.
 */

#include	<ast.h>
#include	<errno.h>
#include	<ccode.h>
#include	"FEATURE/options"
#include	"FEATURE/time"
#include	"FEATURE/cmds"
#ifdef _hdr_utime
#   include	<utime.h>
#   include	<ls.h>
#endif

#if KSHELL
#   include	"defs.h"
#   include	"variables.h"
#else
#   include	<ctype.h>
    extern char ed_errbuf[];
    char e_version[] = "\n@(#)$Id: Editlib version 1993-12-28 r $\0\n";
#endif	/* KSHELL */
#include	"io.h"
#include	"terminal.h"
#include	"history.h"
#include	"edit.h"

static char CURSOR_UP[20] = { ESC, '[', 'A', 0 };



#if SHOPT_MULTIBYTE
#   define is_cntrl(c)	((c<=STRIP) && iscntrl(c))
#   define is_print(c)	((c&~STRIP) || isprint(c))
#else
#   define is_cntrl(c)	iscntrl(c)
#   define is_print(c)	isprint(c)
#endif

#if	(CC_NATIVE == CC_ASCII)
#   define printchar(c)	((c) ^ ('A'-cntl('A')))
#else
    static int printchar(int c)
    {
	switch(c)
	{
	    
	    case cntl('A'): return('A');
	    case cntl('B'): return('B');
	    case cntl('C'): return('C');
	    case cntl('D'): return('D');
	    case cntl('E'): return('E');
	    case cntl('F'): return('F');
	    case cntl('G'): return('G');
	    case cntl('H'): return('H');
	    case cntl('I'): return('I');
	    case cntl('J'): return('J');
	    case cntl('K'): return('K');
	    case cntl('L'): return('L');
	    case cntl('M'): return('M');
	    case cntl('N'): return('N');
	    case cntl('O'): return('O');
	    case cntl('P'): return('P');
	    case cntl('Q'): return('Q');
	    case cntl('R'): return('R');
	    case cntl('S'): return('S');
	    case cntl('T'): return('T');
	    case cntl('U'): return('U');
	    case cntl('V'): return('V');
	    case cntl('W'): return('W');
	    case cntl('X'): return('X');
	    case cntl('Y'): return('Y');
	    case cntl('Z'): return('Z');
	    case cntl(']'): return(']');
	    case cntl('['): return('[');
	}
	return('?');
    }
#endif
#define MINWINDOW	15	/* minimum width window */
#define DFLTWINDOW	80	/* default window width */
#define RAWMODE		1
#define ALTMODE		2
#define ECHOMODE	3
#define	SYSERR	-1

#if SHOPT_OLDTERMIO
#   undef tcgetattr
#   undef tcsetattr
#endif /* SHOPT_OLDTERMIO */

#ifdef RT
#   define VENIX 1
#endif	/* RT */


#ifdef _hdr_sgtty
#   ifdef TIOCGETP
	static int l_mask;
	static struct tchars l_ttychars;
	static struct ltchars l_chars;
	static  char  l_changed;	/* set if mode bits changed */
#	define L_CHARS	4
#	define T_CHARS	2
#	define L_MASK	1
#   endif /* TIOCGETP */
#endif /* _hdr_sgtty */

#if KSHELL
     static int keytrap(Edit_t *,char*, int, int, int);
#else
     Edit_t editb;
#endif	/* KSHELL */


#ifndef _POSIX_DISABLE
#   define _POSIX_DISABLE	0
#endif

#ifdef future
    static int compare(const char*, const char*, int);
#endif  /* future */
#if SHOPT_VSH || SHOPT_ESH
#   define ttyparm	(ep->e_ttyparm)
#   define nttyparm	(ep->e_nttyparm)
    static const char bellchr[] = "\a";	/* bell char */
#endif /* SHOPT_VSH || SHOPT_ESH */


/*
 * This routine returns true if fd refers to a terminal
 * This should be equivalent to isatty
 */
int tty_check(int fd)
{
	register Edit_t *ep = (Edit_t*)(sh_getinterp()->ed_context);
	struct termios tty;
	ep->e_savefd = -1;
	return(tty_get(fd,&tty)==0);
}

/*
 * Get the current terminal attributes
 * This routine remembers the attributes and just returns them if it
 *   is called again without an intervening tty_set()
 */

int tty_get(register int fd, register struct termios *tty)
{
	register Edit_t *ep = (Edit_t*)(sh_getinterp()->ed_context);
	if(fd == ep->e_savefd)
		*tty = ep->e_savetty;
	else
	{
		while(tcgetattr(fd,tty) == SYSERR)
		{
			if(errno !=EINTR)
				return(SYSERR);
			errno = 0;
		}
		/* save terminal settings if in cannonical state */
		if(ep->e_raw==0)
		{
			ep->e_savetty = *tty;
			ep->e_savefd = fd;
		}
	}
	return(0);
}

/*
 * Set the terminal attributes
 * If fd<0, then current attributes are invalidated
 */

int tty_set(int fd, int action, struct termios *tty)
{
	register Edit_t *ep = (Edit_t*)(sh_getinterp()->ed_context);
	if(fd >=0)
	{
#ifdef future
		if(ep->e_savefd>=0 && compare(&ep->e_savetty,tty,sizeof(struct termios)))
			return(0);
#endif
		while(tcsetattr(fd, action, tty) == SYSERR)
		{
			if(errno !=EINTR)
				return(SYSERR);
			errno = 0;
		}
		ep->e_savetty = *tty;
	}
	ep->e_savefd = fd;
	return(0);
}

#if SHOPT_ESH || SHOPT_VSH
/*{	TTY_COOKED( fd )
 *
 *	This routine will set the tty in cooked mode.
 *	It is also called by error.done().
 *
}*/

void tty_cooked(register int fd)
{
	register Edit_t *ep = (Edit_t*)(sh_getinterp()->ed_context);
	if(ep->e_raw==0)
		return;
	if(fd < 0)
		fd = ep->e_savefd;
#ifdef L_MASK
	/* restore flags */
	if(l_changed&L_MASK)
		ioctl(fd,TIOCLSET,&l_mask);
	if(l_changed&T_CHARS)
		/* restore alternate break character */
		ioctl(fd,TIOCSETC,&l_ttychars);
	if(l_changed&L_CHARS)
		/* restore alternate break character */
		ioctl(fd,TIOCSLTC,&l_chars);
	l_changed = 0;
#endif	/* L_MASK */
	/*** don't do tty_set unless ttyparm has valid data ***/
	if(tty_set(fd, TCSANOW, &ttyparm) == SYSERR)
		return;
	ep->e_raw = 0;
	return;
}

/*{	TTY_RAW( fd )
 *
 *	This routine will set the tty in raw mode.
 *
}*/

int tty_raw(register int fd, int echomode)
{
	int echo = echomode;
#ifdef L_MASK
	struct ltchars lchars;
#endif	/* L_MASK */
	register Edit_t *ep = (Edit_t*)(sh_getinterp()->ed_context);
	if(ep->e_raw==RAWMODE)
		return(echo?-1:0);
	else if(ep->e_raw==ECHOMODE)
		return(echo?0:-1);
#if !SHOPT_RAWONLY
	if(ep->e_raw != ALTMODE)
#endif /* SHOPT_RAWONLY */
	{
		if(tty_get(fd,&ttyparm) == SYSERR)
			return(-1);
	}
#if  L_MASK || VENIX
	if(ttyparm.sg_flags&LCASE)
		return(-1);
	if(!(ttyparm.sg_flags&ECHO))
	{
		if(!echomode)
			return(-1);
		echo = 0;
	}
	nttyparm = ttyparm;
	if(!echo)
		nttyparm.sg_flags &= ~(ECHO | TBDELAY);
#   ifdef CBREAK
	nttyparm.sg_flags |= CBREAK;
#   else
	nttyparm.sg_flags |= RAW;
#   endif /* CBREAK */
	ep->e_erase = ttyparm.sg_erase;
	ep->e_kill = ttyparm.sg_kill;
	ep->e_eof = cntl('D');
	ep->e_werase = cntl('W');
	ep->e_lnext = cntl('V');
	if( tty_set(fd, TCSADRAIN, &nttyparm) == SYSERR )
		return(-1);
	ep->e_ttyspeed = (ttyparm.sg_ospeed>=B1200?FAST:SLOW);
#   ifdef TIOCGLTC
	/* try to remove effect of ^V  and ^Y and ^O */
	if(ioctl(fd,TIOCGLTC,&l_chars) != SYSERR)
	{
		lchars = l_chars;
		lchars.t_lnextc = -1;
		lchars.t_flushc = -1;
		lchars.t_dsuspc = -1;	/* no delayed stop process signal */
		if(ioctl(fd,TIOCSLTC,&lchars) != SYSERR)
			l_changed |= L_CHARS;
	}
#   endif	/* TIOCGLTC */
#else
	if (!(ttyparm.c_lflag & ECHO ))
	{
		if(!echomode)
			return(-1);
		echo = 0;
	}
#   ifdef FLUSHO
	ttyparm.c_lflag &= ~FLUSHO;
#   endif /* FLUSHO */
	nttyparm = ttyparm;
#  ifndef u370
	nttyparm.c_iflag &= ~(IGNPAR|PARMRK|INLCR|IGNCR|ICRNL);
	nttyparm.c_iflag |= BRKINT;
#   else
	nttyparm.c_iflag &= 
			~(IGNBRK|PARMRK|INLCR|IGNCR|ICRNL|INPCK);
	nttyparm.c_iflag |= (BRKINT|IGNPAR);
#   endif	/* u370 */
	if(echo)
		nttyparm.c_lflag &= ~ICANON;
	else
		nttyparm.c_lflag &= ~(ICANON|ECHO|ECHOK);
	nttyparm.c_cc[VTIME] = 0;
	nttyparm.c_cc[VMIN] = 1;
#   ifdef VREPRINT
	nttyparm.c_cc[VREPRINT] = _POSIX_DISABLE;
#   endif /* VREPRINT */
#   ifdef VDISCARD
	nttyparm.c_cc[VDISCARD] = _POSIX_DISABLE;
#   endif /* VDISCARD */
#   ifdef VDSUSP
	nttyparm.c_cc[VDSUSP] = _POSIX_DISABLE;
#   endif /* VDSUSP */
#   ifdef VWERASE
	if(ttyparm.c_cc[VWERASE] == _POSIX_DISABLE)
		ep->e_werase = cntl('W');
	else
		ep->e_werase = nttyparm.c_cc[VWERASE];
	nttyparm.c_cc[VWERASE] = _POSIX_DISABLE;
#   else
	    ep->e_werase = cntl('W');
#   endif /* VWERASE */
#   ifdef VLNEXT
	if(ttyparm.c_cc[VLNEXT] == _POSIX_DISABLE )
		ep->e_lnext = cntl('V');
	else
		ep->e_lnext = nttyparm.c_cc[VLNEXT];
	nttyparm.c_cc[VLNEXT] = _POSIX_DISABLE;
#   else
	ep->e_lnext = cntl('V');
#   endif /* VLNEXT */
	ep->e_eof = ttyparm.c_cc[VEOF];
	ep->e_erase = ttyparm.c_cc[VERASE];
	ep->e_kill = ttyparm.c_cc[VKILL];
	if( tty_set(fd, TCSADRAIN, &nttyparm) == SYSERR )
		return(-1);
	ep->e_ttyspeed = (cfgetospeed(&ttyparm)>=B1200?FAST:SLOW);
#endif
	ep->e_raw = (echomode?ECHOMODE:RAWMODE);
	return(0);
}

#if !SHOPT_RAWONLY

/*
 *
 *	Get tty parameters and make ESC and '\r' wakeup characters.
 *
 */

#   ifdef TIOCGETC
int tty_alt(register int fd)
{
	register Edit_t *ep = (Edit_t*)(sh_getinterp()->ed_context);
	int mask;
	struct tchars ttychars;
	switch(ep->e_raw)
	{
	    case ECHOMODE:
		return(-1);
	    case ALTMODE:
		return(0);
	    case RAWMODE:
		tty_cooked(fd);
	}
	l_changed = 0;
	if( ep->e_ttyspeed == 0)
	{
		if((tty_get(fd,&ttyparm) != SYSERR))
			ep->e_ttyspeed = (ttyparm.sg_ospeed>=B1200?FAST:SLOW);
		ep->e_raw = ALTMODE;
	}
	if(ioctl(fd,TIOCGETC,&l_ttychars) == SYSERR)
		return(-1);
	if(ioctl(fd,TIOCLGET,&l_mask)==SYSERR)
		return(-1);
	ttychars = l_ttychars;
	mask =  LCRTBS|LCRTERA|LCTLECH|LPENDIN|LCRTKIL;
	if((l_mask|mask) != l_mask)
		l_changed = L_MASK;
	if(ioctl(fd,TIOCLBIS,&mask)==SYSERR)
		return(-1);
	if(ttychars.t_brkc!=ESC)
	{
		ttychars.t_brkc = ESC;
		l_changed |= T_CHARS;
		if(ioctl(fd,TIOCSETC,&ttychars) == SYSERR)
			return(-1);
	}
	return(0);
}
#   else
#	ifndef PENDIN
#	    define PENDIN	0
#	endif /* PENDIN */
#	ifndef IEXTEN
#	    define IEXTEN	0
#	endif /* IEXTEN */

int tty_alt(register int fd)
{
	register Edit_t *ep = (Edit_t*)(sh_getinterp()->ed_context);
	switch(ep->e_raw)
	{
	    case ECHOMODE:
		return(-1);
	    case ALTMODE:
		return(0);
	    case RAWMODE:
		tty_cooked(fd);
	}
	if((tty_get(fd, &ttyparm)==SYSERR) || (!(ttyparm.c_lflag&ECHO)))
		return(-1);
#	ifdef FLUSHO
	    ttyparm.c_lflag &= ~FLUSHO;
#	endif /* FLUSHO */
	nttyparm = ttyparm;
	ep->e_eof = ttyparm.c_cc[VEOF];
#	ifdef ECHOCTL
	    /* escape character echos as ^[ */
	    nttyparm.c_lflag |= (ECHOE|ECHOK|ECHOCTL|PENDIN|IEXTEN);
	    nttyparm.c_cc[VEOL] = ESC;
#	else
	    /* switch VEOL2 and EOF, since EOF isn't echo'd by driver */
	    nttyparm.c_lflag |= (ECHOE|ECHOK);
	    nttyparm.c_cc[VEOF] = ESC;	/* make ESC the eof char */
#	    ifdef VEOL2
		nttyparm.c_iflag &= ~(IGNCR|ICRNL);
		nttyparm.c_iflag |= INLCR;
		nttyparm.c_cc[VEOL] = '\r';	/* make CR an eol char */
		nttyparm.c_cc[VEOL2] = ep->e_eof; /* make EOF an eol char */
#	    else
		nttyparm.c_cc[VEOL] = ep->e_eof; /* make EOF an eol char */
#	    endif /* VEOL2 */
#	endif /* ECHOCTL */
#	ifdef VREPRINT
		nttyparm.c_cc[VREPRINT] = _POSIX_DISABLE;
#	endif /* VREPRINT */
#	ifdef VDISCARD
		nttyparm.c_cc[VDISCARD] = _POSIX_DISABLE;
#	endif /* VDISCARD */
#	ifdef VWERASE
	    if(ttyparm.c_cc[VWERASE] == _POSIX_DISABLE)
		    nttyparm.c_cc[VWERASE] = cntl('W');
	    ep->e_werase = nttyparm.c_cc[VWERASE];
#	else
	    ep->e_werase = cntl('W');
#	endif /* VWERASE */
#	ifdef VLNEXT
	    if(ttyparm.c_cc[VLNEXT] == _POSIX_DISABLE )
		    nttyparm.c_cc[VLNEXT] = cntl('V');
	    ep->e_lnext = nttyparm.c_cc[VLNEXT];
#	else
	    ep->e_lnext = cntl('V');
#	endif /* VLNEXT */
	ep->e_erase = ttyparm.c_cc[VERASE];
	ep->e_kill = ttyparm.c_cc[VKILL];
	if( tty_set(fd, TCSADRAIN, &nttyparm) == SYSERR )
		return(-1);
	ep->e_ttyspeed = (cfgetospeed(&ttyparm)>=B1200?FAST:SLOW);
	ep->e_raw = ALTMODE;
	return(0);
}

#   endif /* TIOCGETC */
#endif	/* SHOPT_RAWONLY */

/*
 *	ED_WINDOW()
 *
 *	return the window size
 */
int ed_window(void)
{
	int	rows,cols;
	register char *cp = nv_getval(COLUMNS);
	if(cp)
		cols = (int)strtol(cp, (char**)0, 10)-1;
	else
	{
		astwinsize(2,&rows,&cols);
		if(--cols <0)
			cols = DFLTWINDOW-1;
	}
	if(cols < MINWINDOW)
		cols = MINWINDOW;
	else if(cols > MAXWINDOW)
		cols = MAXWINDOW;
	return(cols);
}

/*	E_FLUSH()
 *
 *	Flush the output buffer.
 *
 */

void ed_flush(Edit_t *ep)
{
	register int n = ep->e_outptr-ep->e_outbase;
	register int fd = ERRIO;
	if(n<=0)
		return;
	write(fd,ep->e_outbase,(unsigned)n);
	ep->e_outptr = ep->e_outbase;
}

/*
 * send the bell character ^G to the terminal
 */

void ed_ringbell(void)
{
	write(ERRIO,bellchr,1);
}

/*
 * send a carriage return line feed to the terminal
 */

void ed_crlf(register Edit_t *ep)
{
#ifdef cray
	ed_putchar(ep,'\r');
#endif /* cray */
#ifdef u370
	ed_putchar(ep,'\r');
#endif	/* u370 */
#ifdef VENIX
	ed_putchar(ep,'\r');
#endif /* VENIX */
	ed_putchar(ep,'\n');
	ed_flush(ep);
}
 
/*	ED_SETUP( max_prompt_size )
 *
 *	This routine sets up the prompt string
 *	The following is an unadvertised feature.
 *	  Escape sequences in the prompt can be excluded from the calculated
 *	  prompt length.  This is accomplished as follows:
 *	  - if the prompt string starts with "%\r, or contains \r%\r", where %
 *	    represents any char, then % is taken to be the quote character.
 *	  - strings enclosed by this quote character, and the quote character,
 *	    are not counted as part of the prompt length.
 */

void	ed_setup(register Edit_t *ep, int fd, int reedit)
{
	Shell_t *shp = ep->sh;
	register char *pp;
	register char *last, *prev;
	char *ppmax;
	int myquote = 0, n;
	register int qlen = 1, qwid;
	char inquote = 0;
	ep->e_fd = fd;
	ep->e_multiline = sh_isoption(SH_MULTILINE)!=0;
#ifdef SIGWINCH
	if(!(shp->sigflag[SIGWINCH]&SH_SIGFAULT))
	{
		signal(SIGWINCH,sh_fault);
		shp->sigflag[SIGWINCH] |= SH_SIGFAULT;
	}
	pp = shp->st.trapcom[SIGWINCH];
	shp->st.trapcom[SIGWINCH] = 0;
	sh_fault(SIGWINCH);
	shp->st.trapcom[SIGWINCH] = pp;
	ep->sh->winch = 0;
#endif
#if KSHELL
	ep->e_stkptr = stakptr(0);
	ep->e_stkoff = staktell();
	if(!(last = shp->prompt))
		last = "";
	shp->prompt = 0;
#else
	last = ep->e_prbuff;
#endif /* KSHELL */
	if(shp->hist_ptr)
	{
		register History_t *hp = shp->hist_ptr;
		ep->e_hismax = hist_max(hp);
		ep->e_hismin = hist_min(hp);
	}
	else
	{
		ep->e_hismax = ep->e_hismin = ep->e_hloff = 0;
	}
	ep->e_hline = ep->e_hismax;
	if(!sh_isoption(SH_VI) && !sh_isoption(SH_EMACS) && !sh_isoption(SH_GMACS))
		ep->e_wsize = MAXLINE;
	else
		ep->e_wsize = ed_window()-2;
	ep->e_winsz = ep->e_wsize+2;
	ep->e_crlf = 1;
	ep->e_plen = 0;
	pp = ep->e_prompt;
	ppmax = pp+PRSIZE-1;
	*pp++ = '\r';
	{
		register int c;
		while(prev = last, c = mbchar(last)) switch(c)
		{
			case ESC:
			{
				int skip=0;
				ep->e_crlf = 0;
				*pp++ = c;
				for(n=1; c = *last++; n++)
				{
					if(pp < ppmax)
						*pp++ = c;
					if(c=='\a' || c==ESC || c=='\r')
						break;
					if(skip || (c>='0' && c<='9'))
						continue;
					if(n>1 && c==';')
						skip = 1;
					else if(n>2 || (c!= '[' &&  c!= ']'))
						break;
				}
				if(c==0 || c==ESC || c=='\r')
					last--;
				qlen += (n+1);
				break;
			}
			case '\b':
				if(pp>ep->e_prompt+1)
					pp--;
				break;
			case '\r':
				if(pp == (ep->e_prompt+2)) /* quote char */
					myquote = *(pp-1);
				/*FALLTHROUGH*/

			case '\n':
				/* start again */
				ep->e_crlf = 1;
				qlen = 1;
				inquote = 0;
				pp = ep->e_prompt+1;
				break;

			case '\t':
				/* expand tabs */
				while((pp-ep->e_prompt)%TABSIZE)
				{
					if(pp >= ppmax)
						break;
					*pp++ = ' ';
				}
				break;

			case '\a':
				/* cut out bells */
				break;

			default:
				if(c==myquote)
				{
					qlen += inquote;
					inquote ^= 1;
				}
				if(pp < ppmax)
				{
					if(inquote)
						qlen++;
					else if(!is_print(c))
						ep->e_crlf = 0;
					if((qwid = last - prev) > 1)
						qlen += qwid - mbwidth(c);
					while(prev < last && pp < ppmax)
						*pp++ = *prev++;
				}
				break;
		}
	}
	if(pp-ep->e_prompt > qlen)
		ep->e_plen = pp - ep->e_prompt - qlen;
	*pp = 0;
	if(!ep->e_multiline && (ep->e_wsize -= ep->e_plen) < 7)
	{
		register int shift = 7-ep->e_wsize;
		ep->e_wsize = 7;
		pp = ep->e_prompt+1;
		strcpy(pp,pp+shift);
		ep->e_plen -= shift;
		last[-ep->e_plen-2] = '\r';
	}
	sfsync(sfstderr);
	if(fd == sffileno(sfstderr))
	{
		/* can't use output buffer when reading from stderr */
		static char *buff;
		if(!buff)
			buff = (char*)malloc(MAXLINE);
		ep->e_outbase = ep->e_outptr = buff;
		ep->e_outlast = ep->e_outptr + MAXLINE;
		return;
	}
	qlen = sfset(sfstderr,SF_READ,0);
	/* make sure SF_READ not on */
	ep->e_outbase = ep->e_outptr = (char*)sfreserve(sfstderr,SF_UNBOUND,SF_LOCKR);
	ep->e_outlast = ep->e_outptr + sfvalue(sfstderr);
	if(qlen)
		sfset(sfstderr,SF_READ,1);
	sfwrite(sfstderr,ep->e_outptr,0);
	ep->e_eol = reedit;
	if(ep->e_multiline)
	{
#ifdef _cmd_tput
		char *term;
		if(!ep->e_term)
			ep->e_term = nv_search("TERM",shp->var_tree,0);
		if(ep->e_term && (term=nv_getval(ep->e_term)) && strlen(term)<sizeof(ep->e_termname) && strcmp(term,ep->e_termname))
		{
			sh_trap(".sh.subscript=$(tput cuu1 2>/dev/null)",0);
			if(pp=nv_getval(SH_SUBSCRNOD))
				strncpy(CURSOR_UP,pp,sizeof(CURSOR_UP)-1);
			nv_unset(SH_SUBSCRNOD);
			strcpy(ep->e_termname,term);
		}
#endif
		ep->e_wsize = MAXLINE - (ep->e_plen+1);
	}
	if(ep->e_default && (pp = nv_getval(ep->e_default)))
	{
		n = strlen(pp);
		if(n > LOOKAHEAD)
			n = LOOKAHEAD;
		ep->e_lookahead = n;
		while(n-- > 0)
			ep->e_lbuf[n] = *pp++;
		ep->e_default = 0;
	}
}

static void ed_putstring(register Edit_t *ep, const char *str)
{
	register int c;
	while(c = *str++)
		ed_putchar(ep,c);
}

static void ed_nputchar(register Edit_t *ep, int n, int c)
{
	while(n-->0)
		ed_putchar(ep,c);
}

/*
 * Do read, restart on interrupt unless SH_SIGSET or SH_SIGTRAP is set
 * Use sfpkrd() to poll() or select() to wait for input if possible
 * Unfortunately, systems that get interrupted from slow reads update
 * this access time for for the terminal (in violation of POSIX).
 * The fixtime() macro, resets the time to the time at entry in
 * this case.  This is not necessary for systems that can handle
 * sfpkrd() correctly (i,e., those that support poll() or select()
 */
int ed_read(void *context, int fd, char *buff, int size, int reedit)
{
	register Edit_t *ep = (Edit_t*)context;
	register int rv= -1;
	register int delim = (ep->e_raw==RAWMODE?'\r':'\n');
	Shell_t *shp = ep->sh;
	int mode = -1;
	int (*waitevent)(int,long,int) = shp->waitevent;
	if(ep->e_raw==ALTMODE)
		mode = 1;
	if(size < 0)
	{
		mode = 1;
		size = -size;
	}
	sh_onstate(SH_TTYWAIT);
	errno = EINTR;
	shp->waitevent = 0;
	while(rv<0 && errno==EINTR)
	{
		if(shp->trapnote&(SH_SIGSET|SH_SIGTRAP))
			goto done;
		if(ep->sh->winch && sh_isstate(SH_INTERACTIVE) && (sh_isoption(SH_VI) || sh_isoption(SH_EMACS)))
		{
			Edpos_t	lastpos;
			int	n, rows, newsize;
			/* move cursor to start of first line */
			ed_putchar(ep,'\r');
			ed_flush(ep);
			astwinsize(2,&rows,&newsize);
			n = (ep->e_plen+ep->e_cur)/++ep->e_winsz;
			while(n--)
				ed_putstring(ep,CURSOR_UP);
			if(ep->e_multiline && newsize>ep->e_winsz && (lastpos.line=(ep->e_plen+ep->e_peol)/ep->e_winsz))
			{
				/* clear the current command line */
				n = lastpos.line;
				while(lastpos.line--)
				{
					ed_nputchar(ep,ep->e_winsz,' ');
					ed_putchar(ep,'\n');
				}
				ed_nputchar(ep,ep->e_winsz,' ');
				while(n--)
					ed_putstring(ep,CURSOR_UP);
			}
	                ep->sh->winch = 0;
			ed_flush(ep);
			sh_delay(.05);
			astwinsize(2,&rows,&newsize);
			ep->e_winsz = newsize-1;
			if(!ep->e_multiline && ep->e_wsize < MAXLINE)
				ep->e_wsize = ep->e_winsz-2;
			ep->e_nocrnl=1;
			if(*ep->e_vi_insert)
			{
				buff[0] = ESC;
				buff[1] = cntl('L');
				buff[2] = 'a';
				return(3);
			}
			if(sh_isoption(SH_EMACS) || sh_isoption(SH_VI))
				buff[0] = cntl('L');
			return(1);
		}
		else
			ep->sh->winch = 0;
		/* an interrupt that should be ignored */
		errno = 0;
		if(!waitevent || (rv=(*waitevent)(fd,-1L,0))>=0)
			rv = sfpkrd(fd,buff,size,delim,-1L,mode);
	}
	if(rv < 0)
	{
#ifdef _hdr_utime
#		define fixtime()	if(isdevtty)utime(ep->e_tty,&utimes)
		int	isdevtty=0;
		struct stat statb;
		struct utimbuf utimes;
	 	if(errno==0 && !ep->e_tty)
		{
			if((ep->e_tty=ttyname(fd)) && stat(ep->e_tty,&statb)>=0)
			{
				ep->e_tty_ino = statb.st_ino;
				ep->e_tty_dev = statb.st_dev;
			}
		}
		if(ep->e_tty_ino && fstat(fd,&statb)>=0 && statb.st_ino==ep->e_tty_ino && statb.st_dev==ep->e_tty_dev)
		{
			utimes.actime = statb.st_atime;
			utimes.modtime = statb.st_mtime;
			isdevtty=1;
		}
#else
#		define fixtime()
#endif /* _hdr_utime */
		while(1)
		{
			rv = read(fd,buff,size);
			if(rv>=0 || errno!=EINTR)
				break;
			if(shp->trapnote&(SH_SIGSET|SH_SIGTRAP))
				goto done;
			/* an interrupt that should be ignored */
			fixtime();
		}
	}
	else if(rv>=0 && mode>0)
		rv = read(fd,buff,rv>0?rv:1);
done:
	shp->waitevent = waitevent;
	sh_offstate(SH_TTYWAIT);
	return(rv);
}


/*
 * put <string> of length <nbyte> onto lookahead stack
 * if <type> is non-zero,  the negation of the character is put
 *    onto the stack so that it can be checked for KEYTRAP
 * putstack() returns 1 except when in the middle of a multi-byte char
 */
static int putstack(Edit_t *ep,char string[], register int nbyte, int type) 
{
	register int c;
#if SHOPT_MULTIBYTE
	char *endp, *p=string;
	int size, offset = ep->e_lookahead + nbyte;
	*(endp = &p[nbyte]) = 0;
	endp = &p[nbyte];
	do
	{
		c = (int)((*p) & STRIP);
		if(c< 0x80 && c!='<')
		{
			if (type)
				c = -c;
#   ifndef CBREAK
			if(c == '\0')
			{
				/*** user break key ***/
				ep->e_lookahead = 0;
#	if KSHELL
				sh_fault(SIGINT);
				siglongjmp(ep->e_env, UINTR);
#	endif   /* KSHELL */
			}
#   endif /* CBREAK */

		}
		else
		{
		again:
			if((c=mbchar(p)) >=0)
			{
				p--;	/* incremented below */
				if(type)
					c = -c;
			}
#ifdef EILSEQ
			else if(errno == EILSEQ)
				errno = 0;
#endif
			else if((endp-p) < mbmax())
			{
				if ((c=ed_read(ep,ep->e_fd,endp, 1,0)) == 1)
				{
					*++endp = 0;
					goto again;
				}
				return(c);
			}
			else
			{
				ed_ringbell();
				c = -(int)((*p) & STRIP);
				offset += mbmax()-1;
			}
		}
		ep->e_lbuf[--offset] = c;
		p++;
	}
	while (p < endp);
	/* shift lookahead buffer if necessary */
	if(offset -= ep->e_lookahead)
	{
		for(size=offset;size < nbyte;size++)
			ep->e_lbuf[ep->e_lookahead+size-offset] = ep->e_lbuf[ep->e_lookahead+size];
	}
	ep->e_lookahead += nbyte-offset;
#else
	while (nbyte > 0)
	{
		c = string[--nbyte] & STRIP;
		ep->e_lbuf[ep->e_lookahead++] = (type?-c:c);
#   ifndef CBREAK
		if( c == '\0' )
		{
			/*** user break key ***/
			ep->e_lookahead = 0;
#	if KSHELL
			sh_fault(SIGINT);
			siglongjmp(ep->e_env, UINTR);
#	endif	/* KSHELL */
		}
#   endif /* CBREAK */
	}
#endif /* SHOPT_MULTIBYTE */
	return(1);
}

/*
 * routine to perform read from terminal for vi and emacs mode
 * <mode> can be one of the following:
 *   -2		vi insert mode - key binding is in effect
 *   -1		vi control mode - key binding is in effect
 *   0		normal command mode - key binding is in effect
 *   1		edit keys not mapped
 *   2		Next key is literal
 */
int ed_getchar(register Edit_t *ep,int mode)
{
	register int n, c;
	char readin[LOOKAHEAD+1];
	if(!ep->e_lookahead)
	{
		ed_flush(ep);
		ep->e_inmacro = 0;
		/* The while is necessary for reads of partial multbyte chars */
		*ep->e_vi_insert = (mode==-2);
		if((n=ed_read(ep,ep->e_fd,readin,-LOOKAHEAD,0)) > 0)
			n = putstack(ep,readin,n,1);
		*ep->e_vi_insert = 0;
	}
	if(ep->e_lookahead)
	{
		/* check for possible key mapping */
		if((c = ep->e_lbuf[--ep->e_lookahead]) < 0)
		{
			if(mode<=0 && ep->sh->st.trap[SH_KEYTRAP])
			{
				n=1;
				if((readin[0]= -c) == ESC)
				{
					while(1)
					{
						if(!ep->e_lookahead)
						{
							if((c=sfpkrd(ep->e_fd,readin+n,1,'\r',(mode?400L:-1L),0))>0)
								putstack(ep,readin+n,c,1);
						}
						if(!ep->e_lookahead)
							break;
						if((c=ep->e_lbuf[--ep->e_lookahead])>=0)
						{
							ep->e_lookahead++;
							break;
						}
						c = -c;
						readin[n++] = c;
						if(c>='0' && c<='9' && n>2)
							continue;
						if(n>2 || (c!= '['  &&  c!= 'O'))
							break;
					}
				}
				if(n=keytrap(ep,readin,n,LOOKAHEAD-n,mode))
				{
					putstack(ep,readin,n,0);
					c = ep->e_lbuf[--ep->e_lookahead];
				}
				else
					c = ed_getchar(ep,mode);
			}
			else
				c = -c;
		}
		/*** map '\r' to '\n' ***/
		if(c == '\r' && mode!=2)
			c = '\n';
		if(ep->e_tabcount && !(c=='\t'||c==ESC || c=='\\' || c=='=' || c==cntl('L') || isdigit(c)))
			ep->e_tabcount = 0;
	}
	else
		siglongjmp(ep->e_env,(n==0?UEOF:UINTR));
	return(c);
}

void ed_ungetchar(Edit_t *ep,register int c)
{
	if (ep->e_lookahead < LOOKAHEAD)
		ep->e_lbuf[ep->e_lookahead++] = c;
	return;
}

/*
 * put a character into the output buffer
 */

void	ed_putchar(register Edit_t *ep,register int c)
{
	char buf[8];
	register char *dp = ep->e_outptr;
	register int i,size=1;
	if(!dp)
		return;
	buf[0] = c;
#if SHOPT_MULTIBYTE
	/* check for place holder */
	if(c == MARKER)
		return;
	if((size = mbconv(buf, (wchar_t)c)) > 1)
	{
		for (i = 0; i < (size-1); i++)
			*dp++ = buf[i];
		c = buf[i];
	}
	else
	{
		buf[0] = c;
		size = 1;
	}
#endif	/* SHOPT_MULTIBYTE */
	if (buf[0] == '_' && size==1)
	{
		*dp++ = ' ';
		*dp++ = '\b';
	}
	*dp++ = c;
	*dp = '\0';
	if(dp >= ep->e_outlast)
		ed_flush(ep);
	else
		ep->e_outptr = dp;
}

/*
 * returns the line and column corresponding to offset <off> in the physical buffer
 * if <cur> is non-zero and <= <off>, then correspodning <curpos> will start the search 
 */
Edpos_t ed_curpos(Edit_t *ep,genchar *phys, int off, int cur, Edpos_t curpos)
{
	register genchar *sp=phys;
	register int c=1, col=ep->e_plen;
	Edpos_t pos;
#if SHOPT_MULTIBYTE
	char p[16];
#endif /* SHOPT_MULTIBYTE */
	if(cur && off>=cur)
	{
		sp += cur; 
		off -= cur;
		pos = curpos;
		col = pos.col;
	}
	else
	{
		pos.line = 0;
		while(col > ep->e_winsz)
		{
			pos.line++;
			col -= (ep->e_winsz+1);
		}
	}
	while(off-->0)
	{
		if(c)
			c = *sp++;
#if SHOPT_MULTIBYTE
		if(c && (mbconv(p, (wchar_t)c))==1 && p[0]=='\n')
#else
		if(c=='\n')
#endif /* SHOPT_MULTIBYTE */
			col = 0;
		else
			col++;
		if(col >  ep->e_winsz)
			col = 0;
		if(col==0)
			pos.line++;
	}
	pos.col = col;
	return(pos);
}

int ed_setcursor(register Edit_t *ep,genchar *physical,register int old,register int new,int first)
{
	static int oldline;
	register int delta;
	int clear = 0;
	Edpos_t newpos;

	delta = new - old;
	if(first < 0)
	{
		first = 0;
		clear = 1;
	}
	if( delta == 0  &&  !clear)
		return(new);
	if(ep->e_multiline)
	{
		ep->e_curpos = ed_curpos(ep, physical, old,0,ep->e_curpos);
		if(clear && old>=ep->e_peol && (clear=ep->e_winsz-ep->e_curpos.col)>0)
		{
			ed_nputchar(ep,clear,' ');
			ed_nputchar(ep,clear,'\b');
			return(new);
		}
		newpos =     ed_curpos(ep, physical, new,old,ep->e_curpos);
		if(ep->e_curpos.col==0 && ep->e_curpos.line>0 && oldline<ep->e_curpos.line && delta<0)
			ed_putstring(ep,"\r\n");
		oldline = newpos.line;
		if(ep->e_curpos.line > newpos.line)
		{
			int n,pline,plen=ep->e_plen;
			for(;ep->e_curpos.line > newpos.line; ep->e_curpos.line--)
				ed_putstring(ep,CURSOR_UP);
			pline = plen/(ep->e_winsz+1);
			if(newpos.line <= pline)
				plen -= pline*(ep->e_winsz+1);
			else
				plen = 0;
			if((n=plen- ep->e_curpos.col)>0)
			{
				ep->e_curpos.col += n;
				ed_putchar(ep,'\r');
				if(!ep->e_crlf && pline==0)
					ed_putstring(ep,ep->e_prompt);
				else
				{
					int m = ep->e_winsz+1-plen;
					ed_putchar(ep,'\n');
					n = plen;
					if(m < ed_genlen(physical))
					{
						while(physical[m] && n-->0)
							ed_putchar(ep,physical[m++]);
					}
					ed_nputchar(ep,n,' ');
					ed_putstring(ep,CURSOR_UP);
				}
			}
		}
		else if(ep->e_curpos.line < newpos.line)
		{
			ed_nputchar(ep, newpos.line-ep->e_curpos.line,'\n');
			ep->e_curpos.line = newpos.line;
			ed_putchar(ep,'\r');
			ep->e_curpos.col = 0;
		}
		delta = newpos.col - ep->e_curpos.col;
		old   =  new - delta;
	}
	else
		newpos.line=0;
	if(delta<0)
	{
		int bs= newpos.line && ep->e_plen>ep->e_winsz;
		/*** move to left ***/
		delta = -delta;
		/*** attempt to optimize cursor movement ***/
		if(!ep->e_crlf || bs || (2*delta <= ((old-first)+(newpos.line?0:ep->e_plen))) )
		{
			ed_nputchar(ep,delta,'\b');
			delta = 0;
		}
		else
		{
			if(newpos.line==0)
				ed_putstring(ep,ep->e_prompt);
			else
			{
				first = 1+(newpos.line*ep->e_winsz - ep->e_plen);
				ed_putchar(ep,'\r');
			}
			old = first;
			delta = new-first;
		}
	}
	while(delta-->0)
		ed_putchar(ep,physical[old++]);
	return(new);
}

/*
 * copy virtual to physical and return the index for cursor in physical buffer
 */
int ed_virt_to_phys(Edit_t *ep,genchar *virt,genchar *phys,int cur,int voff,int poff)
{
	register genchar *sp = virt;
	register genchar *dp = phys;
	register int c;
	genchar *curp = sp + cur;
	genchar *dpmax = phys+MAXLINE;
	int d, r;
	sp += voff;
	dp += poff;
	for(r=poff;c= *sp;sp++)
	{
		if(curp == sp)
			r = dp - phys;
#if SHOPT_MULTIBYTE
		d = mbwidth((wchar_t)c);
		if(d==1 && is_cntrl(c))
			d = -1;
		if(d>1)
		{
			/* multiple width character put in place holders */
			*dp++ = c;
			while(--d >0)
				*dp++ = MARKER;
			/* in vi mode the cursor is at the last character */
			if(dp>=dpmax)
				break;
			continue;
		}
		else
#else
		d = (is_cntrl(c)?-1:1);
#endif	/* SHOPT_MULTIBYTE */
		if(d<0)
		{
			if(c=='\t')
			{
				c = dp-phys;
				if(sh_isoption(SH_VI))
					c += ep->e_plen;
				c = TABSIZE - c%TABSIZE;
				while(--c>0)
					*dp++ = ' ';
				c = ' ';
			}
			else
			{
				*dp++ = '^';
				c = printchar(c);
			}
			/* in vi mode the cursor is at the last character */
			if(curp == sp && sh_isoption(SH_VI))
				r = dp - phys;
		}
		*dp++ = c;
		if(dp>=dpmax)
			break;
	}
	*dp = 0;
	ep->e_peol = dp-phys;
	return(r);
}

#if SHOPT_MULTIBYTE
/*
 * convert external representation <src> to an array of genchars <dest>
 * <src> and <dest> can be the same
 * returns number of chars in dest
 */

int	ed_internal(const char *src, genchar *dest)
{
	register const unsigned char *cp = (unsigned char *)src;
	register int c;
	register wchar_t *dp = (wchar_t*)dest;
	if(dest == (genchar*)roundof(cp-(unsigned char*)0,sizeof(genchar)))
	{
		genchar buffer[MAXLINE];
		c = ed_internal(src,buffer);
		ed_gencpy((genchar*)dp,buffer);
		return(c);
	}
	while(*cp)
		*dp++ = mbchar(cp);
	*dp = 0;
	return(dp-(wchar_t*)dest);
}

/*
 * convert internal representation <src> into character array <dest>.
 * The <src> and <dest> may be the same.
 * returns number of chars in dest.
 */

int	ed_external(const genchar *src, char *dest)
{
	register genchar wc;
	register int c,size;
	register char *dp = dest;
	char *dpmax = dp+sizeof(genchar)*MAXLINE-2;
	if((char*)src == dp)
	{
		char buffer[MAXLINE*sizeof(genchar)];
		c = ed_external(src,buffer);

#ifdef _lib_wcscpy
		wcscpy((wchar_t *)dest,(const wchar_t *)buffer);
#else
		strcpy(dest,buffer);
#endif
		return(c);
	}
	while((wc = *src++) && dp<dpmax)
	{
		if((size = mbconv(dp, wc)) < 0)
		{
			/* copy the character as is */
			size = 1;
			*dp = wc;
		}
		dp += size;
	}
	*dp = 0;
	return(dp-dest);
}

/*
 * copy <sp> to <dp>
 */

void	ed_gencpy(genchar *dp,const genchar *sp)
{
	dp = (genchar*)roundof((char*)dp-(char*)0,sizeof(genchar));
	sp = (const genchar*)roundof((char*)sp-(char*)0,sizeof(genchar));
	while(*dp++ = *sp++);
}

/*
 * copy at most <n> items from <sp> to <dp>
 */

void	ed_genncpy(register genchar *dp,register const genchar *sp, int n)
{
	dp = (genchar*)roundof((char*)dp-(char*)0,sizeof(genchar));
	sp = (const genchar*)roundof((char*)sp-(char*)0,sizeof(genchar));
	while(n-->0 && (*dp++ = *sp++));
}

/*
 * find the string length of <str>
 */

int	ed_genlen(register const genchar *str)
{
	register const genchar *sp = str;
	sp = (const genchar*)roundof((char*)sp-(char*)0,sizeof(genchar));
	while(*sp++);
	return(sp-str-1);
}
#endif /* SHOPT_MULTIBYTE */
#endif /* SHOPT_ESH || SHOPT_VSH */

#ifdef future
/*
 * returns 1 when <n> bytes starting at <a> and <b> are equal
 */
static int compare(register const char *a,register const char *b,register int n)
{
	while(n-->0)
	{
		if(*a++ != *b++)
			return(0);
	}
	return(1);
}
#endif

#if SHOPT_OLDTERMIO

#   include	<sys/termio.h>

#ifndef ECHOCTL
#   define ECHOCTL	0
#endif /* !ECHOCTL */
#define ott	ep->e_ott

/*
 * For backward compatibility only
 * This version will use termios when possible, otherwise termio
 */

int tcgetattr(int fd, struct termios *tt)
{
	register Edit_t *ep = (Edit_t*)(sh_getinterp()->ed_context);
	register int r,i;
	ep->e_tcgeta = 0;
	ep->e_echoctl = (ECHOCTL!=0);
	if((r=ioctl(fd,TCGETS,tt))>=0 ||  errno!=EINVAL)
		return(r);
	if((r=ioctl(fd,TCGETA,&ott)) >= 0)
	{
		tt->c_lflag = ott.c_lflag;
		tt->c_oflag = ott.c_oflag;
		tt->c_iflag = ott.c_iflag;
		tt->c_cflag = ott.c_cflag;
		for(i=0; i<NCC; i++)
			tt->c_cc[i] = ott.c_cc[i];
		ep->e_tcgeta++;
		ep->e_echoctl = 0;
	}
	return(r);
}

int tcsetattr(int fd,int mode,struct termios *tt)
{
	register Edit_t *ep = (Edit_t*)(sh_getinterp()->ed_context);
	register int r;
	if(ep->e_tcgeta)
	{
		register int i;
		ott.c_lflag = tt->c_lflag;
		ott.c_oflag = tt->c_oflag;
		ott.c_iflag = tt->c_iflag;
		ott.c_cflag = tt->c_cflag;
		for(i=0; i<NCC; i++)
			ott.c_cc[i] = tt->c_cc[i];
		if(tt->c_lflag&ECHOCTL)
		{
			ott.c_lflag &= ~(ECHOCTL|IEXTEN);
			ott.c_iflag &= ~(IGNCR|ICRNL);
			ott.c_iflag |= INLCR;
			ott.c_cc[VEOF]= ESC;  /* ESC -> eof char */
			ott.c_cc[VEOL] = '\r'; /* CR -> eol char */
			ott.c_cc[VEOL2] = tt->c_cc[VEOF]; /* EOF -> eol char */
		}
		switch(mode)
		{
			case TCSANOW:
				mode = TCSETA;
				break;
			case TCSADRAIN:
				mode = TCSETAW;
				break;
			case TCSAFLUSH:
				mode = TCSETAF;
		}
		return(ioctl(fd,mode,&ott));
	}
	return(ioctl(fd,mode,tt));
}
#endif /* SHOPT_OLDTERMIO */

#if KSHELL
/*
 * Execute keyboard trap on given buffer <inbuff> of given size <isize>
 * <mode> < 0 for vi insert mode
 */
static int keytrap(Edit_t *ep,char *inbuff,register int insize, int bufsize, int mode)
{
	register char *cp;
	int savexit;
	Shell_t *shp = ep->sh;
#if SHOPT_MULTIBYTE
	char buff[MAXLINE];
	ed_external(ep->e_inbuf,cp=buff);
#else
	cp = ep->e_inbuf;
#endif /* SHOPT_MULTIBYTE */
	inbuff[insize] = 0;
	ep->e_col = ep->e_cur;
	if(mode== -2)
	{
		ep->e_col++;
		*ep->e_vi_insert = ESC;
	}
	else
		*ep->e_vi_insert = 0;
	nv_putval(ED_CHRNOD,inbuff,NV_NOFREE);
	nv_putval(ED_COLNOD,(char*)&ep->e_col,NV_NOFREE|NV_INTEGER);
	nv_putval(ED_TXTNOD,(char*)cp,NV_NOFREE);
	nv_putval(ED_MODENOD,ep->e_vi_insert,NV_NOFREE);
	savexit = shp->savexit;
	sh_trap(shp->st.trap[SH_KEYTRAP],0);
	shp->savexit = savexit;
	if((cp = nv_getval(ED_CHRNOD)) == inbuff)
		nv_unset(ED_CHRNOD);
	else if(bufsize>0)
	{
		strncpy(inbuff,cp,bufsize);
		inbuff[bufsize-1]='\0';
		insize = strlen(inbuff);
	}
	else
		insize = 0;
	nv_unset(ED_TXTNOD);
	return(insize);
}
#endif /* KSHELL */

void	*ed_open(Shell_t *shp)
{
	Edit_t *ed = newof(0,Edit_t,1,0);
	ed->sh = shp;
	strcpy(ed->e_macro,"_??");
	return((void*)ed);
}
