/*
 * Copyright (C) 1992 by Rush Record
 * Copyright (C) 1993 by Charles Sandmann (sandmann@clio.rice.edu)
 * 
 * This file is part of ED.
 * 
 * ED 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.
 * 
 * ED 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 ED
 * (see the file COPYING).  If not, write to the Free Software Foundation, 675
 * Mass Ave, Cambridge, MA 02139, USA.
 */
#include "opsys.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXSTRING 64	/* the longest string of chars sent by a single keystroke */

#include "memory.h"
#include "file.h"
#include "ctyp_dec.h"
#include "rec.h"
#include "window.h"
#include "ed_dec.h"
#include "emul_dec.h"
#include "handy.h"
#include "term_cmd.h"
#include "keyvals.h"

typedef struct tree_str *tree_ptr;
typedef struct tree_str {Int branches,leaf,value;Schar *chars;tree_ptr *nodes;} tree_node;

static Char **coms;
static tree_node base = {0,0,0,NULL,NULL};
static Schar history[MAXSTRING];	/* the chars in the (still-being-analyzed) keystring so far */
static Schar *next_history;	/* where the next history char goes */
static Schar *report_history;	/* pointer to next history char to be returned */
static tree_ptr cur;	/* where we are in the tree */
static Schar chrbuf = 0;	/* to receive one char from keyboard */
static Int evolving = 0;	/* whether we are still getting chars from a defined key */
static Int scroll_top,scroll_bot;	/* the last set scrolling region boundaries */
static Int recovering = 0;	/* whether we are recovering from journal */
static Int processmode = 0;	/* whether we are in silent, 'process' mode */
static Int vtstartyet = 0;
static Schar *emulbuf = NULL;	/* buffer to hold emulation characters */
static Schar *emulptr;	/* points to next emulated key */
static Schar *emulend;	/* points to stopping point */
static Int *reptbuf;	/* buffer to hold repeat counts */
static Int *reptptr;	/* points to next repeat count */
static Int *reptend;	/* points to stopping point */
static Int emulsiz = 0;	/* current size of emulation buffer */

extern void up();
extern void down();
extern void vtstart();
extern Char *exefile();
extern Char *filename();

/******************************************************************************\
|Routine: emulate_abort
|Callby: abort_key
|Purpose: To stop emulation if a stop-the-defined-key situation arises.
|Arguments:
|    none
\******************************************************************************/
void emulate_abort()
{
	if(emulptr != emulend)
	{
		emulptr = emulend;
		reptptr = reptend;
		puton();
		ref_display();
	}
}

/******************************************************************************\
|Routine: emulate_key
|Callby: autowrp edit
|Purpose: To make it as though the user typed some keys.
|Arguments:
|    val is the value of the key we pretend the user pressed.
|    repeat is the repeat count for the key.
\******************************************************************************/
void emulate_key(val,repeat)
Schar val;
Int repeat;
{
	Schar *temp,*to,*from;
	Int *ltemp,*lto,*lfrom;
	
	if(repeat != 0)
	{
		if(emulend >= emulbuf + emulsiz)
		{
/* if there is space at beginning of array, slide everything down */
			if(emulptr > emulbuf)
			{
				for(from = emulptr,to = emulbuf;from != emulend;*to++ = *from++);
				emulptr  = emulbuf;
				emulend = to;
				for(lfrom = reptptr,lto = reptbuf;lfrom != reptend;*lto++ = *lfrom++);
				reptptr  = reptbuf;
				reptend = lto;
			}
			else	/* generate new buffers and copy */
			{
/* generate new char buffer */
				temp = (Schar *)imalloc(emulsiz << 1);
				memcpy(temp,emulbuf,emulsiz);
				emulend = temp + (emulend - emulbuf);
				emulptr = temp + (emulptr - emulbuf);
				ifree(emulbuf);
				emulbuf = temp;
/* generate new repeat buffer */
				ltemp = (Int *)imalloc((emulsiz << 1) * sizeof(Int));
				memcpy(ltemp,reptbuf,emulsiz * sizeof(Int));
				reptend = ltemp + (reptend - reptbuf);
				reptptr = ltemp + (reptptr - reptbuf);
				ifree(reptbuf);
				reptbuf = ltemp;
/* increase size */
				emulsiz <<= 1;
			}
		}
		*emulend++ = val;
		*reptend++ = repeat;
/*		if(repeat > 19)    11-93 CWS bug, no way to puton()!
			putoff(); */
	}
}

