emacs.c   [plain text]


/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*          Copyright (c) 1982-2007 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
/* Original version by Michael T. Veach 
 * Adapted for ksh by David Korn */
/* EMACS_MODES: c tabstop=4 

One line screen editor for any program

*/


/*	The following is provided by:
 *
 *			Matthijs N. Melchior
 *			AT&T Network Systems International
 *			APT Nederland
 *			HV BZ335 x2962
 *			hvlpb!mmelchio
 *
 *  These are now on by default
 *
 *  ESH_NFIRST
 *	-  A ^N as first history related command after the prompt will move
 *	   to the next command relative to the last known history position.
 *	   It will not start at the position where the last command was entered
 *	   as is done by the ^P command.  Every history related command will
 *	   set both the current and last position.  Executing a command will
 *	   only set the current position.
 *
 *  ESH_KAPPEND
 *	-  Successive kill and delete commands will accumulate their data
 *	   in the kill buffer, by appending or prepending as appropriate.
 *	   This mode will be reset by any command not adding something to the
 *	   kill buffer.
 *
 *  ESH_BETTER
 *	-  Some enhancements:
 *		- argument for a macro is passed to its replacement
 *		- ^X^H command to find out about history position (debugging)
 *		- ^X^D command to show any debugging info
 *
 *  I do not pretend these for changes are completely independent,
 *  but you can use them to seperate features.
 */

#include	<ast.h>
#include	<ctype.h>
#include	"FEATURE/cmds"
#if KSHELL
#   include	"defs.h"
#endif	/* KSHELL */
#include	"io.h"

#include	"history.h"
#include	"edit.h"
#include	"terminal.h"

#define ESH_NFIRST
#define ESH_KAPPEND
#define ESH_BETTER

#undef putchar
#define putchar(ed,c)	ed_putchar(ed,c)
#define beep()		ed_ringbell()


#if SHOPT_MULTIBYTE
#   define gencpy(a,b)	ed_gencpy(a,b)
#   define genncpy(a,b,n)	ed_genncpy(a,b,n)
#   define genlen(str)	ed_genlen(str)
    static int	print(int);
    static int	_isword(int);
#   define  isword(c)	_isword(out[c])

#else
#   define gencpy(a,b)	strcpy((char*)(a),(char*)(b))
#   define genncpy(a,b,n)	strncpy((char*)(a),(char*)(b),n)
#   define genlen(str)	strlen(str)
#   define print(c)	isprint(c)
#   define isword(c)	(isalnum(out[c]) || (out[c]=='_'))
#endif /*SHOPT_MULTIBYTE */

typedef struct _emacs_
{
	genchar *screen;	/* pointer to window buffer */
	genchar *cursor;	/* Cursor in real screen */
	int 	mark;
	int 	in_mult;
	char	cr_ok;
	char	CntrlO;
	char	overflow;		/* Screen overflow flag set */
	char	scvalid;		/* Screen is up to date */
	int	offset;		/* Screen offset */
	enum
	{
		CRT=0,	/* Crt terminal */
		PAPER	/* Paper terminal */
	} terminal;
	Histloc_t _location;
	int	prevdirection; 
	Edit_t	*ed;	/* pointer to edit data */
} Emacs_t;

#define	editb		(*ep->ed)
#define eol		editb.e_eol
#define cur		editb.e_cur
#define hline		editb.e_hline
#define hloff		editb.e_hloff
#define hismin		editb.e_hismin
#define usrkill		editb.e_kill
#define usrlnext	editb.e_lnext
#define usreof		editb.e_eof
#define usrerase	editb.e_erase
#define crallowed	editb.e_crlf
#define Prompt		editb.e_prompt
#define plen		editb.e_plen
#define kstack		editb.e_killbuf
#define lstring		editb.e_search
#define lookahead	editb.e_lookahead
#define env		editb.e_env
#define raw		editb.e_raw
#define histlines	editb.e_hismax
#define w_size		editb.e_wsize
#define drawbuff	editb.e_inbuf
#define killing		editb.e_mode
#define location	ep->_location

#define LBUF	100
#define KILLCHAR	UKILL
#define ERASECHAR	UERASE
#define EOFCHAR		UEOF
#define LNEXTCHAR		ULNEXT
#define DELETE		('a'==97?0177:7)

/**********************
A large lookahead helps when the user is inserting
characters in the middle of the line.
************************/


typedef enum
{
	FIRST,		/* First time thru for logical line, prompt on screen */
	REFRESH,	/* Redraw entire screen */
	APPEND,		/* Append char before cursor to screen */
	UPDATE,		/* Update the screen as need be */
	FINAL		/* Update screen even if pending look ahead */
} Draw_t;

