/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*          Copyright (c) 1982-2012 AT&T Intellectual Property          *
*          Copyright (c) 2020-2022 Contributors to ksh 93u+m           *
*                      and is licensed under the                       *
*                 Eclipse Public License, Version 1.0                  *
*                    by AT&T Intellectual Property                     *
*                                                                      *
*                A copy of the License is available at                 *
*          http://www.eclipse.org/org/documents/epl-v10.html           *
*         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                  David Korn <dgk@research.att.com>                   *
*                                                                      *
***********************************************************************/
/*
 *  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	<fault.h>
#include	"FEATURE/options"
#include	"FEATURE/time"
#include	"FEATURE/cmds"
#ifdef _hdr_utime
#   include	<utime.h>
#   include	<ls.h>
#endif

#include	"defs.h"
#include	"variables.h"
#include	"io.h"
#include	"terminal.h"
#include	"history.h"
#include	"edit.h"
#include	"shlex.h"

static char CURSOR_UP[20] = { ESC, '[', 'A', 0 };
static char KILL_LINE[20] = { ESC, '[', 'J', 0 };
static Lex_t *savelex;


#if SHOPT_MULTIBYTE
#   define is_cntrl(c)	((c<=STRIP) && iscntrl(c))
#   define is_print(c)	((c&~STRIP) || isprint(c))
#   define genlen(str)	ed_genlen(str)
#else
#   define is_cntrl(c)	iscntrl(c)
#   define is_print(c)	isprint(c)
#   define genlen(str)	strlen(str)
#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 */

static int keytrap(Edit_t *,char*, int, int, int);

#ifndef _POSIX_DISABLE
#   define _POSIX_DISABLE	0
#endif

#define ttyparm		(ep->e_ttyparm)
#define nttyparm	(ep->e_nttyparm)
static const char bellchr[] = "\a";	/* bell char */


/*
 * 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.ed_context);
	struct termios tty;
	Sfio_t *sp;
	ep->e_savefd = -1;
	if(fd < 0 || fd > sh.lim.open_max || sh.fdstatus[fd] == IOCLOSE
	|| (sp = sh.sftable[fd]) && (sfset(sp,0,0) & SF_STRING))
		return(0);
	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.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 canonical 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.ed_context);
	if(fd >=0)
	{
		while(tcsetattr(fd, action, tty) == SYSERR)
		{
			if(errno !=EINTR)
				return(SYSERR);
			errno = 0;
		}
		ep->e_savetty = *tty;
	}
	ep->e_savefd = fd;
	return(0);
}

/*{	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.ed_context);
	if(sh.st.trap[SH_KEYTRAP] && savelex)
		memcpy(sh.lex_context,savelex,sizeof(Lex_t));
	ep->e_keytrap = 0;
	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.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|ISIG|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_intr = ttyparm.c_cc[VINTR];
	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.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.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);
}

#if SHOPT_ESH || SHOPT_VSH
/*
 * 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);
}
#endif /* SHOPT_ESH || SHOPT_VSH */

