/*
 * Electric(tm) VLSI Design System
 *
 * File: usreditemacs.c
 * User interface aid: EMACS-like text window handler
 * Written by: Steven M. Rubin, Static Free Software
 *
 * Copyright (c) 2000 Static Free Software.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Electric(tm); see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, Mass 02111-1307, USA.
 *
 * Static Free Software
 * 4119 Alpine Road
 * Portola Valley, California 94028
 * info@staticfreesoft.com
 */

/*
 * This EMACS-like text editor accepts the following commands:
 * ^A     cursor to start-of-line
 * ^B     backup one character
 * ^D     delete the next character (cannot delete line breaks)
 * ^E     cursor to end-of-line
 * ^F     forward one character
 * ^G     flush state (and force evaluation of changed line)
 * ^H     delete the previous character (cannot delete line breaks)
 * ^K     delete to end-of-line (or kill line if on a null line)
 * ^L     redraw screen
 * RETURN insert new line (only if at end of line)
 * ^N     next line
 * ^O     insert new line (only if at beginning of line)
 * ^P     previous line
 * ^R     reverse search
 * ^S     forward search
 * ^V     shift screen up one page
 * ^X^V   read a text file from disk (you are prompted for the file name)
 * ^X^W   write the text file to disk (you are prompted for the file name)
 * ^Xd    terminate this editor window
 * ^Y     restore line deleted with ^K
 * ^Z     shift screen up one line
 * DEL    delete the previous character (cannot delete line breaks)
 * M(B)   backup one word
 * M(D)   delete the next word (cannot delete line breaks)
 * M(F)   forward one word
 * M(H)   delete the previous word (cannot delete line breaks)
 * M(V)   shift screen down one page
 * M(X)   execute an Electric command
 * M(Z)   shift screen down one line
 * M(<)   cursor to beginning of file
 * M(>)   cursor to end of file
 * M(DEL) delete the previous word (cannot delete line breaks)
 *
 * All printing characters "self-insert"
 * Meta characters can be typed by holding the META key or by prefixing
 * the character with an ESCAPE.
 * In popup editor windows, all commands that would create a new line
 * cause the editor to exit
 */

#include "global.h"
#include "egraphics.h"
#include "usr.h"
#include "usreditemacs.h"

#define	CTLA   01			/* ^A */
#define	CTLB   02			/* ^B */
#define	CTLC   03			/* ^C */
#define	CTLD   04			/* ^D */
#define	CTLE   05			/* ^E */
#define	CTLF   06			/* ^F */
#define	CTLG   07			/* ^G */
#define	CTLH  010			/* ^H */
#define	CTLK  013			/* ^K */
#define	CTLL  014			/* ^L */
#define	CTLN  016			/* ^N */
#define	CTLO  017			/* ^O */
#define	CTLP  020			/* ^P */
#define	CTLR  022			/* ^R */
#define	CTLS  023			/* ^S */
#define	CTLV  026			/* ^V */
#define	CTLW  027			/* ^W */
#define	CTLX  030			/* ^X */
#define	CTLY  031			/* ^Y */
#define	CTLZ  032			/* ^Z */
#define	ESC   033			/* $ */
#define	DEL  0177			/* delete */

/* the bits in us_lastemacschar */
#define	CONTROLX 1			/* ^X was last character typed */
#define	ESCAPE   2			/* ESCAPE was last character typed */

#define	HEADERLINES 2

extern GRAPHICS us_ebox, us_menutext, us_menufigs;

INTSML          us_lastemacschar;		/* state of last character typed */
static INTSML   us_twid, us_thei;		/* size of a single letter */
static INTSML   us_editemacsfont;		/* font size for editor */
static char    *us_killbuf;				/* saved line when ^K is done */
static INTSML   us_killbufchars = 0;	/* characters in kill buffer */
static char    *us_searchbuf = 0;		/* search string for ^S and ^R */

/* prototypes for local routines */
void us_editemacsgotbutton(WINDOWPART*, INTSML, INTSML, INTSML);
void us_editemacsredraweditor(WINDOWPART*);
INTSML us_editemacsimplementchar(WINDOWPART*, INTSML, INTSML, INTSML);
INTSML us_editemacsbackupchar(WINDOWPART*, INTSML);
INTSML us_editemacsadvancechar(WINDOWPART*, INTSML);
void us_editemacsdeletechar(WINDOWPART*);
void us_editemacsshiftscreenup(WINDOWPART*);
void us_editemacsshiftscreendown(WINDOWPART*);
void us_editemacsensuretextshown(WINDOWPART*, INTSML);
void us_editemacsredrawscreen(WINDOWPART*);
void us_editemacsredrawline(WINDOWPART*, INTSML);
void us_editemacssetheader(WINDOWPART*, char*);
void us_editemacsflashcursor(WINDOWPART*);
void us_editemacsoffhighlight(WINDOWPART*);
void us_editemacscleanupline(WINDOWPART*, INTSML);
void us_editemacsworkingoncurline(EDITOR*);
void us_editemacsaddmorelines(EDITOR*);
void us_editemacsaddmorechars(EDITOR*, INTSML);
void us_editemacsgetbuffers(EDITOR*);
void us_editemacsmovebox(WINDOWPART*, INTSML, INTSML, INTSML, INTSML, INTSML, INTSML);
void us_editemacsclearbox(WINDOWPART*, INTSML, INTSML, INTSML, INTSML);
void us_editemacsinvertbox(WINDOWPART*, INTSML, INTSML, INTSML, INTSML);
void us_editemacstext(WINDOWPART*, char*, INTSML, INTSML);

/******************** INTERFACE FOR TEXT WINDOW ********************/

/*
 * routine to convert window "oriwin" to an EMACS editor.  If "oriwin" is
 * NOWINDOWPART, create a popup window with one line.  The window header is in
 * "header".  The number of characters and lines are placed in "chars" and
 * "lines".  Returns the window (NOWINDOWPART if the editor cannot be started).
 */