static void draw(Emacs_t*,Draw_t);
static int escape(Emacs_t*,genchar*, int);
static void putstring(Emacs_t*,char*);
static void search(Emacs_t*,genchar*,int);
static void setcursor(Emacs_t*,int, int);
static void show_info(Emacs_t*,const char*);
static void xcommands(Emacs_t*,int);

int ed_emacsread(void *context, int fd,char *buff,int scend, int reedit)
{
	Edit_t *ed = (Edit_t*)context;
	register int c;
	register int i;
	register genchar *out;
	register int count;
	register Emacs_t *ep = ed->e_emacs;
	int adjust,oadjust;
	char backslash;
	genchar *kptr;
	char prompt[PRSIZE];
	genchar Screen[MAXLINE];
	if(!ep)
	{
		ep = ed->e_emacs = newof(0,Emacs_t,1,0);
		ep->ed = ed;
		ep->prevdirection =  1;
		location.hist_command =  -5;
	}
	Prompt = prompt;
	ep->screen = Screen;
	if(tty_raw(ERRIO,0) < 0)
	{
		 return(reedit?reedit:ed_read(context, fd,buff,scend,0));
	}
	raw = 1;
	/* This mess in case the read system call fails */
	
	ed_setup(ep->ed,fd,reedit);
	out = (genchar*)buff;
#if SHOPT_MULTIBYTE
	out = (genchar*)roundof((char*)out-(char*)0,sizeof(genchar));
	ed_internal(buff,out);
#endif /* SHOPT_MULTIBYTE */
	if(!kstack)
	{
		kstack = (genchar*)malloc(CHARSIZE*MAXLINE);
		kstack[0] = '\0';
	}
	drawbuff = out;
#ifdef ESH_NFIRST
	if (location.hist_command == -5)		/* to be initialized */
	{
		kstack[0] = '\0';		/* also clear kstack... */
		location.hist_command = hline;
		location.hist_line = hloff;
	}
	if (location.hist_command <= hismin)	/* don't start below minimum */
	{
		location.hist_command = hismin + 1;
		location.hist_line = 0;
	}
	ep->in_mult = hloff;			/* save pos in last command */
#endif /* ESH_NFIRST */
	i = sigsetjmp(env,0);
	if (i !=0)
	{
		tty_cooked(ERRIO);
		if (i == UEOF)
		{
			return(0); /* EOF */
		}
		return(-1); /* some other error */
	}
	out[reedit] = 0;
	if(scend+plen > (MAXLINE-2))
		scend = (MAXLINE-2)-plen;
	ep->mark = 0;
	cur = eol;
	draw(ep,reedit?REFRESH:FIRST);
	adjust = -1;
	backslash = 0;
	if (ep->CntrlO)
	{
#ifdef ESH_NFIRST
		ed_ungetchar(ep->ed,cntl('N'));
#else
		location = hist_locate(sh.hist_ptr,location.hist_command,location.hist_line,1);
		if (location.hist_command < histlines)
		{
			hline = location.hist_command;
			hloff = location.hist_line;
			hist_copy((char*)kstack,MAXLINE, hline,hloff);
#   if SHOPT_MULTIBYTE
			ed_internal((char*)kstack,kstack);
#   endif /* SHOPT_MULTIBYTE */
			ed_ungetchar(ep->ed,cntl('Y'));
		}
#endif /* ESH_NFIRST */
	}
	ep->CntrlO = 0;
	while ((c = ed_getchar(ep->ed,0)) != (-1))
	{
		if (backslash)
		{
			backslash = 0;
			if (c==usrerase||c==usrkill||(!print(c) &&
				(c!='\r'&&c!='\n')))
			{
				/* accept a backslashed character */
				cur--;
				out[cur++] = c;
				out[eol] = '\0';
				draw(ep,APPEND);
				continue;
			}
		}
		if (c == usrkill)
		{
			c = KILLCHAR ;
		}
		else if (c == usrerase)
		{
			c = ERASECHAR ;
		} 
		else if (c == usrlnext)
		{
			c = LNEXTCHAR ;
		}
		else if ((c == usreof)&&(eol == 0))
		{
			c = EOFCHAR;
		}
#ifdef ESH_KAPPEND
		if (--killing <= 0)	/* reset killing flag */
			killing = 0;
#endif
		oadjust = count = adjust;
		if(count<0)
			count = 1;
		adjust = -1;
		i = cur;
		switch(c)
		{
		case LNEXTCHAR:
			c = ed_getchar(ep->ed,2);
			goto do_default_processing;
		case cntl('V'):
			show_info(ep,fmtident(e_version));
			continue;
		case '\0':
			ep->mark = i;
			continue;
		case cntl('X'):
			xcommands(ep,count);
			continue;
		case EOFCHAR:
			ed_flush(ep->ed);
			tty_cooked(ERRIO);
			return(0);
#ifdef u370
		case cntl('S') :
		case cntl('Q') :
			continue;
#endif	/* u370 */
		case '\t':
			if(cur>0  && ep->ed->sh->nextprompt)
			{
				if(ep->ed->e_tabcount==0)
				{
					ep->ed->e_tabcount=1;
					ed_ungetchar(ep->ed,ESC);
					goto do_escape;
				}
				else if(ep->ed->e_tabcount==1)
				{
					ed_ungetchar(ep->ed,'=');
					goto do_escape;
				}
				ep->ed->e_tabcount = 0;
			}
		do_default_processing:
		default:

			if ((eol+1) >= (scend)) /*  will not fit on line */
			{
				ed_ungetchar(ep->ed,c); /* save character for next line */
				goto process;
			}
			for(i= ++eol; i>cur; i--)
				out[i] = out[i-1];
			backslash =  (c == '\\');
			out[cur++] = c;
			draw(ep,APPEND);
			continue;
		case cntl('Y') :
			{
				c = genlen(kstack);
				if ((c + eol) > scend)
				{
					beep();
					continue;
				}
				ep->mark = i;
				for(i=eol;i>=cur;i--)
					out[c+i] = out[i];
				kptr=kstack;
				while (i = *kptr++)
					out[cur++] = i;
				draw(ep,UPDATE);
				eol = genlen(out);
				continue;
			}
		case '\n':
		case '\r':
			c = '\n';
			goto process;

		case DELETE:	/* delete char 0x7f */
		case '\b':	/* backspace, ^h */
		case ERASECHAR :
			if (count > i)
				count = i;
#ifdef ESH_KAPPEND
			kptr = &kstack[count];	/* move old contents here */
			if (killing)		/* prepend to killbuf */
			{
				c = genlen(kstack) + CHARSIZE; /* include '\0' */
				while(c--)	/* copy stuff */
					kptr[c] = kstack[c];
			}
			else
				*kptr = 0;	/* this is end of data */
			killing = 2;		/* we are killing */
			i -= count;
			eol -= count;
			genncpy(kstack,out+i,cur-i);
#else
			while ((count--)&&(i>0))
			{
				i--;
				eol--;
			}
			genncpy(kstack,out+i,cur-i);
			kstack[cur-i] = 0;
#endif /* ESH_KAPPEND */
			gencpy(out+i,out+cur);
			ep->mark = i;
			goto update;
		case cntl('W') :
#ifdef ESH_KAPPEND
			++killing;		/* keep killing flag */
#endif
			if (ep->mark > eol )
				ep->mark = eol;
			if (ep->mark == i)
				continue;
			if (ep->mark > i)
			{
				adjust = ep->mark - i;
				ed_ungetchar(ep->ed,cntl('D'));
				continue;
			}
			adjust = i - ep->mark;
			ed_ungetchar(ep->ed,usrerase);
			continue;
		case cntl('D') :
			ep->mark = i;
#ifdef ESH_KAPPEND
			if (killing)
				kptr = &kstack[genlen(kstack)];	/* append here */
			else
				kptr = kstack;
			killing = 2;			/* we are now killing */
#else
			kptr = kstack;
#endif /* ESH_KAPPEND */
			while ((count--)&&(eol>0)&&(i<eol))
			{
				*kptr++ = out[i];
				eol--;
				while(1)
				{
					if ((out[i] = out[(i+1)])==0)
						break;
					i++;
				}
				i = cur;
			}
			*kptr = '\0';
			goto update;
		case cntl('C') :
		case cntl('F') :
		{
			int cntlC = (c==cntl('C'));
			while (count-- && eol>i)
			{
				if (cntlC)
				{
					c = out[i];
#if SHOPT_MULTIBYTE
					if((c&~STRIP)==0 && islower(c))
#else
					if(islower(c))
#endif /* SHOPT_MULTIBYTE */
					{
						c += 'A' - 'a';
						out[i] = c;
					}
				}
				i++;
			}
			goto update;
		}
		case cntl(']') :
			c = ed_getchar(ep->ed,1);
			if ((count == 0) || (count > eol))
                        {
                                beep();
                                continue;
                        }
			if (out[i])
				i++;
			while (i < eol)
			{
				if (out[i] == c && --count==0)
					goto update;
				i++;
			}
			i = 0;
			while (i < cur)
			{
				if (out[i] == c && --count==0)
					break;
				i++;
			};

update:
			cur = i;
			draw(ep,UPDATE);
			continue;

		case cntl('B') :
			if (count > i)
				count = i;
			i -= count;
			goto update;
		case cntl('T') :
			if ((sh_isoption(SH_EMACS))&& (eol!=i))
				i++;
			if (i >= 2)
			{
				c = out[i - 1];
				out[i-1] = out[i-2];
				out[i-2] = c;
			}
			else
			{
				if(sh_isoption(SH_EMACS))
					i--;
				beep();
				continue;
			}
			goto update;
		case cntl('A') :
			i = 0;
			goto update;
		case cntl('E') :
			i = eol;
			goto update;
		case cntl('U') :
			adjust = 4*count;
			continue;
		case KILLCHAR :
			cur = 0;
			oadjust = -1;
		case cntl('K') :
			if(oadjust >= 0)
			{
#ifdef ESH_KAPPEND
				killing = 2;		/* set killing signal */
#endif
				ep->mark = count;
				ed_ungetchar(ep->ed,cntl('W'));
				continue;
			}
			i = cur;
			eol = i;
			ep->mark = i;
#ifdef ESH_KAPPEND
			if (killing)			/* append to kill buffer */
				gencpy(&kstack[genlen(kstack)], &out[i]);
			else
				gencpy(kstack,&out[i]);
			killing = 2;			/* set killing signal */
#else
			gencpy(kstack,&out[i]);
#endif /* ESH_KAPPEND */
			out[i] = 0;
			draw(ep,UPDATE);
			if (c == KILLCHAR)
			{
				if (ep->terminal == PAPER)
				{
					putchar(ep->ed,'\n');
					putstring(ep,Prompt);
				}
				c = ed_getchar(ep->ed,0);
				if (c != usrkill)
				{
					ed_ungetchar(ep->ed,c);
					continue;
				}
				if (ep->terminal == PAPER)
					ep->terminal = CRT;
				else
				{
					ep->terminal = PAPER;
					putchar(ep->ed,'\n');
					putstring(ep,Prompt);
				}
			}
			continue;
		case cntl('L'):
			ed_crlf(ep->ed);
			draw(ep,REFRESH);
			continue;
		case cntl('[') :
		do_escape:
			adjust = escape(ep,out,oadjust);
			continue;
		case cntl('R') :
			search(ep,out,count);
			goto drawline;
		case cntl('P') :
                        if (count <= hloff)
                                hloff -= count;
                        else
                        {
                                hline -= count - hloff;
                                hloff = 0;
                        }
#ifdef ESH_NFIRST
			if (hline <= hismin)
#else
			if (hline < hismin)
#endif /* ESH_NFIRST */
			{
				hline = hismin+1;
				beep();
#ifndef ESH_NFIRST
				continue;
#endif
			}
			goto common;

		case cntl('O') :
			location.hist_command = hline;
			location.hist_line = hloff;
			ep->CntrlO = 1;
			c = '\n';
			goto process;
		case cntl('N') :
#ifdef ESH_NFIRST
			hline = location.hist_command;	/* start at saved position */
			hloff = location.hist_line;
#endif /* ESH_NFIRST */
			location = hist_locate(sh.hist_ptr,hline,hloff,count);
			if (location.hist_command > histlines)
			{
				beep();
#ifdef ESH_NFIRST
				location.hist_command = histlines;
				location.hist_line = ep->in_mult;
#else
				continue;
#endif /* ESH_NFIRST */
			}
			hline = location.hist_command;
			hloff = location.hist_line;
		common:
#ifdef ESH_NFIRST
			location.hist_command = hline;	/* save current position */
			location.hist_line = hloff;
#endif
			hist_copy((char*)out,MAXLINE, hline,hloff);
#if SHOPT_MULTIBYTE
			ed_internal((char*)(out),out);
#endif /* SHOPT_MULTIBYTE */
		drawline:
			eol = genlen(out);
			cur = eol;
			draw(ep,UPDATE);
			continue;
		}
		
	}
	
process:

	if (c == (-1))
	{
		lookahead = 0;
		beep();
		*out = '\0';
	}
	draw(ep,FINAL);
	tty_cooked(ERRIO);
	if(ed->e_nlist)
	{
		ed->e_nlist = 0;
		stakset(ed->e_stkptr,ed->e_stkoff);
	}
	if(c == '\n')
	{
		out[eol++] = '\n';
		out[eol] = '\0';
		ed_crlf(ep->ed);
	}
#if SHOPT_MULTIBYTE
	ed_external(out,buff);
#endif /* SHOPT_MULTIBYTE */
	i = strlen(buff);
	if (i)
		return(i);
	return(-1);
}