#if SHOPT_ESH || SHOPT_VSH
/*	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)
{
	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;
#if SHOPT_ESH && SHOPT_VSH
	ep->e_multiline = sh_isoption(SH_MULTILINE) && (sh_isoption(SH_EMACS) || sh_isoption(SH_GMACS) || sh_isoption(SH_VI));
#elif SHOPT_ESH
	ep->e_multiline = sh_isoption(SH_MULTILINE) && (sh_isoption(SH_EMACS) || sh_isoption(SH_GMACS));
#else
	ep->e_multiline = sh_isoption(SH_MULTILINE) && sh_isoption(SH_VI);
#endif
#ifdef SIGWINCH
	if(!(sh.sigflag[SIGWINCH]&SH_SIGFAULT))
	{
		signal(SIGWINCH,sh_fault);
		sh.sigflag[SIGWINCH] |= SH_SIGFAULT;
	}
	pp = sh.st.trapcom[SIGWINCH];
	sh.st.trapcom[SIGWINCH] = 0;
	sh_fault(SIGWINCH);
	sh.st.trapcom[SIGWINCH] = pp;
	sh.winch = 0;
#endif
#if SHOPT_EDPREDICT
	ep->hlist = 0;
	ep->nhlist = 0;
	ep->hoff = 0;
#endif /* SHOPT_EDPREDICT */
	ep->e_stkoff = staktell();
	ep->e_stkptr = stakfreeze(0);
	if(!(last = sh.prompt))
		last = "";
	sh.prompt = 0;
	if(sh.hist_ptr)
	{
		register History_t *hp = sh.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 SHOPT_ESH && SHOPT_VSH
	if(!sh_isoption(SH_VI) && !sh_isoption(SH_EMACS) && !sh_isoption(SH_GMACS))
#elif SHOPT_ESH
	if(!sh_isoption(SH_EMACS) && !sh_isoption(SH_GMACS))
#elif SHOPT_VSH
	if(!sh_isoption(SH_VI))
#else
	if(1)
#endif /* SHOPT_ESH && SHOPT_VSH */
		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;
	/*
	 * Prepare e_prompt buffer for use when redrawing the command line.
	 * Use only the last line of the potentially multi-line prompt.
	 */
	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;
				if(*last == ']')
				{
					/*
					 * Cut out dtterm/xterm Operating System Commands that set window/icon title, etc.
					 * See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
					 */
					char *cp = last + 1;
					while(*cp >= '0' && *cp <= '9')
						cp++;
					if(*cp++ == ';')
					{
						while(c = *cp++)
						{
							if(c == '\a')			/* legacy terminator */
								break;
							if(c == ESC && *cp == '\\')	/* recommended terminator */
							{
								cp++;
								break;
							}
						}
						if(!c)
							break;
						last = cp;
						continue;
					}
				}
				/*
				 * Try to add the length of included escape sequences to qlen
				 * which is subtracted from the physical length of the prompt.
				 */
				ep->e_crlf = 0;
				if(pp<ppmax)
					*pp++ = ESC;
				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'))
					{
						skip = 0;
						continue;
					}
					if(n==3 && (c=='?' || c=='!'))
						continue;
					else if(n>1 && c==';')
						skip = 1;
					else if(n>2 || (c!='[' && 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 SHOPT_ESH || SHOPT_VSH
	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';
	}
#endif /* SHOPT_ESH || SHOPT_VSH */
	sfsync(sfstderr);
	if(fd == sffileno(sfstderr))
	{
		/* can't use output buffer when reading from stderr */
		static char *buff;
		if(!buff)
			buff = (char*)sh_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 SHOPT_ESH || SHOPT_VSH
	if(ep->e_multiline)
	{
#if defined(_pth_tput) && (_tput_terminfo || _tput_termcap)
		char *term;
		if(!ep->e_term)
			ep->e_term = nv_search("TERM",sh.var_tree,0);
		if(ep->e_term && (term=nv_getval(ep->e_term)) && strlen(term)<sizeof(ep->e_termname) && strcmp(term,ep->e_termname))
		{
			Shopt_t o = sh.options;
			sigblock(SIGINT);
			sh_offoption(SH_RESTRICTED);
			sh_offoption(SH_VERBOSE);
			sh_offoption(SH_XTRACE);
			/* get the cursor up sequence from tput */
#if _tput_terminfo
			sh_trap(".sh.subscript=$(" _pth_tput " cuu1 2>/dev/null)",0);
#elif _tput_termcap
			sh_trap(".sh.subscript=$(" _pth_tput " up 2>/dev/null)",0);
#else
#error no tput method
#endif
			if((pp = nv_getval(SH_SUBSCRNOD)) && strlen(pp) < sizeof(CURSOR_UP))
				strcpy(CURSOR_UP,pp);
			else
				CURSOR_UP[0] = '\0';  /* no escape sequence is better than a faulty one */
			nv_unset(SH_SUBSCRNOD);
			strcpy(ep->e_termname,term);
			sh.options = o;
			sigrelease(SIGINT);
		}
#endif
		ep->e_wsize = MAXLINE - (ep->e_plen+1);
	}
#endif /* SHOPT_ESH || SHOPT_VSH */
	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;
	}
	if(sh.st.trap[SH_KEYTRAP])
	{
		if(!savelex)
			savelex = (Lex_t*)sh_malloc(sizeof(Lex_t));
		memcpy(savelex, sh.lex_context, sizeof(Lex_t));
	}
}
#endif /* SHOPT_ESH || SHOPT_VSH */

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 select(2) (via sfpkrd()) to wait for input if possible
 *
 * The return value is the number of bytes read, or < 0 for EOF.
 *
 * Unfortunately, systems that get interrupted from slow reads update
 * this access time 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 have 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)?nttyparm.c_cc[VEOL]:'\n');
	int mode = -1;
	int (*waitevent)(int,long,int) = sh.waitevent;
	/* sfpkrd must use select(2) to intercept SIGWINCH for ed_read */
	if(ep->e_raw==ALTMODE)
		mode = 2;
	if(size < 0)
	{
		mode = 2;
		size = -size;
	}
	sh_onstate(SH_TTYWAIT);
	errno = EINTR;
	sh.waitevent = 0;
	while(rv<0 && errno==EINTR)
	{
		if(sh.trapnote&(SH_SIGSET|SH_SIGTRAP))
			goto done;
#if SHOPT_ESH || SHOPT_VSH
#if SHOPT_ESH && SHOPT_VSH
		if(sh.winch && sh_isstate(SH_INTERACTIVE) && (sh_isoption(SH_VI) || sh_isoption(SH_EMACS) || sh_isoption(SH_GMACS)))
#elif SHOPT_ESH
		if(sh.winch && sh_isstate(SH_INTERACTIVE) && (sh_isoption(SH_EMACS) || sh_isoption(SH_GMACS)))
#else
		if(sh.winch && sh_isstate(SH_INTERACTIVE) && sh_isoption(SH_VI))
#endif
		{
			/* redraw the prompt after receiving SIGWINCH */
			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);
			}
			ed_flush(ep);
			sh_delay(.05,0);
			astwinsize(2,&rows,&newsize);
			ep->e_winsz = newsize-1;
			if(ep->e_winsz < MINWINDOW)
				ep->e_winsz = MINWINDOW;
			if(!ep->e_multiline && ep->e_wsize < MAXLINE)
				ep->e_wsize = ep->e_winsz-2;