WINDOWPART *us_editemacsmakeeditor(WINDOWPART *oriwin, char *header, INTSML *chars, INTSML *lines)
{
	INTBIG x, y;
	INTSML i, swid, shei, pwid;
	REGISTER EDITOR *e;
	REGISTER VARIABLE *var;
	REGISTER WINDOWPART *win;

	/* if no window exists, create one as a popup */
	win = oriwin;
	if (win == NOWINDOWPART)
	{
		getpaletteparameters(&swid, &shei, &pwid);
		if (el_curwindowpart == NOWINDOWPART)
		{
			x = swid / 2;
			y = shei / 2;
		} else
		{
			(void)getxy(&x, &y);
			x = applyxscale(el_curwindowpart, x-el_curwindowpart->screenlx) + el_curwindowpart->uselx;
			y = applyyscale(el_curwindowpart, y-el_curwindowpart->screenly) + el_curwindowpart->usely;
		}

		/* create a window that covers the popup */
		startobjectchange((INTBIG)us_aid, VAID);
		win = newwindowpart("popup", el_curwindowpart);
		win->uselx = (INTSML)maxi(x-200, 0);
		win->usely = (INTSML)maxi(y-us_thei-1, 0);
		win->usehx = (INTSML)mini(win->uselx+400, swid-1);
		win->usehy = (INTSML)mini(win->usely+us_thei*(HEADERLINES+1)+3, shei-1);
		win->screenlx = win->uselx;
		win->screenhx = win->usehx;
		win->screenly = win->usely;
		win->screenhy = win->usehy;
		computewindowscale(win);
		win->state = (win->state & ~WINDOWTYPE) | POPTEXTWINDOW;
	} else win->state = (win->state & ~(WINDOWTYPE|WINDOWSIMULATING)) | TEXTWINDOW;

	win->curnodeproto = NONODEPROTO;
	win->buttonhandler = us_editemacsgotbutton;
	win->charhandler = us_editemacsgotchar;
	win->termhandler = us_editemacseditorterm;
	win->redisphandler = us_editemacsredraweditor;
	win->changehandler = 0;
	win->screenlx = win->uselx;   win->screenhx = win->usehx;
	win->screenly = win->usely;   win->screenhy = win->usehy;
	computewindowscale(win);

	/*
	 * the font that is used by the editor is the fixed-width font, TXTEDITOR.
	 * This can be changed by setting the variable "USER_textedit_font" on the user aid
	 * object.  The value is the point size minus 4 divided by 2 (the value 0 is the
	 * default font TXTEDITOR).  For example, to set the font to be 14 points, type:
	 *     -var set aid:user.USER_textedit_font 5
	 */
	us_editemacsfont = TXTEDITOR;
	var = getval((INTBIG)us_aid, VAID, VINTEGER, "USER_textedit_font");
	if (var != NOVARIABLE && var->addr >= TXT4P && var->addr <= TXT20P)
		us_editemacsfont = (INTSML)var->addr;
	screensettextsize(win, us_editemacsfont);
	screengettextsize(win, "X", &us_twid, &us_thei);

	/* create a new editor object */
	e = us_alloceditor();
	if (e == NOEDITOR) return(NOWINDOWPART);
	e->state = (e->state & ~(EDITORTYPE|EGRAPHICSOFF|LINESFIXED)) | EMACSEDITOR;
	(void)allocstring(&e->header, header, us_aid->cluster);
	e->highlightedline = -1;
	us_lastemacschar = 0;
	e->curline = e->curchar = 0;
	e->working = -1;
	e->firstline = 0;
	e->swid = win->usehx - win->uselx - 2;
	e->shei = win->usehy - win->usely - us_thei*HEADERLINES - 1;
	e->offx = win->uselx + 1;
	e->revy = e->shei + win->usely + 1;
	*chars = e->screenchars = e->swid / us_twid;
	*lines = e->screenlines = e->shei / us_thei;

	/* turn this window into an EMACS editor */
	if ((e->state&EDITORINITED) == 0)
	{
		/* first time: allocate buffers */
		e->state |= EDITORINITED;
		e->maxlines = e->screenlines;
		e->mostchars = e->screenchars;
		e->textarray = (char **)emalloc((e->maxlines * (sizeof (char *))), us_aid->cluster);
		if (e->textarray == 0) ttyputerr("No memory for text");
		e->maxchars = (INTSML *)emalloc((e->maxlines * SIZEOFINTSML), us_aid->cluster);
		if (e->maxchars == 0) ttyputerr("No memory for characters");
		us_editemacsgetbuffers(e);
		for(i=0; i<e->maxlines; i++)
		{
			e->textarray[i] = (char *)emalloc((e->screenchars+1), us_aid->cluster);
			if (e->textarray[i] == 0) ttyputerr("No memory for line");
			e->textarray[i][0] = 0;
			e->maxchars[i] = e->screenchars;
		}
	} else
	{
		/* make sure buffers cover the screen */
		while (e->screenlines > e->maxlines) us_editemacsaddmorelines(e);
		for(i=0; i<e->maxlines; i++)
		{
			e->textarray[i][0] = 0;
			while (e->screenchars > e->maxchars[i])
				us_editemacsaddmorechars(e, i);
		}
	}

	/* now finish initializing window */
	win->editor = e;
	if (oriwin != NOWINDOWPART)
	{
		/* clear window and write header */
		us_editemacssetheader(win, header);

		/* show initial cursor */
		us_editemacsflashcursor(win);
	} else
	{
		/* finish initializing window if it is a new popup one */
		e->savedbox = screensavebox(win, (INTSML)maxi(e->offx-2,0), (INTSML)(e->offx+e->swid+2),
			(INTSML)maxi(e->revy-e->shei-2,0), (INTSML)(e->revy+us_thei*HEADERLINES+1));
		endobjectchange((INTBIG)us_aid, VAID);
	}
	return(win);
}

/*
 * routine to free all memory associated with this editor
 */
void us_editemacsterminate(EDITOR *e)
{
}

/*
 * routine to return the total number of valid lines in the edit buffer
 */
INTBIG us_editemacstotallines(WINDOWPART *win)
{
	REGISTER INTSML i;
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return(0);
	for(i = e->maxlines-1; i >= 0; i--) if (e->textarray[i][0] != 0) break;
	return(i+1);
}

/*
 * routine to get the string on line "lindex" (0 based).  A negative line
 * returns the current line.  Returns -1 if the index is beyond the file limit
 */
char *us_editemacsgetline(WINDOWPART *win, INTBIG lindex)
{
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return("");
	if (lindex < 0) lindex = e->curline;
	if (lindex >= e->maxlines) return(NOSTRING);
	return(e->textarray[lindex]);
}

/*
 * routine to add line "str" to the text facet to become line "lindex"
 */
void us_editemacsaddline(WINDOWPART *win, INTBIG lindex, char *str)
{
	REGISTER char *pt;
	REGISTER INTBIG savedline, savedchar;
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;
	us_editemacsflashcursor(win);
	savedchar = e->curchar;   savedline = e->curline;
	e->curline = lindex;   e->curchar = 0;
	(void)us_editemacsimplementchar(win, CTLO, 0, 0);
	if (str[0] == 0) (void)us_editemacsimplementchar(win, ' ', 0, 0); else
		for(pt = str; *pt != 0; pt++)
			(void)us_editemacsimplementchar(win, *pt, 0, 0);
	e->curchar = savedchar;   e->curline = savedline;
	us_editemacsflashcursor(win);
}

/*
 * routine to replace the line number "lindex" with the string "str".
 */
void us_editemacsreplaceline(WINDOWPART *win, INTBIG lindex, char *str)
{
	REGISTER char *pt;
	REGISTER INTBIG savedline, savedchar;
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;
	us_editemacsflashcursor(win);
	savedchar = e->curchar;   savedline = e->curline;
	if (e->curline == lindex) savedchar = 0;
	e->curline = lindex;   e->curchar = 0;
	if (e->textarray[lindex][0] != 0) (void)us_editemacsimplementchar(win, CTLK, 0, 0);
	for(pt = str; *pt != 0; pt++) (void)us_editemacsimplementchar(win, *pt, 0, 0);
	e->curchar = savedchar;   e->curline = savedline;
	us_editemacsflashcursor(win);
}

/*
 * routine to delete line number "lindex"
 */
void us_editemacsdeleteline(WINDOWPART *win, INTBIG lindex)
{
	REGISTER INTBIG savedchar, savedline;
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;
	us_editemacsflashcursor(win);
	savedchar = e->curchar;   savedline = e->curline;
	if (e->curline == lindex) { savedline++;   savedchar = 0; }
	e->curline = lindex;   e->curchar = 0;
	if (e->textarray[lindex][0] != 0) (void)us_editemacsimplementchar(win, CTLK, 0, 0);
	(void)us_editemacsimplementchar(win, CTLK, 0, 0);
	e->curchar = savedchar;   e->curline = savedline;
	us_editemacsflashcursor(win);
}

/*
 * routine to highlight line "lindex" in the text window
 */
void us_editemacshighlightline(WINDOWPART *win, INTBIG lindex)
{
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;
	us_editemacsoffhighlight(win);
	if ((e->state&EGRAPHICSOFF) != 0) return;
	e->highlightedline = lindex;
	us_editemacsensuretextshown(win, (INTSML)e->highlightedline);
	if (e->highlightedline-e->firstline < e->screenlines && e->highlightedline >= e->firstline)
		us_editemacsinvertbox(win, 0, (INTSML)(e->highlightedline-e->firstline), e->swid, us_thei);
}