static void show_info(Emacs_t *ep,const char *str)
{
	register genchar *out = drawbuff;
	register int c;
	genchar string[LBUF];
	int sav_cur = cur;
	/* save current line */
	genncpy(string,out,sizeof(string)/sizeof(*string));
	*out = 0;
	cur = 0;
#if SHOPT_MULTIBYTE
	ed_internal(str,out);
#else
	gencpy(out,str);
#endif	/* SHOPT_MULTIBYTE */
	draw(ep,UPDATE);
	c = ed_getchar(ep->ed,0);
	if(c!=' ')
		ed_ungetchar(ep->ed,c);
	/* restore line */
	cur = sav_cur;
	genncpy(out,string,sizeof(string)/sizeof(*string));
	draw(ep,UPDATE);
}

static void putstring(Emacs_t* ep,register char *sp)
{
	register int c;
	while (c= *sp++)
		 putchar(ep->ed,c);
}


static int escape(register Emacs_t* ep,register genchar *out,int count)
{
	register int i,value;
	int digit,ch;
	digit = 0;
	value = 0;
	while ((i=ed_getchar(ep->ed,0)),isdigit(i))
	{
		value *= 10;
		value += (i - '0');
		digit = 1;
	}
	if (digit)
	{
		ed_ungetchar(ep->ed,i) ;
#ifdef ESH_KAPPEND
		++killing;		/* don't modify killing signal */
#endif
		return(value);
	}
	value = count;
	if(value<0)
		value = 1;
	switch(ch=i)
	{
		case cntl('V'):
			show_info(ep,fmtident(e_version));
			return(-1);
		case ' ':
			ep->mark = cur;
			return(-1);

#ifdef ESH_KAPPEND
		case '+':		/* M-+ = append next kill */
			killing = 2;
			return -1;	/* no argument for next command */
#endif

		case 'p':	/* M-p == ^W^Y (copy stack == kill & yank) */
			ed_ungetchar(ep->ed,cntl('Y'));
			ed_ungetchar(ep->ed,cntl('W'));
#ifdef ESH_KAPPEND
			killing = 0;	/* start fresh */
#endif
			return(-1);

		case 'l':	/* M-l == lower-case */
		case 'd':
		case 'c':
		case 'f':
		{
			i = cur;
			while(value-- && i<eol)
			{
				while ((out[i])&&(!isword(i)))
					i++;
				while ((out[i])&&(isword(i)))
					i++;
			}
			if(ch=='l')
			{
				value = i-cur;
				while (value-- > 0)
				{
					i = out[cur];
#if SHOPT_MULTIBYTE
					if((i&~STRIP)==0 && isupper(i))
#else
					if(isupper(i))
#endif /* SHOPT_MULTIBYTE */
					{
						i += 'a' - 'A';
						out[cur] = i;
					}
					cur++;
				}
				draw(ep,UPDATE);
				return(-1);
			}

			else if(ch=='f')
				goto update;
			else if(ch=='c')
			{
				ed_ungetchar(ep->ed,cntl('C'));
				return(i-cur);
			}
			else
			{
				if (i-cur)
				{
					ed_ungetchar(ep->ed,cntl('D'));
#ifdef ESH_KAPPEND
					++killing;	/* keep killing signal */
#endif
					return(i-cur);
				}
				beep();
				return(-1);
			}
		}
		
		
		case 'b':
		case DELETE :
		case '\b':
		case 'h':
		{
			i = cur;
			while(value-- && i>0)
			{
				i--;
				while ((i>0)&&(!isword(i)))
					i--;
				while ((i>0)&&(isword(i-1)))
					i--;
			}
			if(ch=='b')
				goto update;
			else
			{
				ed_ungetchar(ep->ed,usrerase);
#ifdef ESH_KAPPEND
				++killing;
#endif
				return(cur-i);
			}
		}
		
		case '>':
			ed_ungetchar(ep->ed,cntl('N'));
#ifdef ESH_NFIRST
			if (ep->in_mult)
			{
				location.hist_command = histlines;
				location.hist_line = ep->in_mult - 1;
			}
			else
			{
				location.hist_command = histlines - 1;
				location.hist_line = 0;
			}
#else
			hline = histlines-1;
			hloff = 0;
#endif /* ESH_NFIRST */
			return(0);
		
		case '<':
			ed_ungetchar(ep->ed,cntl('P'));
			hloff = 0;
#ifdef ESH_NFIRST
			hline = hismin + 1;
			return 0;
#else
			return(hline-hismin);
#endif /* ESH_NFIRST */


		case '#':
			ed_ungetchar(ep->ed,'\n');
			ed_ungetchar(ep->ed,(out[0]=='#')?cntl('D'):'#');
			ed_ungetchar(ep->ed,cntl('A'));
			return(-1);
		case '_' :
		case '.' :
		{
			genchar name[MAXLINE];
			char buf[MAXLINE];
			char *ptr;
			ptr = hist_word(buf,MAXLINE,(count?count:-1));
#if !KSHELL
			if(ptr==0)
			{
				beep();
				break;
			}
#endif	/* KSHELL */
			if ((eol - cur) >= sizeof(name))
			{
				beep();
				return(-1);
			}
			ep->mark = cur;
			gencpy(name,&out[cur]);
			while(*ptr)
			{
				out[cur++] = *ptr++;
				eol++;
			}
			gencpy(&out[cur],name);
			draw(ep,UPDATE);
			return(-1);
		}
#if KSHELL

		/* file name expansion */
		case cntl('[') :	/* filename completion */
			i = '\\';
		case '*':		/* filename expansion */
		case '=':	/* escape = - list all matching file names */
			ep->mark = cur;
			if(ed_expand(ep->ed,(char*)out,&cur,&eol,i,count) < 0)
			{
				if(ep->ed->e_tabcount==1)
				{
					ep->ed->e_tabcount=2;
					ed_ungetchar(ep->ed,cntl('\t'));
					return(-1);
				}
				beep();
			}
			else if(i=='=')
			{
				draw(ep,REFRESH);
				if(count>0)
					ep->ed->e_tabcount=0;
				else
				{
					i=ed_getchar(ep->ed,0);
					ed_ungetchar(ep->ed,i);
					if(isdigit(i))
						ed_ungetchar(ep->ed,ESC);
				}
			}
			else
			{
				if(i=='\\' && cur>ep->mark && (out[cur-1]=='/' || out[cur-1]==' '))
					ep->ed->e_tabcount=0;
				draw(ep,UPDATE);
			}
			return(-1);

		/* search back for character */
		case cntl(']'):	/* feature not in book */
		{
			int c = ed_getchar(ep->ed,1);
			if ((value == 0) || (value > eol))
			{
				beep();
				return(-1);
			}
			i = cur;
			if (i > 0)
				i--;
			while (i >= 0)
			{
				if (out[i] == c && --value==0)
					goto update;
				i--;
			}
			i = eol;
			while (i > cur)
			{
				if (out[i] == c && --value==0)
					break;
				i--;
			};

		}
		update:
			cur = i;
			draw(ep,UPDATE);
			return(-1);

#ifdef _cmd_tput
		case cntl('L'): /* clear screen */
			sh_trap("tput clear", 0);
			draw(ep,REFRESH);
			return(-1);
#endif
		case '[':	/* feature not in book */
			switch(i=ed_getchar(ep->ed,1))
			{
			    case 'A':
				ed_ungetchar(ep->ed,cntl('P'));
				return(-1);
			    case 'B':
				ed_ungetchar(ep->ed,cntl('N'));
				return(-1);
			    case 'C':
				ed_ungetchar(ep->ed,cntl('F'));
				return(-1);
			    case 'D':
				ed_ungetchar(ep->ed,cntl('B'));
				return(-1);
			    case 'H':
				ed_ungetchar(ep->ed,cntl('A'));
				return(-1);
			    case 'Y':
				ed_ungetchar(ep->ed,cntl('E'));
				return(-1);
			    default:
				ed_ungetchar(ep->ed,i);
			}
			i = '_';

		default:
			/* look for user defined macro definitions */
			if(ed_macro(ep->ed,i))
#   ifdef ESH_BETTER
				return(count);	/* pass argument to macro */
#   else
				return(-1);
#   endif /* ESH_BETTER */
#else
		update:
			cur = i;
			draw(ep,UPDATE);
			return(-1);

		default:
#endif	/* KSHELL */
		beep();
		return(-1);
	}
}