/******************************************************************************\
|Routine: set_processmode
|Callby: main
|Purpose: Determines whether display is turned back on after multiply-repeated
|         defined keys.
|Arguments:
|    mode is the 'process' mode flag.
\******************************************************************************/
void set_processmode(mode)
Int mode;
{
	processmode = mode;
}

/******************************************************************************\
|Routine: trans_term_string
|Callby: init_term restore_par
|Purpose: Handles the control-character quoting convention used in the
|         terminal configuration files. Control characters that might interfere
|         with reading the file are quoted with the ascii character 0x1d.
|Arguments:
|    buf is the buffer holding the string from the configuration file.
|    l is the returned (perhaps modified) length of the processed string.
\******************************************************************************/
void trans_term_string(buf,l)
Char *buf;
Int *l;
{
	register Int k,v;
	register Char *c,*start,*end;

	for(start = c = buf,end = buf + *l;buf != end;)
	{
		if((v = *buf++) == 0x1d)
			for(k = v = 0;k < 3;k++)
				v = 10 * v + *buf++ - '0';
		*c++ = v;
	}
	*c = 0;
	*l = c - start;
}

/******************************************************************************\
|Routine: init_terminal
|Callby: main
|Purpose: Initializes the terminal for editing. Processes the user-specified
|         terminal configuration file, and sets up the screen database.
|Arguments:
|    term is the name of the terminal configuration file.
|    setup is the name of the user's setup file.
|    cnrow,cncol are buffers containing the number of lines and columns on
|            the terminal screen.
\******************************************************************************/
void init_terminal(term,setup,cnrow,cncol)
Char *term,*setup,*cnrow,*cncol;
{
	FILE *fp,*ifp,*ofp;
	Int l,i,j,b,nkeys;
	tree_ptr t,*new,newnode;
	Char buf[512],defkeyfile[512];
	Schar *newchars;
	struct stat statbuf;

	if(my_sscanf(cnrow,"%d",&NROW) != 1)
	{
		printf("Number of screen lines is illegible (specified as %s).\r\n",cnrow);
		cleanup(-1);
	}
	if(my_sscanf(cncol,"%d",&NCOL) != 1)
	{
		printf("Number of screen columns is illegible (specified as %s).\r\n",cncol);
		cleanup(-1);
	}
	if(!NROW)
	{
		strcpy(buf,"$ROWS");
		envir_subs(buf);
		if(strcmp(buf,"$ROWS"))
		{
			if(my_sscanf(buf,"%d",&i) == 1)
				NROW = i;
		}
	}
	if(!NCOL)
	{
		strcpy(buf,"$COLS");
		envir_subs(buf);
		if(strcmp(buf,"$COLS"))
		{
			if(my_sscanf(buf,"%d",&i) == 1)
				NCOL = i;
		}
	}
	ttysetsize(&NROW,&NCOL);
	scroll_top = 1;
	scroll_bot = NROW;
	if(!emulbuf)
	{
		emulbuf = emulptr = emulend = (Schar *)imalloc(emulsiz = 512);
		reptbuf = reptptr = reptend = (Int *)imalloc(emulsiz * sizeof(Int));
	}
	coms = (Char **)imalloc((int)NCOMS * sizeof(Char *));
	EMU_NROW = NROW;	/* set up emulation layer */
	EMU_NCOL = NCOL;
	EMU_CURATT = 0;
	EMU_SCREEN = (Char *)imalloc(EMU_NROW*EMU_NCOL);
	EMU_ATTRIB = (Char *)imalloc(EMU_NROW*EMU_NCOL);
	memset(EMU_SCREEN,' ',EMU_NCOL * EMU_NROW);
	memset(EMU_ATTRIB,0,EMU_NCOL * EMU_NROW);
	EMU_CURROW = -1;	/* flag that it is not defined for put */
	if(!(fp = fopen(term,"r")))
	{
		printf("Can't open configuration file %s.\r\n",term);
		cleanup(-1);
	}
	BRAINDEAD = 1;
	for(i = 0;i < (int)NCOMS;i++)
	{
		if(!fgets(buf,sizeof(buf),fp))
		{
			printf("Error reading configuration file.\r\n");
			cleanup(-1);
		}
		l = strlen(buf) - 1;
		buf[l] = '\0';
		trans_term_string(buf,&l);
		coms[i] = (Char *)imalloc(l + 1);
		strcpy(coms[i],buf);
		if(i != (int)EOB && coms[i][0] != '~')
			BRAINDEAD = 0;
	}
	my_fscanf(fp,"%d\n",&nkeys);
	for(i = 1;i <= nkeys;i++)
	{
		if(!fgets(buf,sizeof(buf),fp))
		{
			printf("Error reading key strings from configuration file.\r\n");
			cleanup(-1);
		}
		buf[(l = strlen(buf) - 1)] = '\0';
		trans_term_string(buf,&l);
		t = &base;
		for(j = 0;j < l;j++)
		{
			for(b = 0;b < t->branches;b++)
				if(t->chars[b] == buf[j])
					break;
			if(b < t->branches)	/* match for char at current node, just advance to child node */
				t = t->nodes[b];
			else	/* no match for this char at this node */
			{
/* allocate a bigger array of tree pointers, transfer the old if any, and add the new pointer at the end */
				new = (tree_ptr *)imalloc((t->branches + 1)*sizeof(tree_ptr));
				if(t->branches > 0)
				{
					memcpy(new,t->nodes,t->branches * sizeof(tree_ptr));
					ifree(t->nodes);
				}
				t->nodes = new;
				newnode = new[t->branches] = (tree_ptr)imalloc(sizeof(tree_node));
/* set the new node up as empty */
				newnode->branches = newnode->leaf = 0;
/* allocate a bigger array for characters, transfer the old, add the new char at the end */
				newchars = (Schar *)imalloc(t->branches + 1);
				if(t->branches > 0)
				{
					memcpy(newchars,t->chars,t->branches);
					ifree(t->chars);
				}
				t->chars = newchars;
				newchars[t->branches] = buf[j];
/* one more char at this node now */
				t->branches++;
/* we now sit at new node */
				t = newnode;
			}
		}	/* get next char in string */
/* flag the leaf (pointed to by t) as a termination */
		t->leaf = 1;
		t->value = -i;
	}	/* get next key string */
	fclose(fp);
	cur = &base;	/* initialize tree search */
	next_history = history;	/* point to start of history buffer */
	vtstart();
/* do the user-specific setup file */
	strcpy(KEYFILE,setup);
	strip_quotes(KEYFILE);
	envir_subs(KEYFILE);
	if(stat(KEYFILE,&statbuf))
	{
#ifdef VMS
		if((ofp = fopen(KEYFILE,"w","ctx=rec","rfm=var","rat=cr","shr=nil")))
#else
		if((ofp = fopen(KEYFILE,"w")))
#endif
		{
			strcpy(defkeyfile,exefile());
			strcpy(filename(defkeyfile),"ed.setup");
			if((ifp = fopen(defkeyfile,"r")))
			{
				while(fgets(buf,sizeof(buf),ifp))
					fputs(buf,ofp);
				fclose(ifp);
			}
			fclose(ofp);
		}
	}
/* these have to be set to make wait_message work right (restore_par() may call slip_message/wait_message) */
	BOTROW = NROW;
	FIRSTCOL = 1;
	restore_par(-1);	/* the -1 means restore all params, not just one in particular */
}