/*
 * routine to stop the graphic display of changes (for batching)
 */
void us_editemacssuspendgraphics(WINDOWPART *win)
{
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;
	e->state |= EGRAPHICSOFF;
}

/*
 * routine to restart the graphic display of changes and redisplay (for batching)
 */
void us_editemacsresumegraphics(WINDOWPART *win)
{
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;
	e->state &= ~EGRAPHICSOFF;
	us_editemacsredrawscreen(win);
	us_editemacsflashcursor(win);
}

/*
 * routine to write the text file to "file"
 */
void us_editemacswritetextfile(WINDOWPART *win, char *file)
{
	REGISTER EDITOR *e;
	REGISTER INTSML i, j;
	REGISTER FILE *f;
	char *truename;

	e = win->editor;
	if (e == NOEDITOR) return;

	/* find the last line */
	for(j = e->maxlines-1; j >= 0; j--) if (e->textarray[j][0] != 0) break;
	if (j < 0)
	{
		ttyputerr("File is empty");
		return;
	}

	f = xcreate(file, FILETYPETEXT, "Text File", &truename);
	if (f == NULL)
	{
		if (truename != 0) ttyputerr("Cannot write %s", truename);
		return;
	}

	for(i=0; i<=j; i++) xprintf(f, "%s\n", e->textarray[i]);
	xclose(f);
	ttyputmsg("%s written", truename);
}

/*
 * routine to read the text file "file"
 */
void us_editemacsreadtextfile(WINDOWPART *win, char *file)
{
	REGISTER EDITOR *e;
	REGISTER INTSML i, c;
	REGISTER FILE *f;
	char *filename;

	e = win->editor;
	if (e == NOEDITOR) return;

	/* get the file */
	f = xopen(file, FILETYPETEXT, "", &filename);
	if (f == NULL)
	{
		ttyputerr("Cannot read %s", file);
		return;
	}
	ttyputmsg("Reading %s", file);

	/* erase the text that is there */
	e->state |= EGRAPHICSOFF;
	for(i=0; i<e->maxlines; i++)
	{
		if (e->textarray[i][0] == 0) continue;
		if (win->changehandler != 0)
			(*win->changehandler)(win, REPLACETEXTLINE, e->textarray[i], "", i);
		e->textarray[i][0] = 0;
	}
	e->curline = e->curchar = e->firstline = 0;

	/* read the file */
	for(;;)
	{
		c = xgetc(f);
		if (c == EOF) break;
		(void)us_editemacsimplementchar(win, (INTSML)(c&0177), 0, 0);
	}
	xclose(f);

	/* announce the new text */
	if (win->changehandler != 0)
		for(i=e->maxlines-1; i>=0; i--)
			if (e->textarray[i][0] != 0)
				(*win->changehandler)(win, REPLACETEXTLINE, "", e->textarray[i], i);

	/* restore the display */
	e->state &= ~EGRAPHICSOFF;
	us_editemacsredrawscreen(win);
}

/******************** WINDOW CONTROL ********************/

void us_editemacseditorterm(WINDOWPART *win)
{
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;

	if ((win->state&WINDOWTYPE) == POPTEXTWINDOW)
	{
		(void)screenrestorebox(e->savedbox, 0);
	}
}

void us_editemacsgotbutton(WINDOWPART *win, INTSML but, INTSML x, INTSML y)
{
	REGISTER EDITOR *e;
	REGISTER INTBIG xc, yc, len;

	e = win->editor;
	if (e == NOEDITOR) return;
	xc = (x-e->offx) / us_twid;
	if (xc < 0) xc = 0;
	yc = (e->revy-y) / us_thei + e->firstline;
	if (yc < e->firstline) yc = e->firstline;
	if (yc >= e->maxlines) yc = e->maxlines-1;
	len = strlen(e->textarray[yc]);
	if (xc > len) xc = len;
	us_editemacsflashcursor(win);
	e->curchar = xc;
	e->curline = yc;
	us_editemacsflashcursor(win);
}

void us_editemacsredraweditor(WINDOWPART *win)
{
	REGISTER INTSML i;
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;

	/* compute window extents */
	e->swid = win->usehx - win->uselx - 2;
	e->shei = win->usehy - win->usely - us_thei*HEADERLINES - 1;
	e->offx = win->uselx + 1;
	e->revy = e->shei + win->usely + 1;
	e->screenlines = e->shei / us_thei;
	e->screenchars = e->swid / us_twid;

	while (e->screenlines > e->maxlines) us_editemacsaddmorelines(e);
	for(i=0; i<e->maxlines; i++)
	{
		while (e->screenchars > e->maxchars[i])
			us_editemacsaddmorechars(e, i);
	}

	us_editemacsredrawscreen(win);
	us_editemacsflashcursor(win);
}

/*
 * keyboard interrupt routine for the text window
 */
INTSML us_editemacsgotchar(WINDOWPART *win, INTSML i)
{
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return(1);
	if ((i & 0200) != 0)
	{
		us_editemacsflashcursor(win);
		if (us_editemacsimplementchar(win, (INTSML)(i&0177), 1, 1) != 0) return(1);
		us_editemacsflashcursor(win);
	} else
	{
		us_editemacsflashcursor(win);
		if (us_editemacsimplementchar(win, i, 0, 1) != 0) return(1);
		us_editemacsflashcursor(win);
	}
	setactivity("EMACS Editing");
	return(0);
}

void us_editemacscut(WINDOWPART *w) { ttybeep(); }

void us_editemacscopy(WINDOWPART *w) { ttybeep(); }

void us_editemacspaste(WINDOWPART *w) { ttybeep(); }

void us_editemacsundo(WINDOWPART *w) { ttybeep(); }

void us_editemacssearch(WINDOWPART *w, char *str, INTSML fromtop) { ttybeep(); }

/******************** EDITOR CONTROL ********************/

/*
 * routine to implement EMACS character "i", with meta key held down if "m" is
 * 1.  The key is issued internally if "fromuser" is zero.  Routine returns
 * nonzero if the editor window has been terminated.
 */