/*
 * This routine process all commands starting with ^X
 */

static void xcommands(register Emacs_t *ep,int count)
{
        register int i = ed_getchar(ep->ed,0);
	NOT_USED(count);
        switch(i)
        {
                case cntl('X'):	/* exchange dot and mark */
                        if (ep->mark > eol)
                                ep->mark = eol;
                        i = ep->mark;
                        ep->mark = cur;
                        cur = i;
                        draw(ep,UPDATE);
                        return;

#if KSHELL
#   ifdef ESH_BETTER
                case cntl('E'):	/* invoke emacs on current command */
			if(ed_fulledit(ep->ed)==-1)
				beep();
			else
			{
#if SHOPT_MULTIBYTE
				ed_internal((char*)drawbuff,drawbuff);
#endif /* SHOPT_MULTIBYTE */
				ed_ungetchar(ep->ed,'\n');
			}
			return;

#	define itos(i)	fmtbase((long)(i),0,0)/* want signed conversion */

		case cntl('H'):		/* ^X^H show history info */
			{
				char hbuf[MAXLINE];

				strcpy(hbuf, "Current command ");
				strcat(hbuf, itos(hline));
				if (hloff)
				{
					strcat(hbuf, " (line ");
					strcat(hbuf, itos(hloff+1));
					strcat(hbuf, ")");
				}
				if ((hline != location.hist_command) ||
				    (hloff != location.hist_line))
				{
					strcat(hbuf, "; Previous command ");
					strcat(hbuf, itos(location.hist_command));
					if (location.hist_line)
					{
						strcat(hbuf, " (line ");
						strcat(hbuf, itos(location.hist_line+1));
						strcat(hbuf, ")");
					}
				}
				show_info(ep,hbuf);
				return;
			}
#	if 0	/* debugging, modify as required */
		case cntl('D'):		/* ^X^D show debugging info */
			{
				char debugbuf[MAXLINE];

				strcpy(debugbuf, "count=");
				strcat(debugbuf, itos(count));
				strcat(debugbuf, " eol=");
				strcat(debugbuf, itos(eol));
				strcat(debugbuf, " cur=");
				strcat(debugbuf, itos(cur));
				strcat(debugbuf, " crallowed=");
				strcat(debugbuf, itos(crallowed));
				strcat(debugbuf, " plen=");
				strcat(debugbuf, itos(plen));
				strcat(debugbuf, " w_size=");
				strcat(debugbuf, itos(w_size));

				show_info(ep,debugbuf);
				return;
			}
#	endif /* debugging code */
#   endif /* ESH_BETTER */
#endif /* KSHELL */

                default:
                        beep();
                        return;
	}
}