#if SHOPT_ESH && SHOPT_VSH
			if(sh_isoption(SH_VI))
				vi_redraw(ep->e_vi);
			else
				emacs_redraw(ep->e_emacs);
#elif SHOPT_VSH
			vi_redraw(ep->e_vi);
#elif SHOPT_ESH
			emacs_redraw(ep->e_emacs);
#endif
		}
#endif /* SHOPT_ESH || SHOPT_VSH */
		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(sh.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:
	sh.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;
				sh_fault(SIGINT);
				siglongjmp(ep->e_env, UINTR);
			}
#   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;
			sh_fault(SIGINT);
			siglongjmp(ep->e_env, UINTR);
		}
#   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;
		*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 && -c == ep->e_intr)
			{
				killpg(getpgrp(),SIGINT);
				siglongjmp(ep->e_env, UINTR);
			}
			if(mode<=0 && sh.st.trap[SH_KEYTRAP])
			{
				ep->e_keytrap = 1;
				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);
				ep->e_keytrap = 0;
			}
			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);
}

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

#if SHOPT_ESH || SHOPT_VSH
/*
 * 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;
}
#endif /* SHOPT_ESH || SHOPT_VSH */

#if SHOPT_ESH || SHOPT_VSH
/*
 * returns the line and column corresponding to offset <off> in the physical buffer
 * if <cur> is non-zero and <= <off>, then corresponding <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);
}
#endif /* SHOPT_ESH || SHOPT_VSH */

#if SHOPT_ESH || SHOPT_VSH
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 < 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);
}
#endif /* SHOPT_ESH || SHOPT_VSH */

#if SHOPT_ESH || SHOPT_VSH
/*
 * 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;
				c += ep->e_plen;
				c = TABSIZE - c%TABSIZE;
				while(--c>0)
					*dp++ = ' ';
				c = ' ';
			}
			else
			{
				*dp++ = '^';
				c = printchar(c);
			}
			if(curp == sp)
				r = dp - phys;
		}
		*dp++ = c;
		if(dp>=dpmax)
			break;
	}
	*dp = 0;
	ep->e_peol = dp-phys;
	return(r);
}
#endif /* SHOPT_ESH || SHOPT_VSH */

#if (SHOPT_ESH || SHOPT_VSH) && 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);
}
#endif /* (SHOPT_ESH || SHOPT_VSH) && SHOPT_MULTIBYTE */

#if SHOPT_MULTIBYTE
/*
 * 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 char *dp = dest;
	char *dpmax = dp+sizeof(genchar)*MAXLINE-2;
	if((char*)src == dp)
	{
		int c;
		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)
	{
		ssize_t size;
		if((size = mbconv(dp, wc)) < 0)
		{
			/* copy the character as is */
			size = 1;
			*dp = wc;
		}
		dp += size;
	}
	*dp = 0;
	return(dp-dest);
}
#endif /* SHOPT_MULTIBYTE */