INTSML us_editemacsimplementchar(WINDOWPART *win, INTSML i, INTSML m, INTSML fromuser)
{
	REGISTER EDITOR *e;
	INTSML j, k, count, savecur;
	char s[2], *pt, *paramstart[MAXPARS], *nextline;
	extern COMCOMP us_userp;
	REGISTER USERCOM *uc;

	e = win->editor;
	if (e == NOEDITOR) return(1);

	/* make sure line highlighting is off */
	us_editemacsoffhighlight(win);

	/* if ESCAPE was typed, set meta bit */
	if ((us_lastemacschar&ESCAPE) != 0)
	{
		m = 1;
		us_lastemacschar &= ~ESCAPE;
	}

	/* separate interpreter for ^X prefix */
	if ((us_lastemacschar&CONTROLX) != 0)
	{
		us_lastemacschar &= ~CONTROLX;

		/* save the text in a disk file */
		if (i == CTLW && m == 0)
		{
			if (fromuser != 0) us_editemacsshipchanges(win);
			pt = ttygetline("File name: ");
			if (pt == 0 || *pt == 0)
			{
				us_abortedmsg();
				return(0);
			}
			us_editemacswritetextfile(win, pt);
			return(0);
		}

		/* read a text from a disk file */
		if (i == CTLV && m == 0)
		{
			if (fromuser != 0) us_editemacsshipchanges(win);
			pt = ttygetline("File name: ");
			if (pt == 0 || *pt == 0)
			{
				us_abortedmsg();
				return(0);
			}
			us_editemacsreadtextfile(win, pt);
			return(0);
		}

		/* delete this window (^X^C doesn't work because ^C is trapped by sys) */
		if ((i == 'd' || i == CTLC) && m == 0)
		{
			if (fromuser != 0) us_editemacsshipchanges(win);
			return(1);
		}
		ttyputerr("Unrecognized command: ^X%s", us_keyname((INTSML)(i+(m==0?0:0200))));
		ttyputmsg("Use '^Xd' to exit, 'M(X)help emacs' for a list of commands");
		return(0);
	}

	/* ESCAPE prefix for the next character */
	if (i == ESC && m == 0)
	{
		us_lastemacschar |= ESCAPE;
		return(0);
	}

	/* ^X prefix for the next character */
	if (i == CTLX && m == 0)
	{
		us_lastemacschar |= CONTROLX;
		return(0);
	}

	/* self-insert of normal characters */
	if (i == '\t') i = ' ';
	if (i >= ' ' && i <= '~' && m == 0)
	{
		if (fromuser != 0) us_editemacsworkingoncurline(e);

		/* see if line needs to be extended */
		if ((INTBIG)strlen(e->textarray[e->curline]) >= e->maxchars[e->curline])
			us_editemacsaddmorechars(e, (INTSML)e->curline);

		/* see if new character is at end of line */
		if (e->textarray[e->curline][e->curchar] != 0)
		{
			/* it is not: shift letters on the line */
			for(j=e->maxchars[e->curline]; j>e->curchar; j--)
				e->textarray[e->curline][j] = e->textarray[e->curline][j-1];
			if (e->curline-e->firstline < e->screenlines && e->curline >= e->firstline &&
				e->curchar < e->screenchars-1 && (e->state&EGRAPHICSOFF) == 0)
			{
				us_editemacsmovebox(win, (INTSML)(e->curchar+1), (INTSML)(e->curline-e->firstline),
					(INTSML)(e->swid-e->curchar*us_twid-us_twid), us_thei, (INTSML)e->curchar,
						(INTSML)(e->curline-e->firstline));
				us_editemacscleanupline(win, (INTSML)e->curline);
			}
		} else e->textarray[e->curline][e->curchar+1] = 0;

		/* put the character on the display and into image memory */
		if (e->curline-e->firstline < e->screenlines &&
			e->curline >= e->firstline && e->curchar < e->screenchars &&
				(e->state&EGRAPHICSOFF) == 0)
		{
			us_editemacsclearbox(win, (INTSML)e->curchar, (INTSML)(e->curline-e->firstline), us_twid,
				us_thei);
			s[0] = (char)i;   s[1] = 0;
			us_editemacstext(win, s, (INTSML)e->curchar, (INTSML)(e->curline-e->firstline));
			us_editemacscleanupline(win, (INTSML)e->curline);
		}
		e->textarray[e->curline][e->curchar] = (char)i;

		/* advance the character pointer */
		e->curchar++;
		return(0);
	}

	/* search for a string */
	if (i == CTLS && m == 0)
	{
		pt = ttygetline("Search for: ");
		if (pt == 0) return(0);
		if (*pt == 0)
		{
			if (us_searchbuf == 0)
			{
				ttyputmsg("No previous search string");
				return(0);
			}
			pt = us_searchbuf;
		} else
		{
			if (us_searchbuf != 0) efree(us_searchbuf);
			(void)allocstring(&us_searchbuf, pt, us_aid->cluster);
		}

		/* search forward for the string */
		i = strlen(us_searchbuf);
		j = (INTSML)e->curline;
		k = (INTSML)e->curchar;
		for(;;)
		{
			/* if string matches, set cursor pointers */
			if (namesamen(us_searchbuf, &e->textarray[j][k], i) == 0)
			{
				if (fromuser != 0) us_editemacsshipchanges(win);
				e->curchar = k+i;
				e->curline = j;
				us_editemacsensuretextshown(win, (INTSML)e->curline);
				break;
			}

			/* advance by one character if not at the end of the line */
			if (e->textarray[j][k] != 0)
			{
				k++;
				continue;
			}

			/* advance to the next line */
			k = 0;
			j++;
			if (j >= e->maxlines)
			{
				ttyputmsg("Can't find \"%s\"", us_searchbuf);
				return(0);
			}
		}
		return(0);
	}

	/* reverse search for a string */
	if (i == CTLR && m == 0)
	{
		pt = ttygetline("Reverse search for: ");
		if (pt == 0) return(0);
		if (*pt == 0)
		{
			if (us_searchbuf == 0)
			{
				ttyputmsg("No previous search string");
				return(0);
			}
			pt = us_searchbuf;
		} else
		{
			if (us_searchbuf != 0) efree(us_searchbuf);
			(void)allocstring(&us_searchbuf, pt, us_aid->cluster);
		}

		/* search backward for the string */
		i = strlen(us_searchbuf);
		j = (INTSML)e->curline;
		k = e->curchar - 1;
		if (k < 0)
		{
			j--;
			if (j < 0)
			{
				ttyputmsg("Can't find \"%s\"", us_searchbuf);
				return(0);
			}
			k = strlen(e->textarray[j]) - 1;
		}
		for(;;)
		{
			/* if string matches, set cursor pointers */
			if (namesamen(us_searchbuf, &e->textarray[j][k], i) == 0)
			{
				if (fromuser != 0) us_editemacsshipchanges(win);
				e->curchar = k;
				e->curline = j;
				us_editemacsensuretextshown(win, (INTSML)e->curline);
				break;
			}

			/* backup by one character if not at the start of the line */
			if (k != 0)
			{
				k--;
				continue;
			}

			/* backup to the previous line */
			j--;
			if (j < 0)
			{
				ttyputmsg("Can't find \"%s\"", us_searchbuf);
				return(0);
			}
			k = strlen(e->textarray[j]) - 1;
		}
		return(0);
	}

	/* delete next character on a line (cannot delete line break) */
	if (i == CTLD && m == 0)
	{
		if (e->textarray[e->curline][e->curchar] == 0)
		{
			ttyputerr("Can only delete characters within a line.  Use ^K to remove lines.");
			return(0);
		}
		if (fromuser != 0) us_editemacsworkingoncurline(e);
		us_editemacsdeletechar(win);
		return(0);
	}

	/* delete next word on a line (cannot delete line break) */
	if (i == 'd' && m == 1)
	{
		if (fromuser != 0) us_editemacsworkingoncurline(e);

		/* first delete all nonalphanumeric characters */
		while (!isalnum(e->textarray[e->curline][e->curchar]) &&
			e->textarray[e->curline][e->curchar] != 0) us_editemacsdeletechar(win);

		/* if stopped at end-of-line, print error */
		if (e->textarray[e->curline][e->curchar] == 0)
		{
			ttyputerr("Can only delete characters within a line.  Use ^K to remove lines.");
			return(0);
		}

		/* now delete alphanumeric characters */
		while (isalnum(e->textarray[e->curline][e->curchar])) us_editemacsdeletechar(win);
		return(0);
	}

	/* delete previous character on a line (cannot delete line break) */
	if ((i == CTLH || i == DEL) && m == 0)
	{
		if (e->curchar <= 0)
		{
			ttyputerr("Can only delete characters within a line.  Use ^K to remove lines.");
			return(0);
		}
		if (fromuser != 0) us_editemacsworkingoncurline(e);
		e->curchar--;
		us_editemacsdeletechar(win);
		return(0);
	}

	/* delete previous word on a line (cannot delete line break) */
	if ((i == 'h' || i == DEL) && m == 1)
	{
		if (fromuser != 0) us_editemacsworkingoncurline(e);

		/* first backwards delete all nonalphanumeric characters */
		for(;;)
		{
			if (e->curchar <= 0) break;
			if (isalnum(e->textarray[e->curline][e->curchar-1])) break;
			e->curchar--;
			us_editemacsdeletechar(win);
		}

		/* if stopped at start-of-line, print error */
		if (e->curchar <= 0)
		{
			ttyputerr("Can only delete characters in a single line");
			return(0);
		}

		/* now backwards delete alphanumeric characters */
		for(;;)
		{
			e->curchar--;
			us_editemacsdeletechar(win);
			if (e->curchar <= 0) break;
			if (!isalnum(e->textarray[e->curline][e->curchar-1])) break;
		}
		return(0);
	}

	/* kill to end of line (delete line if at beginning of empty line) */
	if (i == CTLK && m == 0)
	{
		if (e->curchar == 0 && e->textarray[e->curline][e->curchar] == 0)
		{
			/* exit editor if only editing one line */
			if ((win->state&WINDOWTYPE) == POPTEXTWINDOW)
			{
				if (fromuser != 0) us_editemacsshipchanges(win);
				return(1);
			}

			/* cannot change number of lines if not allowed */
			if (fromuser != 0 && (e->state&LINESFIXED) != 0)
			{
				ttyputerr("Cannot delete lines in this edit session");
				return(0);
			}

			/* delete line in memory */
			if (fromuser != 0) us_editemacsshipchanges(win);
			savecur = (INTSML)e->curline;
			for(; e->curline < e->maxlines; e->curline++)
			{
				if (e->curline == e->maxlines-1) nextline = ""; else
					nextline = e->textarray[e->curline+1];
				if (strcmp(e->textarray[e->curline], nextline) == 0) continue;
				if (fromuser != 0) us_editemacsworkingoncurline(e);
				(void)strcpy(e->textarray[e->curline], nextline);
				if (fromuser != 0) us_editemacsshipchanges(win);
			}
			e->curline = savecur;

			/* delete line on the screen */
			if (e->curline-e->firstline < e->screenlines-1 &&
				e->curline >= e->firstline && (e->state&EGRAPHICSOFF) == 0)
			{
				us_editemacsmovebox(win, 0, (INTSML)(e->curline-e->firstline), e->swid,
					(INTSML)(e->shei-(e->curline-e->firstline+1)*us_thei), 0,
						(INTSML)(e->curline-e->firstline+1));
				if (e->screenlines < e->maxlines)
				{
					us_editemacsclearbox(win, 0, (INTSML)(e->screenlines-1), e->swid, us_thei);
					us_editemacsredrawline(win, (INTSML)(e->screenlines-1));
				}
			}
		} else
		{
			if (e->textarray[e->curline][e->curchar] == 0)
			{
				ttyputerr("Can only delete entire lines");
				return(0);
			}

			/* kill to end of line */
			if (fromuser != 0) us_editemacsworkingoncurline(e);
			(void)strcpy(us_killbuf, &e->textarray[e->curline][e->curchar]);
			if (e->curline-e->firstline < e->screenlines &&
				e->curline >= e->firstline && (e->state&EGRAPHICSOFF) == 0)
					us_editemacsclearbox(win, (INTSML)e->curchar, (INTSML)(e->curline-e->firstline),
						(INTSML)(e->swid-e->curchar*us_twid), us_thei);
			e->textarray[e->curline][e->curchar] = 0;
		}
		return(0);
	}

	if (i == CTLY && m == 0)
	{
		for(pt = us_killbuf; *pt != 0; pt++)
		{
			if (*pt == CTLY || *pt == CTLK) continue;
			(void)us_editemacsimplementchar(win, *pt, 0, fromuser);
		}
		return(0);
	}

	if (i == CTLL && m == 0)
	{
		us_editemacsredrawscreen(win);
		return(0);
	}

	if (i == CTLB && m == 0)
	{
		(void)us_editemacsbackupchar(win, fromuser);
		return(0);
	}

	if (i == 'b' && m == 1)
	{
		for(;;)
		{
			if (us_editemacsbackupchar(win, fromuser) != 0) break;
			if (isalnum(e->textarray[e->curline][e->curchar])) break;
		}
		for(;;)
		{
			if (e->curchar <= 0) break;
			if (!isalnum(e->textarray[e->curline][e->curchar-1])) break;
			(void)us_editemacsbackupchar(win, fromuser);
		}
		return(0);
	}

	if (i == CTLF && m == 0)
	{
		/* exit editor if at end of line and only editing one line */
		if (e->textarray[e->curline][e->curchar] == 0 && (win->state&WINDOWTYPE) == POPTEXTWINDOW)
		{
			if (fromuser != 0) us_editemacsshipchanges(win);
			return(1);
		}

		(void)us_editemacsadvancechar(win, fromuser);
		return(0);
	}

	if (i == 'f' && m == 1)
	{
		/* exit editor if at end of line and only editing one line */
		if (e->textarray[e->curline][e->curchar] == 0 && (win->state&WINDOWTYPE) == POPTEXTWINDOW)
		{
			if (fromuser != 0) us_editemacsshipchanges(win);
			return(1);
		}

		for(;;)
		{
			if (isalnum(e->textarray[e->curline][e->curchar])) break;
			if (us_editemacsadvancechar(win, fromuser) != 0) break;
		}
		for(;;)
		{
			if (us_editemacsadvancechar(win, fromuser) != 0) break;
			if (!isalnum(e->textarray[e->curline][e->curchar])) break;
		}
		return(0);
	}

	if (i == CTLA && m == 0)
	{
		e->curchar = 0;
		return(0);
	}

	if (i == CTLE && m == 0)
	{
		for(j=0; e->textarray[e->curline][j] != 0; j++) ;
		e->curchar = j;
		return(0);
	}

	if (i == CTLG && m == 0)
	{
		ttyputmsg("Analyzing this line");
		if (fromuser != 0) us_editemacsshipchanges(win);
		return(0);
	}

	if (i == CTLP && m == 0)
	{
		/* exit editor if only editing one line */
		if ((win->state&WINDOWTYPE) == POPTEXTWINDOW)
		{
			if (fromuser != 0) us_editemacsshipchanges(win);
			return(1);
		}

		if (e->curline <= 0) return(0);
		if (fromuser != 0)
		{
			us_editemacsshipchanges(win);
			if (e->curline == e->firstline && e->firstline > 0)
				us_editemacsshiftscreendown(win);
		}
		e->curline--;
		for(j=0; e->textarray[e->curline][j] != 0; j++) ;
		if (e->curchar > j) e->curchar = j;
		return(0);
	}

	if (i == CTLN && m == 0)
	{
		/* exit editor if only editing one line */
		if ((win->state&WINDOWTYPE) == POPTEXTWINDOW)
		{
			if (fromuser != 0) us_editemacsshipchanges(win);
			return(1);
		}

		if (e->curline >= e->maxlines-1) us_editemacsaddmorelines(e);
		if (fromuser != 0)
		{
			us_editemacsshipchanges(win);
			if (e->curline == e->firstline+e->screenlines-1)
				us_editemacsshiftscreenup(win);
		}
		e->curline++;
		for(j=0; e->textarray[e->curline][j] != 0; j++) ;
		if (e->curchar > j) e->curchar = j;
		return(0);
	}

	if (i == CTLZ && m == 0)
	{
		/* exit editor if only editing one line */
		if ((win->state&WINDOWTYPE) == POPTEXTWINDOW)
		{
			if (fromuser != 0) us_editemacsshipchanges(win);
			return(1);
		}

		us_editemacsshiftscreenup(win);
		if (fromuser != 0 && e->curline == e->firstline) e->curline++;
		return(0);
	}

	if (i == 'z' && m == 1)
	{
		/* exit editor if only editing one line */
		if ((win->state&WINDOWTYPE) == POPTEXTWINDOW)
		{
			if (fromuser != 0) us_editemacsshipchanges(win);
			return(1);
		}

		if (e->firstline <= 0) return(0);
		us_editemacsshiftscreendown(win);
		if (fromuser != 0 && e->curline == e->firstline+e->screenlines-1)
			e->curline--;
		return(0);
	}

	if (i == CTLV && m == 0)
	{
		/* exit editor if only editing one line */
		if ((win->state&WINDOWTYPE) == POPTEXTWINDOW)
		{
			if (fromuser != 0) us_editemacsshipchanges(win);
			return(1);
		}

		j = e->screenlines / 5 * 4;
		e->firstline += j;
		if ((e->state&EGRAPHICSOFF) == 0)
		{
			us_editemacsmovebox(win, 0, 0, e->swid, (INTSML)(us_thei*(e->screenlines-j)), 0, j);
			us_editemacsclearbox(win, 0, (INTSML)(e->screenlines-j), e->swid, (INTSML)(us_thei*j));
			for(k=0; k<j; k++)
			{
				if (e->screenlines-j+k+e->firstline < e->maxlines)
					us_editemacsredrawline(win, (INTSML)(e->screenlines-j+k));
			}
		}
		if (fromuser != 0 && e->curline < e->firstline)
		{
			us_editemacsshipchanges(win);
			e->curline = e->firstline + e->screenlines/2;
		}
		return(0);
	}

	if (i == 'v' && m == 1)
	{
		/* exit editor if only editing one line */
		if ((win->state&WINDOWTYPE) == POPTEXTWINDOW)
		{
			if (fromuser != 0) us_editemacsshipchanges(win);
			return(1);
		}

		if (e->firstline == 0) return(0);
		j = e->screenlines / 5 * 4;
		if (j > e->firstline) j = (INTSML)e->firstline;
		e->firstline -= j;
		if ((e->state&EGRAPHICSOFF) == 0)
		{
			us_editemacsmovebox(win, 0, j, e->swid, (INTSML)(us_thei*(e->screenlines-j)), 0, 0);
			us_editemacsclearbox(win, 0, 0, e->swid, (INTSML)(us_thei*j));
			for(k=0; k<j; k++)
			{
				if (e->firstline+k < e->maxlines) us_editemacsredrawline(win, k);
			}
		}
		if (fromuser != 0 && e->curline >= e->screenlines+e->firstline)
		{
			us_editemacsshipchanges(win);
			e->curline = e->firstline + e->screenlines/2;
		}
		return(0);
	}

	if (i == '>' && m == 1)
	{
		if (fromuser != 0) us_editemacsshipchanges(win);
		for(j=e->maxlines-1; j>=0; j--) if (e->textarray[j][0] != 0) break;
		e->curline = j+1;
		if (fromuser != 0 && e->curline >= e->firstline+e->screenlines)
		{
			e->firstline = e->curline - e->screenlines/2;
			us_editemacsredrawscreen(win);
		}
		e->curchar = 0;
		return(0);
	}

	if (i == '<' && m == 1)
	{
		if (fromuser != 0)
		{
			us_editemacsshipchanges(win);
			if (e->firstline != 0)
			{
				e->firstline = 0;
				us_editemacsredrawscreen(win);
			}
		}
		e->curline = 0;
		e->curchar = 0;
		return(0);
	}

	/* M(X) prefix for Electric commands */
	if (i == 'x' && m == 1)
	{
		if (fromuser != 0) us_editemacsshipchanges(win);
		count = ttygetparam(":", &us_userp, MAXPARS, paramstart);
		if (count == 0) return(0);
		uc = us_buildcommand(count, paramstart);
		if (uc == NOUSERCOM) return(0);
		us_execute(uc, 0, 1, 1);
		us_freeusercom(uc);
		return(0);
	}

	if (i == CTLO && m == 0)
	{
		/* exit editor if only editing one line */
		if ((win->state&WINDOWTYPE) == POPTEXTWINDOW)
		{
			if (fromuser != 0) us_editemacsshipchanges(win);
			return(1);
		}

		/* cannot change number of lines if not allowed */
		if (fromuser != 0 && (e->state&LINESFIXED) != 0)
		{
			ttyputerr("Cannot insert lines in this edit session");
			return(0);
		}

		/* must be at the beginning of a line */
		if (e->curchar != 0)
		{
			ttyputerr("Can only create new blank lines (use ^A first)");
			return(0);
		}

		/* see if there is room in the file */
		for(i=e->maxlines-1; i>=0; i--) if (e->textarray[i][0] != 0) break;
		if (i >= e->maxlines-1) us_editemacsaddmorelines(e);

		/* shift lines down */
		if (fromuser != 0) us_editemacsshipchanges(win);
		savecur = (INTSML)e->curline;
		for(e->curline = i+1; e->curline >= savecur; e->curline--)
		{
			if (e->curline == savecur) nextline = ""; else
				nextline = e->textarray[e->curline-1];
			if (strcmp(e->textarray[e->curline], nextline) == 0) continue;
			if (fromuser != 0) us_editemacsworkingoncurline(e);
			(void)strcpy(e->textarray[e->curline], nextline);
			if (fromuser != 0) us_editemacsshipchanges(win);
		}
		e->curline = savecur;
		if (e->curline-e->firstline < e->screenlines-1 &&
			e->curline >= e->firstline && (e->state&EGRAPHICSOFF) == 0)
				us_editemacsmovebox(win, 0, (INTSML)(e->curline+1-e->firstline),
					e->swid, (INTSML)(e->shei-(e->curline+1-e->firstline)*us_thei), 0,
						(INTSML)(e->curline-e->firstline));

		/* clear current line */
		e->textarray[e->curline][0] = 0;
		if (e->curline-e->firstline < e->screenlines &&
			e->curline >= e->firstline && (e->state&EGRAPHICSOFF) == 0)
				us_editemacsclearbox(win, 0, (INTSML)(e->curline-e->firstline), e->swid, us_thei);
		return(0);
	}

	if ((i == '\n' || i == '\r') && m == 0)
	{
		/* exit editor if only editing one line */
		if ((win->state&WINDOWTYPE) == POPTEXTWINDOW)
		{
			if (fromuser != 0) us_editemacsshipchanges(win);
			return(1);
		}

		/* cannot change number of lines if not allowed */
		if (fromuser != 0 && (e->state&LINESFIXED) != 0)
		{
			ttyputerr("Cannot insert lines in this edit session");
			return(0);
		}

		/* must be at the end of a line */
		if (e->textarray[e->curline][e->curchar] != 0)
		{
			ttyputmsg("Can only create new blank lines (use ^E first)");
			return(0);
		}

		/* see if there is room in the file */
		if (e->curline >= e->maxlines-1) us_editemacsaddmorelines(e);

		if (fromuser != 0)
		{
			us_editemacsshipchanges(win);
			if (e->curline == e->firstline+e->screenlines-1)
				us_editemacsshiftscreenup(win);
		}
		e->curline++;
		e->curchar = 0;

		/* shift lines down */
		savecur = (INTSML)e->curline;
		for(i=e->maxlines-1; i>=0; i--) if (e->textarray[i][0] != 0) break;
		for(e->curline = i+1; e->curline >= savecur; e->curline--)
		{
			if (e->curline == savecur) nextline = ""; else
				nextline = e->textarray[e->curline-1];
			if (strcmp(e->textarray[e->curline], nextline) == 0) continue;
			if (fromuser != 0) us_editemacsworkingoncurline(e);
			(void)strcpy(e->textarray[e->curline], nextline);
			if (fromuser != 0) us_editemacsshipchanges(win);
		}
		e->curline = savecur;
		if (e->curline-e->firstline < e->screenlines-1 &&
			e->curline >= e->firstline && (e->state&EGRAPHICSOFF) == 0)
				us_editemacsmovebox(win, 0, (INTSML)(e->curline+1-e->firstline),
					e->swid, (INTSML)(e->shei-(e->curline+1-e->firstline)*us_thei), 0,
						(INTSML)(e->curline-e->firstline));

		/* clear current line */
		e->textarray[e->curline][0] = 0;
		if (e->curline-e->firstline < e->screenlines &&
			e->curline >= e->firstline && (e->state&EGRAPHICSOFF) == 0)
				us_editemacsclearbox(win, 0, (INTSML)(e->curline-e->firstline), e->swid, us_thei);
		return(0);
	}

	/* flash the display */
	ttyputerr("Unrecognized key: %s", us_keyname((INTSML)(i+(m==0?0:0200))));
	ttyputmsg("Use '^Xd' to exit, 'M(X)help emacs' for a list of commands");
	return(0);
}