static void search(Emacs_t* ep,genchar *out,int direction)
{
#ifndef ESH_NFIRST
	Histloc_t location;
#endif
	register int i,sl;
	genchar str_buff[LBUF];
	register genchar *string = drawbuff;
	/* save current line */
	int sav_cur = cur;
	genncpy(str_buff,string,sizeof(str_buff)/sizeof(*str_buff));
	string[0] = '^';
	string[1] = 'R';
	string[2] = '\0';
	sl = 2;
	cur = sl;
	draw(ep,UPDATE);
	while ((i = ed_getchar(ep->ed,1))&&(i != '\r')&&(i != '\n'))
	{
		if (i==usrerase || i==DELETE || i=='\b' || i==ERASECHAR)
		{
			if (sl > 2)
			{
				string[--sl] = '\0';
				cur = sl;
				draw(ep,UPDATE);
			}
			else
				beep();
			continue;
		}
		if (i==usrkill)
		{
			beep();
			goto restore;
		}
		if (i == '\\')
		{
			string[sl++] = '\\';
			string[sl] = '\0';
			cur = sl;
			draw(ep,APPEND);
			i = ed_getchar(ep->ed,1);
			string[--sl] = '\0';
		}
		string[sl++] = i;
		string[sl] = '\0';
		cur = sl;
		draw(ep,APPEND);
	}
	i = genlen(string);
	
	if (direction < 1)
	{
		ep->prevdirection = -ep->prevdirection;
		direction = 1;
	}
	else
		direction = -1;
	if (i != 2)
	{
#if SHOPT_MULTIBYTE
		ed_external(string,(char*)string);
#endif /* SHOPT_MULTIBYTE */
		strncpy(lstring,((char*)string)+2,SEARCHSIZE);
		ep->prevdirection = direction;
	}
	else
		direction = ep->prevdirection ;
	location = hist_find(sh.hist_ptr,(char*)lstring,hline,1,direction);
	i = location.hist_command;
	if(i>0)
	{
		hline = i;
#ifdef ESH_NFIRST
		hloff = location.hist_line = 0;	/* display first line of multi line command */
#else
		hloff = location.hist_line;
#endif /* ESH_NFIRST */
		hist_copy((char*)out,MAXLINE, hline,hloff);
#if SHOPT_MULTIBYTE
		ed_internal((char*)out,out);
#endif /* SHOPT_MULTIBYTE */
		return;
	}
	if (i < 0)
	{
		beep();
#ifdef ESH_NFIRST
		location.hist_command = hline;
		location.hist_line = hloff;
#else
		hloff = 0;
		hline = histlines;
#endif /* ESH_NFIRST */
	}
restore:
	genncpy(string,str_buff,sizeof(str_buff)/sizeof(*str_buff));
	cur = sav_cur;
	return;
}