/******************************************************************************\
|Routine: command_one
|Callby: init_term
|Purpose: Issues a terminal command that has one integer parameter.
|Arguments:
|    command is the terminal command buffer.
|    parm is the single parameter.
\******************************************************************************/
void command_one(command,parm)
Char *command;
Int parm;
{
	register Int i;
	register Char *p,*q,c;
	Char buf[128];

	if(puttest())
	{
		for(p = command,q = buf;(c = *p++);)
		{
			if(c == '%')
			{
				p++;
				if(parm > 99)
				{
					*q++ = (i = (parm / 100)) + '0';
					parm -= 100 * i;
					*q++ = (i = (parm / 10)) + '0';
					parm -= 10 * i;
					*q++ = parm + '0';
				}
				else if(parm > 9)
				{
					*q++ = (i = (parm / 10)) + '0';
					parm -= 10 * i;
					*q++ = parm + '0';
				}
				else
					*q++ = parm + '0';
			}
			else
				*q++ = c;
		}
		*q = '\0';
		put(buf);
	}
}

/******************************************************************************\
|Routine: command_two
|Callby: init_term
|Purpose: Issues a terminal command that has two integer parameters.
|Arguments:
|    command is the terminal command buffer.
|    parm1,parm2 are the parameters.
\******************************************************************************/
void command_two(command,parm1,parm2)
Char *command;
Int parm1,parm2;
{
	register Int i,j;
	register Char *p,*q,c;
	Char buf[128];

	if(puttest())
	{
		j = parm1;
		for(p = command,q = buf;(c = *p++);)
		{
			if(c == '%')
			{
				p++;
				if(j > 99)
				{
					*q++ = (i = (j / 100)) + '0';
					j -= 100 * i;
					*q++ = (i = (j / 10)) + '0';
					j -= 10 * i;
					*q++ = j + '0';
				}
				else if(j > 9)
				{
					*q++ = (i = (j / 10)) + '0';
					j -= 10 * i;
					*q++ = j + '0';
				}
				else
					*q++ = j + '0';
				j = parm2;
			}
			else
				*q++ = c;
		}
		*q = '\0';
		put(buf);
	}
}