/*
 * routine to back the cursor by one character.  Returns nonzero if at the
 * start of the file
 */
INTSML us_editemacsbackupchar(WINDOWPART *win, INTSML fromuser)
{
	REGISTER EDITOR *e;
	REGISTER INTSML j;

	e = win->editor;
	if (e == NOEDITOR) return(1);

	if (e->curchar > 0)
	{
		e->curchar--;
		return(0);
	}
	if (e->curline <= 0) return(1);

	if (fromuser != 0)
	{
		us_editemacsshipchanges(win);
		if (e->curline == e->firstline && e->firstline > 0)
			us_editemacsshiftscreendown(win);
	}
	e->curline--;
	for(j=0; e->textarray[e->curline][j] != 0; j++) ;
	e->curchar = j;
	return(0);
}

/*
 * routine to advance the cursor by one character.  Returns nonzero if at the
 * end of the file
 */
INTSML us_editemacsadvancechar(WINDOWPART *win, INTSML fromuser)
{
	REGISTER EDITOR *e;
	REGISTER INTSML atend, j;

	e = win->editor;
	if (e == NOEDITOR) return(1);

	if (e->textarray[e->curline][e->curchar] != 0)
	{
		e->curchar++;
		return(0);
	}

	atend = 1;
	for(j=e->curline+1; j < e->maxlines; j++)
	{
		if (e->textarray[j][0] == 0) continue;
		atend = 0;
		break;
	}
	if (e->curline >= e->maxlines-1) us_editemacsaddmorelines(e);
	if (fromuser != 0)
	{
		us_editemacsshipchanges(win);
		if (e->curline == e->firstline+e->screenlines-1)
			us_editemacsshiftscreenup(win);
	}
	e->curline++;
	e->curchar = 0;
	return(atend);
}