#if (SHOPT_ESH || SHOPT_VSH) && SHOPT_MULTIBYTE
/*
 * 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++);
}
#endif /* (SHOPT_ESH || SHOPT_VSH) && SHOPT_MULTIBYTE */

#if (SHOPT_ESH || SHOPT_VSH) && SHOPT_MULTIBYTE
/*
 * 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++));
}
#endif /* (SHOPT_ESH || SHOPT_VSH) && SHOPT_MULTIBYTE */

#if (SHOPT_ESH || SHOPT_VSH) && SHOPT_MULTIBYTE
/*
 * 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_ESH || SHOPT_VSH) && SHOPT_MULTIBYTE */

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

/*
 * 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;
#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 = sh.savexit;
	sh_trap(sh.st.trap[SH_KEYTRAP],0);
	sh.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);
}

#if SHOPT_EDPREDICT
static int ed_sortdata(const char *s1, const char *s2)
{
	Histmatch_t *m1 = (Histmatch_t*)s1;
	Histmatch_t *m2 = (Histmatch_t*)s2;
	return(strcmp(m1->data,m2->data));
}

static int ed_sortindex(const char *s1, const char *s2)
{
	Histmatch_t *m1 = (Histmatch_t*)s1;
	Histmatch_t *m2 = (Histmatch_t*)s2;
	return(m2->index-m1->index);
}

static int ed_histlencopy(const char *cp, char *dp)
{
	int c,n=1,col=1;
	const char *oldcp=cp;
	for(n=0;c = mbchar(cp);oldcp=cp,col++)
	{
		if(c=='\n' && *cp)
		{
			n += 2;
			if(dp)
			{
				*dp++ = '^';
				*dp++ = 'J';
				col +=2;
			}
		}
		else if(c=='\t')
		{
			n++;
			if(dp)
				*dp++ = ' ';
		}
		else
		{
			n  += cp-oldcp;
			if(dp)
			{
				while(oldcp < cp)
					*dp++ = *oldcp++;
			}
		}
	}
	return(n);
}

int ed_histgen(Edit_t *ep,const char *pattern)
{
	Histmatch_t	*mp,*mplast=0;
	History_t	*hp;
	off_t		offset;
	int 		ac=0,l,n,index1,index2;
	size_t		m;
	char		*cp, **argv=0, **av, **ar;
	static		int maxmatch;
	if(!(hp=sh.hist_ptr) && (!nv_getval(HISTFILE) || !sh_histinit()))
		return(0);
	if(ep->e_cur <=2)
		maxmatch = 0;
	else if(maxmatch && ep->e_cur > maxmatch)
	{
		ep->hlist = 0;
		ep->hfirst = 0;
		return(0);
	}
	hp = sh.hist_ptr;
	if(*pattern=='#' && *++pattern=='#')
		return(0);
	cp = stakalloc(m=strlen(pattern)+6);
	sfsprintf(cp,m,"@(%s)*%c",pattern,0);
	if(ep->hlist)
	{
		m = strlen(ep->hpat)-4;
		if(memcmp(pattern,ep->hpat+2,m)==0)
		{
			n = strcmp(cp,ep->hpat)==0;
			for(argv=av=(char**)ep->hlist,mp=ep->hfirst; mp;mp= mp->next)
			{
				if(n || strmatch(mp->data,cp))
					*av++ = (char*)mp;
			}
			*av = 0;
			ep->hmax = av-argv;
			if(ep->hmax==0)
				maxmatch = ep->e_cur;
			return(ep->hmax=av-argv);
		}
		stakset(ep->e_stkptr,ep->e_stkoff);
	}
	if((m=strlen(cp)) >= sizeof(ep->hpat))
		m = sizeof(ep->hpat)-1;
	memcpy(ep->hpat,cp,m);
	ep->hpat[m] = 0;
	pattern = cp;
	index1 = (int)hp->histind;
	for(index2=index1-hp->histsize; index1>index2; index1--)
	{
		offset = hist_tell(hp,index1);
		sfseek(hp->histfp,offset,SEEK_SET);
		if(!(cp = sfgetr(hp->histfp,0,0)))
			continue;
		if(*cp=='#')
			continue;
		if(strmatch(cp,pattern))
		{
			l = ed_histlencopy(cp,(char*)0);
			mp = (Histmatch_t*)stakalloc(sizeof(Histmatch_t)+l);
			mp->next = mplast;
			mplast = mp;
			mp->len = l;
			ed_histlencopy(cp,mp->data);
			mp->count = 1;
			mp->data[l] = 0;
			mp->index = index1;
			ac++;
		}
	}
	if(ac>0)
	{
		l = ac;
		argv = av  = (char**)stakalloc((ac+1)*sizeof(char*));
		for(; l>=0 && (*av= (char*)mp); mp=mp->next,av++)
			l--;
		*av = 0;
		strsort(argv,ac,ed_sortdata);
		mplast = (Histmatch_t*)argv[0];
		for(ar= av= &argv[1]; mp=(Histmatch_t*)*av; av++)
		{
			if(strcmp(mp->data,mplast->data)==0)
			{
				mplast->count++;
				if(mp->index> mplast->index)
					mplast->index = mp->index;
				continue;
			}
			*ar++ = (char*)(mplast=mp);
		}
		*ar = 0;
		mplast->next = 0;
		ac = ar-argv;
		strsort(argv,ac,ed_sortindex);
		mplast = (Histmatch_t*)argv[0];
		for(av= &argv[1]; mp=(Histmatch_t*)*av; av++, mplast=mp)
			mplast->next = mp;
		mplast->next = 0;
	}
	if (argv)
	{
		ep->hlist = (Histmatch_t**)argv;
		ep->hfirst = ep->hlist?ep->hlist[0]:0;
	}
	else
		ep->hfirst = 0;
	return(ep->hmax=ac);
}

#if SHOPT_ESH || SHOPT_VSH
void	ed_histlist(Edit_t *ep,int n)
{
	Histmatch_t	*mp,**mpp = ep->hlist+ep->hoff;
	int		i,last=0,save[2];
	if(n)
	{
		/* don't bother updating the screen if there is typeahead */
		if(!ep->e_lookahead && sfpkrd(ep->e_fd,save,1,'\r',200L,-1)>0)
			ed_ungetchar(ep,save[0]);
		if(ep->e_lookahead)
			return;
		ed_putchar(ep,'\n');
		ed_putchar(ep,'\r');
	}
	else
	{
		stakset(ep->e_stkptr,ep->e_stkoff);
		ep->hlist = 0;
		ep->nhlist = 0;
	}
	ed_putstring(ep,KILL_LINE);
	if(n)
	{
		for(i=1; (mp= *mpp) && i <= 16 ; i++,mpp++)
		{
			last = 0;
			if(mp->len >= ep->e_winsz-4)
			{
				last = ep->e_winsz-4;
				save[0] = mp->data[last-1];
				save[1] = mp->data[last];
				mp->data[last-1] = '\n';
				mp->data[last] = 0;
			}
			ed_putchar(ep,i<10?' ':'1');
			ed_putchar(ep,i<10?'0'+i:'0'+i-10);
			ed_putchar(ep,')');
			ed_putchar(ep,' ');
			ed_putstring(ep,mp->data);
			if(last)
			{
				mp->data[last-1] = save[0];
				mp->data[last] = save[1];
			}
			ep->nhlist = i;
		}
		last = i-1;
		while(i-->0)
			ed_putstring(ep,CURSOR_UP);
	}
	ed_flush(ep);
}
#endif /* SHOPT_ESH || SHOPT_VSH */