/******************************************************************************\
|Routine: vtstart
|Callby: init_term wincom
|Purpose: Sets the terminal to a known state.
|Arguments:
|    none
\******************************************************************************/
void vtstart()
{
	ttystart();
	if(!BRAINDEAD) put(coms[VTSTART]);
	vtstartyet = 1;
	EMU_CURATT = 0;
}

/******************************************************************************\
|Routine: move
|Callby: bigger_win cfg command edit fix_scroll help imalloc init_term inquire insert insert_win killer load_key match_paren paint paint_window parse_fnm ref_window remove_win scroll_down scroll_up slip_message trim unselect wincom
|Purpose: Positions the cursor.
|Arguments:
|    row is the line number on the screen. The top line is line 1.
|    col is the column number. The leftmost column is column 1.
\******************************************************************************/
void move(row,col)
Int row,col;
{
	col -= FIRSTCOL - 1;
	row = max(1,min(EMU_NROW,row));
	col = max(1,min(EMU_NCOL,col));
	if(coms[MOVE][0] != '~')
		command_two(coms[MOVE],row,col);
	EMU_CURROW = --row;
	EMU_CURCOL = --col;
}

/******************************************************************************\
|Routine: marge
|Callby: bigger_win cfg command edit help init_term insert insert_win killer parse_fnm set_window smaller_win
|Purpose: Sets the scrolling region on the screen.
|Arguments:
|    row1 is the top line of the scrolling region.
|    row2 is the bottom line of the scrolling region.
\******************************************************************************/
void marge(row1,row2)
Int row1,row2;
{
	if(coms[MARGE][0] != '~')
		command_two(coms[MARGE],row1,row2);
	scroll_top = max(1,min(EMU_NROW,row1));
	scroll_bot = max(1,min(EMU_NROW,row2));
}

/******************************************************************************\
|Routine: del_line
|Callby: bigger_win init_term insert insert_win killer scroll_up
|Purpose: Deletes lines, causing lines below to scroll up.
|Arguments:
|    lines is the number of lines to delete.
\******************************************************************************/
void del_line(lines)
Int lines;
{
	register Int i,from,to,save;

	i = scroll_bot - EMU_CURROW;
	lines = max(1,min(i,lines)); 
	if(coms[DEL_LINE][0] == '~')
	{
		if(coms[MARGE][0] == '~')
		{
			for(i = EMU_CURROW + lines;i < scroll_bot;i++)
			{
				from = i * EMU_NCOL;
				to = from - EMU_NCOL * lines;
				memcpy(EMU_SCREEN + to,EMU_SCREEN + from,EMU_NCOL);
				memcpy(EMU_ATTRIB + to,EMU_ATTRIB + from,EMU_NCOL);
			}
			for(i = scroll_bot - 1;i >= scroll_bot - lines;i--)
			{
				memset(EMU_SCREEN + i * EMU_NCOL,' ',EMU_NCOL);
				memset(EMU_ATTRIB + i * EMU_NCOL,0,EMU_NCOL);
			}
			if(!BRAINDEAD) ref_display();
		}
		else
		{
			save = scroll_top;
			marge(EMU_CURROW + 1,scroll_bot);
			move(scroll_bot,1);
			for(i = 0;i < lines;i++)
				down();
			marge((scroll_top = save),scroll_bot);
		}
	}
	else
	{
		if(coms[MARGE][0] == '~')
		{
			lines = max(1,min(lines,scroll_bot - EMU_CURROW));
			command_one(coms[DEL_LINE],lines);
			if(scroll_bot != EMU_NROW)
			{
				Int saverow,savecol;
				
				saverow = EMU_CURROW + 1;
				savecol = EMU_CURCOL + 1;
				move(scroll_bot - lines + 1,1);
				if(coms[INS_LINE][0] != '~')
					command_one(coms[INS_LINE],lines);
				move(saverow,savecol);
			}
		}
		else
			command_one(coms[DEL_LINE],lines);
		for(i = EMU_CURROW + lines;i < scroll_bot;i++)
		{
			from = i * EMU_NCOL;
			to = from - EMU_NCOL * lines;
			memcpy(EMU_SCREEN + to,EMU_SCREEN + from,EMU_NCOL);
			memcpy(EMU_ATTRIB + to,EMU_ATTRIB + from,EMU_NCOL);
		}
		for(i = scroll_bot - 1;i >= scroll_bot - lines;i--)
		{
			memset(EMU_SCREEN + i * EMU_NCOL,' ',EMU_NCOL);
			memset(EMU_ATTRIB + i * EMU_NCOL,0,EMU_NCOL);
		}
	}
}