/*
 * routine to delete the current character
 */
void us_editemacsdeletechar(WINDOWPART *win)
{
	REGISTER INTBIG j;
	char s[2];
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;

	for(j=e->curchar; j<e->maxchars[e->curline]; j++)
		e->textarray[e->curline][j] = e->textarray[e->curline][j+1];
	if (e->curline-e->firstline < e->screenlines && e->curline >= e->firstline &&
		e->curchar < e->screenchars && (e->state&EGRAPHICSOFF) == 0)
	{
		us_editemacsmovebox(win, (INTSML)e->curchar, (INTSML)(e->curline-e->firstline),
			(INTSML)(e->swid-e->curchar*us_twid-us_twid), us_thei,
				(INTSML)(e->curchar+1), (INTSML)(e->curline-e->firstline));
		if ((INTBIG)strlen(e->textarray[e->curline]) >= e->screenchars)
		{
			us_editemacsclearbox(win, (INTSML)(e->screenchars-1), (INTSML)(e->curline-e->firstline),
				us_twid, us_thei);
			s[0] = e->textarray[e->curline][e->screenchars-1];   s[1] = 0;
			us_editemacstext(win, s, (INTSML)(e->screenchars-1), (INTSML)(e->curline-e->firstline));
		}
		us_editemacscleanupline(win, (INTSML)e->curline);
	}
}