#endif /* SHOPT_EDPREDICT */

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

#undef ioctl
int	sh_ioctl(int fd, int cmd, void* val, int sz)
{
	int r,err=errno;
	if(sz == sizeof(void*))
	{
		while((r=ioctl(fd,cmd,val)) < 0 && errno==EINTR)
			errno = err;
	}
	else
	{
		Sflong_t l = (Sflong_t)val;
		if(sizeof(val)==sizeof(long))
		{
			while((r=ioctl(fd,cmd,(unsigned long)l)) < 0 && errno==EINTR)
				errno = err;
		}
		else if(sizeof(int)!=sizeof(long))
		{
			while((r=ioctl(fd,cmd,(unsigned int)l)) < 0 && errno==EINTR)
				errno = err;
		}
	}
	return(r);
}

#ifdef _lib_tcgetattr
#   undef tcgetattr
int sh_tcgetattr(int fd, struct termios *tty)
    {
	int r,err = errno;
	while((r=tcgetattr(fd,tty)) < 0 && errno==EINTR)
		errno = err;
	return(r);
    }

#   undef tcsetattr
int sh_tcsetattr(int fd, int cmd, struct termios *tty)
    {
	int r,err = errno;
	while((r=tcsetattr(fd,cmd,tty)) < 0 && errno==EINTR)
		errno = err;
	return(r);
    }
#endif