/******************************************************************************\
|Routine: ins_line
|Callby: bigger_win init_term insert killer scroll_down
|Purpose: Inserts lines at the cursor, causing lines below to scroll down.
|Arguments:
|    lines is the number of lines to insert.
\******************************************************************************/
void ins_line(lines)
Int lines;
{
	register Int i,from,to,save;

	i = scroll_bot - EMU_CURROW;
	lines = max(1,min(i,lines)); 
	if(coms[INS_LINE][0] == '~')
	{
		if(coms[MARGE][0] == '~')
		{
			for(i = scroll_bot - 1 - lines;i >= EMU_CURROW;i--)
			{
				from = i * EMU_NCOL;
				to = from + EMU_NCOL * lines;
				memcpy(EMU_SCREEN + to,EMU_SCREEN + from,EMU_NCOL);
				memcpy(EMU_ATTRIB + to,EMU_ATTRIB + from,EMU_NCOL);
			}
			for(i = EMU_CURROW;i < EMU_CURROW + lines;i++)
			{
				memset(EMU_SCREEN + i * EMU_NCOL,' ',EMU_NCOL);
				memset(EMU_ATTRIB + i * EMU_NCOL,0,EMU_NCOL);
			}
			if(!BRAINDEAD) ref_display();
		}
		else
		{
			save = scroll_top;
			marge(EMU_CURROW + 1,scroll_bot);
			move(scroll_top,1);
			for(i = 0;i < lines;i++)
				up();
			marge((scroll_top = save),scroll_bot);
		}
	}
	else
	{
		if(coms[MARGE][0] == '~')
		{
			lines = max(1,min(lines,scroll_bot - EMU_CURROW));
			if(scroll_bot < EMU_NROW)
			{
				Int saverow,savecol;
				
				saverow = EMU_CURROW + 1;
				savecol = EMU_CURCOL + 1;
				move(scroll_bot - lines + 1,1);
				if(coms[DEL_LINE][0] != '~')
					command_one(coms[DEL_LINE],lines);
				move(saverow,savecol);
			}
		}
		command_one(coms[INS_LINE],lines);
		for(i = scroll_bot - 1 - lines;i >= EMU_CURROW;i--)
		{
			from = i * EMU_NCOL;
			to = from + EMU_NCOL * lines;
			memcpy(EMU_SCREEN + to,EMU_SCREEN + from,EMU_NCOL);
			memcpy(EMU_ATTRIB + to,EMU_ATTRIB + from,EMU_NCOL);
		}
		for(i = EMU_CURROW;i < EMU_CURROW + lines;i++)
		{
			memset(EMU_SCREEN + i * EMU_NCOL,' ',EMU_NCOL);
			memset(EMU_ATTRIB + i * EMU_NCOL,0,EMU_NCOL);
		}
	}
}

/******************************************************************************\
|Routine: up
|Callby: init_term
|Purpose: Moves the cursor up one line. Scrolls down if the cursor is at the
|         top of the scrolling region.
|Arguments:
|    none
\******************************************************************************/
void up()
{
	if(coms[MARGE][0] == '~' && EMU_CURROW == scroll_top - 1)
	{
		ins_line(1);
		return;
	}
	if(coms[UP][0] != '~')
		put(coms[UP]);
	if(EMU_CURROW == scroll_top - 1)
	{
		Int i,from,to;
		
		for(i = scroll_bot - 2;i >= EMU_CURROW;i--)
		{
			from = i * EMU_NCOL;
			to = from + EMU_NCOL;
			memcpy(EMU_SCREEN + to,EMU_SCREEN + from,EMU_NCOL);
			memcpy(EMU_ATTRIB + to,EMU_ATTRIB + from,EMU_NCOL);
		}
		memset(EMU_SCREEN + EMU_CURROW * EMU_NCOL,' ',EMU_NCOL);
		memset(EMU_ATTRIB + EMU_CURROW * EMU_NCOL,0,EMU_NCOL);
	}
	else
	{
		if(--EMU_CURROW < 0)
			EMU_CURROW = 0;
	}
}