/*
 * routine to shift the text screen up one line
 */
void us_editemacsshiftscreenup(WINDOWPART *win)
{
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;

	e->firstline++;
	if ((e->state&EGRAPHICSOFF) != 0) return;
	us_editemacsmovebox(win, 0, 0, e->swid, (INTSML)(us_thei*(e->screenlines-1)), 0, 1);
	us_editemacsclearbox(win, 0, (INTSML)(e->screenlines-1), e->swid, us_thei);
	if (e->screenlines+e->firstline <= e->maxlines)
		us_editemacsredrawline(win, (INTSML)(e->screenlines-1));
}

/*
 * routine to shift the text screen down one line
 */
void us_editemacsshiftscreendown(WINDOWPART *win)
{
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;

	e->firstline--;
	if ((e->state&EGRAPHICSOFF) != 0) return;
	us_editemacsmovebox(win, 0, 1, e->swid, (INTSML)(us_thei*(e->screenlines-1)), 0, 0);
	us_editemacsclearbox(win, 0, 0, e->swid, us_thei);
	us_editemacsredrawline(win, 0);
}

/*
 * routine to ensure that line "line" is shown in the display
 */
void us_editemacsensuretextshown(WINDOWPART *win, INTSML line)
{
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;

	if (line-e->firstline >= e->screenlines || line < e->firstline)
	{
		e->firstline = maxi(0, line - e->screenlines/2);
		us_editemacsredrawscreen(win);
	}
}

/*
 * routine to redisplay the text screen
 */
void us_editemacsredrawscreen(WINDOWPART *win)
{
	REGISTER INTSML j;
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;
	if ((e->state&EGRAPHICSOFF) != 0) return;

	/* put header on display */
	us_editemacssetheader(win, e->header);

	/* clear the screen */
	us_editemacsclearbox(win, 0, 0, e->swid, e->shei);

	/* rewrite each line */
	for(j=0; j<e->screenlines; j++) if (j+e->firstline < e->maxlines)
		us_editemacsredrawline(win, j);
}

/*
 * routine to write line "j" of the screen
 */
void us_editemacsredrawline(WINDOWPART *win, INTSML j)
{
	REGISTER INTSML save;
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;

	if ((INTBIG)strlen(e->textarray[j+e->firstline]) > e->screenchars)
	{
		save = e->textarray[j+e->firstline][e->screenchars];
		e->textarray[j+e->firstline][e->screenchars] = 0;
	} else save = 0;
	us_editemacstext(win, e->textarray[j+e->firstline], 0, j);
	if (save != 0) e->textarray[j+e->firstline][e->screenchars] = (char)save;
}

/*
 * routine to write the header string
 */
void us_editemacssetheader(WINDOWPART *win, char *header)
{
	REGISTER INTSML save;
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;
	screendrawbox(win, (INTSML)(e->offx-1), (INTSML)(e->offx+e->swid), (INTSML)(e->revy+1),
		(INTSML)(e->revy+us_thei*HEADERLINES), &us_ebox);
	if ((INTBIG)strlen(header) > e->screenchars)
	{
		save = header[e->screenchars];
		header[e->screenchars] = 0;
	} else save = 0;
	us_editemacstext(win, header, 0, -2);
	if (strcmp(win->location, "entire") == 0)
		us_editemacstext(win, "This facet is purely textual", 0, -1); else
			us_editemacstext(win, "Type ^Xd when done editing", 0, -1);

	if (save != 0) header[e->screenchars] = (char)save;
	screeninvertbox(win, (INTSML)(e->offx-1), (INTSML)(e->offx+e->swid), (INTSML)(e->revy+1), (INTSML)(e->revy+us_thei*HEADERLINES));
	us_menufigs.col = el_colmengly;
	screendrawline(win, (INTSML)(e->offx-1), e->revy, (INTSML)(e->offx-1), (INTSML)(e->revy-e->shei-1),
		&us_menufigs, 0);
	screendrawline(win, (INTSML)(e->offx+e->swid+1), e->revy, (INTSML)(e->offx+e->swid+1),
		(INTSML)(e->revy-e->shei-1), &us_menufigs, 0);
	screendrawline(win, (INTSML)(e->offx-1), (INTSML)(e->revy-e->shei-1), (INTSML)(e->offx+e->swid+1),
		(INTSML)(e->revy-e->shei-1), &us_menufigs, 0);
}