/* Adjust screen to agree with inputs: logical line and cursor */
/* If 'first' assume screen is blank */
/* Prompt is always kept on the screen */

static void draw(register Emacs_t *ep,Draw_t option)
{
#define	NORMAL ' '
#define	LOWER  '<'
#define	BOTH   '*'
#define	UPPER  '>'

	register genchar *sptr;		/* Pointer within screen */
	genchar nscreen[2*MAXLINE];	/* New entire screen */
	genchar *ncursor;		/* New cursor */
	register genchar *nptr;		/* Pointer to New screen */
	char  longline;			/* Line overflow */
	genchar *logcursor;
	genchar *nscend;		/* end of logical screen */
	register int i;
	
	nptr = nscreen;
	sptr = drawbuff;
	logcursor = sptr + cur;
	longline = NORMAL;
	
	if (option == FIRST || option == REFRESH)
	{
		ep->overflow = NORMAL;
		ep->cursor = ep->screen;
		ep->offset = 0;
		ep->cr_ok = crallowed;
		if (option == FIRST)
		{
			ep->scvalid = 1;
			return;
		}
		*ep->cursor = '\0';
		putstring(ep,Prompt);	/* start with prompt */
	}
	
	/*********************
	 Do not update screen if pending characters
	**********************/
	
	if ((lookahead)&&(option != FINAL))
	{
		
		ep->scvalid = 0; /* Screen is out of date, APPEND will not work */
		
		return;
	}
	
	/***************************************
	If in append mode, cursor at end of line, screen up to date,
	the previous character was a 'normal' character,
	and the window has room for another character.
	Then output the character and adjust the screen only.
	*****************************************/
	

	i = *(logcursor-1);	/* last character inserted */
	
	if ((option == APPEND)&&(ep->scvalid)&&(*logcursor == '\0')&&
	    print(i)&&((ep->cursor-ep->screen)<(w_size-1)))
	{
		putchar(ep->ed,i);
		*ep->cursor++ = i;
		*ep->cursor = '\0';
		return;
	}

	/* copy the line */
	ncursor = nptr + ed_virt_to_phys(ep->ed,sptr,nptr,cur,0,0);
	nptr += genlen(nptr);
	sptr += genlen(sptr);
	nscend = nptr - 1;
	if(sptr == logcursor)
		ncursor = nptr;
	
	/*********************
	 Does ncursor appear on the screen?
	 If not, adjust the screen offset so it does.
	**********************/
	
	i = ncursor - nscreen;
	
	if ((ep->offset && i<=ep->offset)||(i >= (ep->offset+w_size)))
	{
		/* Center the cursor on the screen */
		ep->offset = i - (w_size>>1);
		if (--ep->offset < 0)
			ep->offset = 0;
	}
			
	/*********************
	 Is the range of screen[0] thru screen[w_size] up-to-date
	 with nscreen[offset] thru nscreen[offset+w_size] ?
	 If not, update as need be.
	***********************/
	
	nptr = &nscreen[ep->offset];
	sptr = ep->screen;
	
	i = w_size;
	
	while (i-- > 0)
	{
		
		if (*nptr == '\0')
		{
			*(nptr + 1) = '\0';
			*nptr = ' ';
		}
		if (*sptr == '\0')
		{
			*(sptr + 1) = '\0';
			*sptr = ' ';
		}
		if (*nptr == *sptr)
		{
			nptr++;
			sptr++;
			continue;
		}
		setcursor(ep,sptr-ep->screen,*nptr);
		*sptr++ = *nptr++;
#if SHOPT_MULTIBYTE
		while(*nptr==MARKER)
		{
			if(*sptr=='\0')
				*(sptr + 1) = '\0';
			*sptr++ = *nptr++;
			i--;
			ep->cursor++;
		}
#endif /* SHOPT_MULTIBYTE */
	}
	
	/******************
	
	Screen overflow checks 
	
	********************/
	
	if (nscend >= &nscreen[ep->offset+w_size])
	{
		if (ep->offset > 0)
			longline = BOTH;
		else
			longline = UPPER;
	}
	else
	{
		if (ep->offset > 0)
			longline = LOWER;
	}
	
	/* Update screen overflow indicator if need be */
	
	if (longline != ep->overflow)
	{
		setcursor(ep,w_size,longline);
		ep->overflow = longline;
	}
	i = (ncursor-nscreen) - ep->offset;
	setcursor(ep,i,0);
	if(option==FINAL && ep->ed->e_multiline)
		setcursor(ep,nscend-nscreen,0);
	ep->scvalid = 1;
	return;
}

/*
 * put the cursor to the <newp> position within screen buffer
 * if <c> is non-zero then output this character
 * cursor is set to reflect the change
 */

static void setcursor(register Emacs_t *ep,register int newp,int c)
{
	register int oldp = ep->cursor - ep->screen;
	newp  = ed_setcursor(ep->ed, ep->screen, oldp, newp, 0);
	if(c)
	{
		putchar(ep->ed,c);
		newp++;
	}
	ep->cursor = ep->screen+newp;
	return;
}

#if SHOPT_MULTIBYTE
static int print(register int c)
{
	return((c&~STRIP)==0 && isprint(c));
}

static int _isword(register int c)
{
	return((c&~STRIP) || isalnum(c) || c=='_');
}
#endif /* SHOPT_MULTIBYTE */