/******************************************************************************\
|Routine: down
|Callby: init_term
|Purpose: Moves the cursor down one line. Scrolls up if the cursor is at the
|         bottom of the scrolling region.
|Arguments:
|    none
\******************************************************************************/
void down()
{
	if(coms[MARGE][0] == '~' && EMU_CURROW == scroll_bot - 1)
	{
		Int savecol;

		savecol = EMU_CURCOL + 1;
		move(scroll_top,1);
		del_line(1);
		move(scroll_bot,savecol);
		return;
	}
	if(coms[DOWN][0] != '~')
		put(coms[DOWN]);
	if(EMU_CURROW == scroll_bot - 1)
	{
		Int i,from,to;
		
		for(i = scroll_top - 1;i < scroll_bot - 1;i++)
		{
			to = i * EMU_NCOL;
			from = to + EMU_NCOL;
			memcpy(EMU_SCREEN + to,EMU_SCREEN + from,EMU_NCOL);
			memcpy(EMU_ATTRIB + to,EMU_ATTRIB + from,EMU_NCOL);
		}
		memset(EMU_SCREEN + (scroll_bot - 1) * EMU_NCOL,' ',EMU_NCOL);
		memset(EMU_ATTRIB + (scroll_bot - 1) * EMU_NCOL,0,EMU_NCOL);
	}
	else
	{
		if(++EMU_CURROW >= EMU_NROW)
			EMU_CURROW = EMU_NROW - 1;
	}
}

/******************************************************************************\
|Routine: reverse
|Callby: command edit help inquire load_key match_paren ref_window remove_win select slip_message
|Purpose: Turns on reverse video.
|Arguments:
|    none
\******************************************************************************/
void reverse()
{
	if(coms[REVERSE][0] != '~')
		put(coms[REVERSE]);
	EMU_CURATT |= 1;
}

/******************************************************************************\
|Routine: normal
|Callby: command edit help inquire load_key match_paren ref_window remove_win select slip_message
|Purpose: Turns off reverse video, bold, underline, flash.
|Arguments:
|    none
\******************************************************************************/
void normal()
{
	if(coms[NORMAL][0] != '~')
		put(coms[NORMAL]);
	EMU_CURATT = 0;
}

/******************************************************************************\
|Routine: bell
|Callby: command edit wincom
|Purpose: Beeps the terminal.
|Arguments:
|    none
\******************************************************************************/
void bell()
{
	if(coms[BELL][0] != '~')
		put(coms[BELL]);
	else
		ring_bell();
}

/******************************************************************************\
|Routine: ers_screen
|Callby: cfg ref_display
|Purpose: Erases the entire screen.
|Arguments:
|    none
\******************************************************************************/
void ers_screen()
{
	if(coms[ERS_SCREEN][0] != '~')
		put(coms[ERS_SCREEN]);
	memset(EMU_SCREEN,' ',EMU_NCOL * EMU_NROW);
	memset(EMU_ATTRIB,0,EMU_NCOL * EMU_NROW);
}

/******************************************************************************\
|Routine: ers_end
|Callby: edit help init_term inquire load_key paint paint_window parse_fnm ref_window remove_win slip_message
|Purpose: Erases everything to the right of the cursor on the current line.
|Arguments:
|    none
\******************************************************************************/
void ers_end()
{
	register Int off;
	register Char *s,*a,*end;

	off = EMU_CURROW * EMU_NCOL + EMU_CURCOL;
	s = EMU_SCREEN + off;
	a = EMU_ATTRIB + off;
	end = s + EMU_NCOL - EMU_CURCOL;
	while(s != end)
	{
		if(*s != ' ' || *a != 0)
			break;
		s++;
		a++;
	}
	if(s != end)
	{
		if(coms[ERS_END][0] != '~')
			put(coms[ERS_END]);
		memset(EMU_SCREEN + off,' ',EMU_NCOL - EMU_CURCOL);
		memset(EMU_ATTRIB + off,0,EMU_NCOL - EMU_CURCOL);
	}
}

/******************************************************************************\
|Routine: right
|Callby: paint paint_window put
|Purpose: Moves the cursor to the right.
|Arguments:
|    cols is the number of columns to move.
\******************************************************************************/
void right(cols)
Int cols;
{
	if((cols = min(EMU_NCOL - EMU_CURCOL - 1,cols)))
	{
		if(coms[RIGHT][0] != '~')
			command_one(coms[RIGHT],cols);
		EMU_CURCOL += cols;
	}
}