/*
 * routine to invert the character cursor, turning it on or off
 */
void us_editemacsflashcursor(WINDOWPART *win)
{
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;

	if (e->curline-e->firstline < e->screenlines && e->curline >= e->firstline &&
		e->curchar < e->screenchars && (e->state&EGRAPHICSOFF) == 0)
			us_editemacsinvertbox(win, (INTSML)e->curchar, (INTSML)(e->curline-e->firstline), us_twid,
				us_thei);
}

/*
 * turn off the highlighting of a line of text (if one is highlighted)
 */
void us_editemacsoffhighlight(WINDOWPART *win)
{
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;

	if (e->highlightedline < 0) return;
	if (e->highlightedline-e->firstline < e->screenlines &&
		e->highlightedline >= e->firstline && (e->state&EGRAPHICSOFF) == 0)
			us_editemacsinvertbox(win, 0, (INTSML)(e->highlightedline-e->firstline), e->swid, us_thei);
	e->highlightedline = -1;
}

/*
 * routine to cleanup the bits at the end of a line that are not a full-width
 * character
 */
void us_editemacscleanupline(WINDOWPART *win, INTSML line)
{
	REGISTER INTSML residue;
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;

	residue = e->swid-e->screenchars*us_twid;
	if (residue == 0) return;
	us_editemacsclearbox(win, e->screenchars, (INTSML)(line-e->firstline), residue, us_thei);
}

/*
 * routine to declare that the user is now working on line "line"
 */
void us_editemacsworkingoncurline(EDITOR *e)
{
	if (e->working == -1)
	{
		/* first time: save the line */
		(void)strcpy(e->formerline, e->textarray[e->curline]);
		e->working = e->curline;
	}
}

/*
 * routine to declare that the user is done with line "line"
 */
void us_editemacsshipchanges(WINDOWPART *win)
{
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;

	if (e->working == -1) return;
	us_editemacsflashcursor(win);
	if (win->changehandler != 0)
		(*win->changehandler)(win, REPLACETEXTLINE, e->formerline, e->textarray[e->curline],
			e->working);
	us_editemacsflashcursor(win);
	e->working = -1;
}

/******************** ALLOCATION ********************/

/*
 * routine to double the number of lines in the text buffer
 */
void us_editemacsaddmorelines(EDITOR *e)
{
	REGISTER INTSML oldlines, *maxchars, i;
	REGISTER char **textarray;

	/* save former buffer size, double it */
	oldlines = (INTSML)e->maxlines;
	e->maxlines *= 2;

	/* allocate new arrays */
	maxchars = (INTSML *)emalloc((e->maxlines * SIZEOFINTSML), us_aid->cluster);
	if (maxchars == 0) ttyputerr("No memory for line lengths");
	textarray = (char **)emalloc((e->maxlines * (sizeof (char *))), us_aid->cluster);
	if (textarray == 0) ttyputerr("No memory for line pointers");

	/* copy old information */
	for(i=0; i<e->maxlines; i++)
	{
		if (i >= oldlines)
		{
			textarray[i] = (char *)emalloc((e->screenchars+1), us_aid->cluster);
			if (textarray[i] == 0) ttyputerr("No memory for line");
			textarray[i][0] = 0;
			maxchars[i] = e->screenchars;
		} else
		{
			textarray[i] = (char *)emalloc((e->maxchars[i]+1), us_aid->cluster);
			if (textarray[i] == 0) ttyputerr("No memory for line");
			(void)strcpy(textarray[i], e->textarray[i]);
			maxchars[i] = e->maxchars[i];
		}
	}

	/* free old arrays */
	for(i=0; i<oldlines; i++) efree(e->textarray[i]);
	efree((char *)e->textarray);
	efree((char *)e->maxchars);

	/* setup pointers correctly */
	e->textarray = textarray;
	e->maxchars = maxchars;
}

/*
 * routine to double the number of characters in line "line"
 */
void us_editemacsaddmorechars(EDITOR *e, INTSML line)
{
	REGISTER char *oldline;

	oldline = e->textarray[line];
	e->maxchars[line] *= 2;
	e->textarray[line] = (char *)emalloc((e->maxchars[line]+1), us_aid->cluster);
	(void)strcpy(e->textarray[line], oldline);
	if (e->maxchars[line] > e->mostchars)
	{
		e->mostchars = e->maxchars[line];
		efree(e->formerline);
		us_editemacsgetbuffers(e);
	}
}

/*
 * routine to allocate the single-line buffers
 */
void us_editemacsgetbuffers(EDITOR *e)
{
	e->formerline = (char *)emalloc((e->mostchars+1), us_aid->cluster);
	if (e->formerline == 0) ttyputerr("No memory for former line");
	if (e->mostchars > us_killbufchars)
	{
		if (us_killbufchars != 0) efree(us_killbuf);
		us_killbuf = (char *)emalloc(e->mostchars, us_aid->cluster);
		if (us_killbuf == 0) ttyputerr("No memory for kill buffer");
		us_killbufchars = e->mostchars;
	}
}

/******************** GRAPHIC SUPPORT ********************/

/*
 * move text starting at character position (sx,sy) to character position
 * (dx, dy).
 */
void us_editemacsmovebox(WINDOWPART *win, INTSML dx, INTSML dy, INTSML wid, INTSML hei, INTSML sx, INTSML sy)
{
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;
	screenmovebox(win, (INTSML)(sx*us_twid+e->offx), (INTSML)(e->revy-(sy*us_thei+hei-1)), wid, hei,
		(INTSML)(dx*us_twid+e->offx), (INTSML)(e->revy-(dy*us_thei+hei-1)));
}

/*
 * erase text starting at character position (dx,dy) for (wid, hei)
 */
void us_editemacsclearbox(WINDOWPART *win, INTSML dx, INTSML dy, INTSML wid, INTSML hei)
{
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;

	dx = (dx * us_twid) + e->offx;
	dy *= us_thei;
	screendrawbox(win, dx, (INTSML)(dx+wid-1), (INTSML)(e->revy-(dy+hei-1)), (INTSML)(e->revy-dy),
		&us_ebox);
}

/*
 * invert text starting at character position (dx,dy) for (wid, hei)
 */
void us_editemacsinvertbox(WINDOWPART *win, INTSML dx, INTSML dy, INTSML wid, INTSML hei)
{
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;

	dx = (dx * us_twid) + e->offx;
	dy *= us_thei;
	screeninvertbox(win, dx, (INTSML)(dx+wid-1), (INTSML)(e->revy-(dy+hei-1)), (INTSML)(e->revy-dy));
}

/*
 * write text "str" starting at character position (x,y)
 */
void us_editemacstext(WINDOWPART *win, char *str, INTSML x, INTSML y)
{
	REGISTER EDITOR *e;

	e = win->editor;
	if (e == NOEDITOR) return;

	screensettextsize(win, us_editemacsfont);
	us_menutext.col = el_colmentxt;
	screendrawtext(win, (INTSML)(x*us_twid + e->offx), (INTSML)(e->revy-(y+1)*us_thei), str, &us_menutext);
}