/******************************************************************************\
|Routine: left
|Callby: not called
|Purpose: Moves the cursor to the left.
|Arguments:
|    cols is the number of columns to move.
\******************************************************************************/
void left(cols)
Int cols;
{
	if((cols = min(EMU_CURCOL,cols)))
	{
		if(coms[LEFT][0] != '~')
			command_one(coms[LEFT],cols);
		EMU_CURCOL -= cols;
	}
}

/******************************************************************************\
|Routine: next
|Callby: cfg help paint paint_window parse_fnm
|Purpose: Moves the cursor to the beginning of the next line on the screeen.
|         Scrolls up if the cursor is at the bottom of the scrolling region.
|Arguments:
|    none
\******************************************************************************/
void next()
{
	if(coms[NEXT][0] != '~')
		put(coms[NEXT]);
	EMU_CURCOL = 0;
	if(EMU_CURROW == scroll_bot - 1)
	{
		Int i,from,to;
		
		for(i = scroll_top - 1;i < scroll_bot - 1;i++)
		{
			to = i * EMU_NCOL;
			from = to + EMU_NCOL;
			memcpy(EMU_SCREEN + to,EMU_SCREEN + from,EMU_NCOL);
			memcpy(EMU_ATTRIB + to,EMU_ATTRIB + from,EMU_NCOL);
		}
		memset(EMU_SCREEN + (scroll_bot - 1) * EMU_NCOL,' ',EMU_NCOL);
		memset(EMU_ATTRIB + (scroll_bot - 1) * EMU_NCOL,0,EMU_NCOL);
	}
	else
	{
		if(++EMU_CURROW >= EMU_NROW)
			EMU_CURROW = EMU_NROW - 1;
	}
}

/******************************************************************************\
|Routine: eob
|Callby: insert killer paint paint_window unselect
|Purpose: Displays the end-of-buffer string (default is [eob]).
|Arguments:
|    none
\******************************************************************************/
void eob()
{
	putz(coms[EOB]);
}

/******************************************************************************\
|Routine: cr
|Callby: cfg help inquire paint paint_window unselect
|Purpose: Moves the cursor to the beginning of the current line.
|Arguments:
|    none
\******************************************************************************/
void cr()
{
	if(coms[CR][0] != '~')
		put(coms[CR]);
	EMU_CURCOL = 0;
}

/******************************************************************************\
|Routine: ers_bottom
|Callby: insert_win wincom
|Purpose: Erases from the cursor to the bottom of the screen.
|Arguments:
|    none
\******************************************************************************/
void ers_bottom()
{
	register Int off,ndel;
	
	if(coms[ERS_BOTTOM][0] != '~')
		put(coms[ERS_BOTTOM]);
	off = EMU_CURROW * EMU_NCOL + EMU_CURCOL;
	ndel = EMU_NROW * EMU_NCOL - off;
	memset(EMU_SCREEN + off,' ',ndel);
	memset(EMU_ATTRIB + off,0,ndel);
}

/******************************************************************************\
|Routine: vtend
|Callby: parse_fnm restore_par wincom
|Purpose: Resets the terminal when ED terminates.
|Arguments:
|    none
\******************************************************************************/
void vtend()
{
	if(vtstartyet && !BRAINDEAD) put(coms[VTEND]);
	if(BRAINDEAD)
	{
		EMU_CURCOL = 0;
		ers_end();
		if(--EMU_CURROW < 0) EMU_CURROW = 0;
	}
	putout();
	ttyend();
	EMU_CURATT = 0;
}

/******************************************************************************\
|Routine: unmatchable
|Callby: wincom
|Purpose: Renders an area on the screen 'unmatchable' to insure new data bound
|         there is actually displayed.
|Arguments:
|    top is the top line of the area to be made unmatchable.
|    bot is the bottom line of the area.
\******************************************************************************/
void unmatchable(top,bot)
Int top,bot;
{
	memset(EMU_ATTRIB + (top - 1) * EMU_NCOL,-1,(bot - top + 1) * EMU_NCOL);
}

/******************************************************************************\
|Routine: get_next_key
|Callby: edit help inquire load_key slip_message
|Purpose: Gets the next character in the input stream (either from the terminal
|         or the journal file). Returns the repeat count to be applied to the
|         key.
|Arguments:
|    buf is the return key value.
\******************************************************************************/
Int get_next_key(buf)
Schar *buf;
{
	register Int b,i;
	Int repeat;

/* if emulated keys await, do that */
	if(emulptr < emulend)
	{
		*buf = *emulptr++;
		return(*reptptr++);
	}
/* if a defined key is evolving, get from there */
	if(evolving)
	{
		if((evolving = next_key(&chrbuf,&repeat)))
		{
			*buf = chrbuf;
			return(repeat);
		}
		if(was_putoff() && !processmode)
		{
			puton();
			ref_display();
			move(CURROW,CURCOL);
		}
	}
/* if recovery is in progress, get from there */
	if(recovering)
	{
		unjournal(buf,&repeat);
		if(repeat > 0)
		{
			if((evolving = defined_key(*buf,repeat)))
			{
				next_key(&chrbuf,&repeat);
				*buf = chrbuf;
			}
			return(repeat);
		}
		if(processmode)
		{
			printf("That journal file terminates abnormally.\r\n");
			cleanup(-1);
		}
		recovering = 0;
	}
/* dump the output buffer and prepare */
	putout();
	repeat = 1;
/* report_history contains input bytes that only partially matched a known sequence */
	if(report_history)
	{
		if(report_history == next_history)	/* we hit the end of the history buffer, wait for more input */
		{
			next_history = history;
			report_history = NULL;
			cur = &base;
		}
		else
		{
			*buf = *report_history++;
			journal(*buf,1);
			if((evolving = defined_key(*buf,1)))	/* report from history buffer */
			{
				next_key(&chrbuf,&repeat);
				*buf = chrbuf;
			}
			return(repeat);
		}
	}
/* no other form of input, get from the terminal */
	while(1)
	{
		ttyget(&chrbuf);	/* get byte from user */
traverse:
		for(b = 0;b < cur->branches;b++)	/* see if it is known at current node */
			if(cur->chars[b] == chrbuf)
				break;
		if(b < cur->branches)	/* it exists at this node */
		{
			*next_history++ = chrbuf;	/* store in history buffer, in case we get a miss later */
			cur = cur->nodes[b];	/* advance to subnode for that byte */
			if(cur->leaf && !cur->branches)	/* it is a leaf, report it */
			{
				*buf = cur->value;	/* return value */
				next_history = history;	/* clear the history buffer */
				cur = &base;	/* initialize the tree search */
				journal(*buf,repeat);
				if((evolving = defined_key(*buf,repeat)))
				{
					next_key(&chrbuf,&repeat);
					*buf = chrbuf;
				}
				return(repeat);
			}
		}
		else	/* a miss */
		{
			if(cur == &base)	/* special, simple, handling for top-level miss (a common occurrence) */
			{
				*buf = chrbuf;
				journal(*buf,repeat);
				if((evolving = defined_key(*buf,repeat)))
				{
					next_key(&chrbuf,&repeat);
					*buf = chrbuf;
					return(repeat);
				}
			}
			else
			{
				if(cur->leaf)	/* previous sequence was allowed to (optionally) terminate, stick its value on stack */
				{
					if(cur->value == KEY_GOLD)
					{
						if(isdigit(chrbuf))	/* special handling for GOLD */
						{
							repeat = 0;
							while(isdigit(chrbuf))  /* acquire digits and build up repeat count */
							{
								repeat = 10 * repeat + chrbuf - '0';
								ttyget(&chrbuf);
							}
							cur = &base;
							next_history = history;
							goto traverse;
						}
						else	/* flag gold-<key> with negative repeat count */
						{
							i = toupper(chrbuf);
							if(i >= 'A' && i <= 'Z')
								chrbuf = i + 0x80 - 'A';
							cur = &base;
							next_history = history;
							goto traverse;
						}
					}
					history[0] = cur->value;
					next_history = history+1;
				}
				*next_history++ = chrbuf;
				report_history = history + 1;
				*buf = history[0];
				journal(*buf,repeat);
				if((evolving = defined_key(*buf,repeat)))
				{
					next_key(&chrbuf,&repeat);
					*buf = chrbuf;
				}
			}
			return(repeat);
		}
	}
}

/******************************************************************************\
|Routine: set_recover
|Callby: journal
|Purpose: Sets a flag that tells get_next_key to get characters from the
|         journal file instead of the terminal.
|Arguments:
|    none
\******************************************************************************/
void set_recover()
{
	recovering = 1;
}

/******************************************************************************\
|Routine: get_eob_char
|Callby: select
|Purpose: Returns the first character of the user's <eob> string.
|Arguments:
|    none
\******************************************************************************/
Int get_eob_char()
{
	register Int i;
	
	if(!(i = coms[EOB][0]))
		i = ' ';
	return(i);
}

