From: ant@mks.com (Anthony Howe)
Newsgroups: comp.editors
Subject: Ant's Editor August '92
Date: 17 Aug 92 13:49:03 GMT
Organization: Mortice Kern Systems, Waterloo, Ontario, CANADA

Anthony's Editor August 92
==========================

WHAT'S NEW

  +	Configuartion file support for help text and key bindings.
  +	Message line: tells you that the file has been modified or saved.
  +	Block, Cut, and Paste commands.
  +	Fixed control character display problem.
  -	Dropped flip-editor-style command.
  -	Dropped TERMCAP function-key support in favour of configuration files.

AE'91, in its obfuscated form, won "Best Utility" in the 1991 International
Obfuscated C Code Contest.  Since that contest, AE has been revised and
extended in order to try new ideas and provide better functionality, while
still retaining a simple interface and modualar design.  There are four 
files included in the August '92 distribution.

	ae.man		Manual reference and installation guide.
	ae.c		The source.
	ae.rc		Configuration file for AE
	ea.rc		Configuration file for EA (ansi cursor keys)

AE'92 merges two schools of thought by providing both VI style (modual)
and EMACS style (modeless) editing interfaces.  Users can configure the
command-key bindings and the help text for either style.

AE'92 is simple enough that anyone can modify it to add features.  The
source is in the Public Domain, so people are free to modify and use
as they see fit.  

e.g.
	o  Tutorials on the Buffer Gap Scheme and/or editor design.
	o  A basis for an editor that can be built into a project.
	o  An editor for novice users.

AE has been know to compile on a wide variety of machines and compilers
like BSD and System V Unix with GNU C, PC mahcines with WatCom C or Turbo C,
and ATARI ST machines with Sozobon C.  Any machine that provides at least 
K&R C and a CURSES library should have no trouble getting AE to compile.

See the INSTALLATION section of the manual on how to build AE.  Also
take note of the BUG section of the manual.

Anthony Howe
ant@mks.com

----ae.man---
0.  NAME

	ae	Ant's Editor  August '92


1.  SYNOPSIS

	ae <filename>	
	ea <filename>


2.  OPERANDS

filename	The name of a existing or new file to edit is required.


3.  DESCRIPTION

AE is a full screen, modual text editor, and EA is a full screen,
modelss text editor.  The source should be portable to any environment
that provides a K&R C compiler and a CURSES library.

Text files consists of lines of printable text or tab characters.  
A line can be of arbitary length and is delimited by either a 
newline or the end of file.  Carriage return is mapped to newline 
on input and ignored on output.  Tab stops are every eight columns.


4.  COMMANDS

Two default configuration files are supplied.  "ae.rc" is read when 
AE is invoked, and "ea.rc" is read when EA is used.


4.1  AE  MODUAL STYLE

h j k l		left, down, up, right cursor movement
H J K L		word left, page down, page up, word right
[ ]		beginning and end of line
t b		top and bottom of file
i ESC		enter insert mode, escape to leave
x		delete character under the cursor
X		delete character left of the cursor
?		toggle help on/off
B		toggle block on/off
C		cut block to scrap
P		paste scrap into buffer
F		write buffer to file
R		refresh the screen
Q		quit


4.2  EA  MODELESS STYLE

cursor keys	left, down, up, right cursor movement
^W ^E		word left,  word right
^N ^P		page down, page up
^A ^D		beginning and end of line
^T ^B		top and bottom of file
backspace	delete character left of the cursor
^X		delete character under the cursor
F1		toggle help on/off
F2		toggle block on/off
F3		cut block to scrap
F4		paste scrap into buffer
^F		write buffer to file
^R		refresh the screen
^C		quit


5.  CONFIGURATION

The user is able to configure the editor with the help text and keys
that the user likes.  It is possible to define a modual or modeless
key interface and support multi-character key sequences.

The configuration file layout is fairly simple.  All control words
begin on a line starting with a period (.).  Invalid keywords are
ignored.  The following keys words are used:

.help_text
	All the subsequent lines upto the next keyword are considered
	help text.  The terminating keyword is discarded.  There may
	be more than one .help_text given.

.insert_enter <string>
.insert_exit <string>
	Enter and exit insert mode.  

.delete_left <string>
.delete_right <string>
	Delete character to the left or right of the cursor.

.block <string>
.cut <string>
.paste <string>
	Block on/off toggle, cut block, and paste before.

.cursor_up <string>
.cursor_down <string>
.cursor_left <string>
.cursor_right <string>
	Cursor motion in four directions.  Typically the arrow keys.

.page_up <string>
.page_down <string>
        Previous or next screen full of text.

.word_left <string>
.word_right <string>
        Move to word left or right of the current cursor.

.line_left <string>
.line_right <string>
	Move to the beginning or end of the line.

.file_top <string>
.file_bottom <string>
	Move to the top and bottom of the file buffer.

.help <string>
	Toggle the help text and ruler line on and off.

.quit <string>
	Exit the editor.

.redraw <string>
	Force a screen redraw.

.file_save <string>
	Save the file buffer to the current filename.

.itself  <character>
	The following character represents itself.  This is really a
	redundant keyword since any key not defined by a keyword,
	automatically represents itself.

.stty_erase
.stty_kill
	Declare that the terminal's values for the erase and kill
	characters should be used in insert mode to backspace-erase,
	or discard and restart input.

The parameters <string> and <character> can be any text other than
whitspace (blank, tab, carriage-return, and newline).  It is possible
to specify control keys by prefixing the following characters with a
caret (^):

	@ a b c d e f g h i j k l m n o 
	p q r s t u v w x y z [ \ ] ^ _

Also numeric and literal escapes are possible.  A numeric escape
begins with a backslash followed by either an octal number with 
leading '0', a hex number with leading '0x', or a decimal number.
The value must be between 0 to 255.

A literal escape also begins with a backslash but is then followed
by a character that is not a digit.

eg.
	.insert_enter	i		<-- single character string
	.insert_exit	^[		<-- defines ASCII ESC
	.delete_right	\0x7f		<-- defines ASCII DEL
	.cursor_up	^[[A		<-- defines sequence ESC [ A

Note that the name of the editor determines whether or not the editor
behaves in a modual (AE) or modeless (EA) fashion.


6.  EXIT STATUS

0	Success termination.
1	General error.
2	Usage error.
3	Failed to initialize the screen.
4	Problem with the configuration file.


7.  INSTALLATION 

Requires K&R C and a CURSES library for the given target machine.

To build AE on System V equivalent systems, type

	cc -O -o ae ae.c -lcurses

To build AE on BSD equivalent systems, type

	cc -O -o ae ae.c -lcurses -ltermcap

To build AE on systems that have POSIX.1 or System V termios library,
add to the command line -DPOSIX=1, like

	cc -DPOSIX=1 -O -o ae ae.c -lcurses 

To use EA version of AE either link or copy the file to EA.

	cp ae ea

If the constants BUF, HUP, AE_CONFIG, or AE_CONFIG are not defined on
the compile command line then the defaults used are

	BUF       = 32767 
	HUP       = "ae.hup"
	AE_CONFIG = "ae.rc"
	EA_CONFIG = "ea.rc"

The BUF size should be set at compile time to 32767.  This value was
used because the Sozobon C compiler for the Atari ST has 16 bit ints
and a limit on the size of arrays & structures of 32k.  Also the
WatCom C compiler for the PC also has 16 bits ints.  On machines that
have 32 bit ints (most unix boxes), a larger value for BUF could be
used.

It is recommend that compact memory model be used on PC class
machines.  Small memory model may work too provided BUF is not too
large.

HUP should define the name of the fall-back write file.  POSIX should
be defined for systems that have POSIX.1 termios support (which is
based on System V termios).

Most EBCDIC machines use block mode terminals.  This is a problem
that has not been addressed and/or tested for.


8.  BUGS

This editor will display a file with long lines, but has trouble
scrolling the screen with long lines.  Paging up and down should
work correctly, however.


9.  REFERENCES

[Fin80]	Craig A. Finseth, "Theory and Practice of Text Editors or 
	A Cookbook For An EMACS", TM-165, MIT Lab. for Computer 
	Science

[KeP81]	Kernighan & Plauger, "Software Tools in Pascal", 
	Addison-Wesley, 81, chapter 6

[Mil86]	Eugene W. Myers & Webb Miller, "Row-replacement Algorithums
	for Screen Editors", TR 86-19, Dept. of Compter Science, 
	U. of Arizona

[MyM86]	Eugene W. Myers & Webb Miller, "A simple row-replacement 
	method", TR 86-28, Dept. of Compter Science, U. of Arizona

[Mil87]	Webb Miller, "A Software Tools Sampler", Prentice Hall, 87
	ISBN 0-13-822305-X, chapter 5

[net90]	"Editor 101/102" articles from comp.editors


10.  FILES

ae.man		AE August '92 manual
ae.c		AE August '92 source 
ae.rc		Configuration file for AE modual style
ea.rc		Configuration file for EA modeless style (ansi cursor keys)

----ae.c----
/*
 *	ae.c		
 *
 *	Anthony's Editor  August '92
 *
 *	Public Domain 1991, 1992 by Anthony Howe.  All rights released.
 */

#include <ctype.h>
#include <curses.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#undef _
#ifdef __STDC__
#define _(x)	x
#else
#define _(x)	()
#endif

#ifndef BUF
#define BUF		32767
#endif /* BUF */

#ifndef HUP
#define HUP		"ae.hup"
#endif /* HUP */

#ifndef AE_CONFIG
#define AE_CONFIG	"ae.rc"
#endif /* AE_CONFIG */

#ifndef EA_CONFIG
#define EA_CONFIG	"ea.rc"
#endif /* EA_CONFIG */

/* Exit status. */
#define EXIT_OK		0
#define EXIT_ERROR	1
#define EXIT_USAGE	2
#define EXIT_INITSCR	3
#define EXIT_CONFIG	4

/* Screen partitioning. */
#define MSGLINE		0
#define HELPLINE	1
#undef  TEXTLINE	

#define NOMARK		-1

typedef struct keytable_t {
	int key;
	void (*func) _((void));
} keytable_t;

int done, modified, modeless;
int point, page, epage;
int row, col;
int input;
int marker = NOMARK;
int textline = HELPLINE;
size_t nscrap;
char buf[BUF];
char *ebuf;
char *gap = buf;
char *egap;
char *filename;
char *scrap;

/*
 *	The following assertions must be maintained.
 *
 *	o  buf <= gap <= egap <= ebuf
 *		If gap == egap then the buffer is full.
 *
 *	o  cursor = ptr(point) and cursor < gap or egap <= cursor 
 *
 *	o  page <= point < epage
 *
 *	o  0 <= point <= pos(ebuf) <= BUF
 *
 *
 *	Memory representation of the file:
 *
 *		low	buf  -->+----------+
 *				|  front   |
 *				| of file  |
 *			gap  -->+----------+<-- character not in file 
 *				|   hole   |
 *			egap -->+----------+<-- character in file
 *				|   back   |
 *				| of file  |
 *		high	ebuf -->+----------+<-- character not in file 
 *
 *
 *	Point & Gap
 *
 *	The Point is the current cursor position while the Gap is the 
 *	position where the last edit operation took place. The Gap is 
 *	ment to be the cursor but to avoid shuffling characters while 
 *	the cursor moves it is easier to just move a pointer and when 
 *	something serious has to be done then you move the Gap to the 
 *	Point. 
 *
 *
 *	Use of stdio for portability.
 *
 *	Stdio will handle the necessary conversions of text files to 
 *	and from a machine specific format.  Things like fixed length 
 *	records; CRLF mapping into <newline> (\n) and back again; 
 *	null padding; control-Z end-of-file marks; and other assorted 
 *	bizare issues that appear on many unusual machines.
 *
 *	AE is meant to be simple in both code and usage.  With that
 *	in mind certain assumptions are made.
 *
 *	Reading:  If a file cannot be opened, assume that it is a
 *	new file.  If an error occurs, fall back to a safe state and
 *	assume an empty file.  fread() is typed size_t which is an
 *	unsigned number.  Zero (0) would indicate a read error or an
 *	empty file.  A return value less than BUF is alright, since
 *	we asked for the maximum allowed.
 *
 *	Writing:  If the file cannot be opened or a write error occurs,
 *	then we scramble and save the user's changes in a file called 
 *	ae.hup.  If ae.hup fails to open or a write error occurs, then 
 *	we assume that shit happens.
 *
 */

int adjust _((int, int));
int nextline _((int));
int pos _((char *));
int prevline _((int));
int save _((char *));
char *ptr _((int));
void fatal _((int));
void ruler _((int));

void backsp _((void));
void bottom _((void));
void delete _((void));
void display _((void));
void down _((void));
void file _((void));
void help _((void));
void insert _((void));
void insert_mode _((void));
void left _((void));
void lnbegin _((void));
void lnend _((void));
void movegap _((void));
void pgdown _((void));
void pgup _((void));
void redraw _((void));
void right _((void));
void quit _((void));
void flip _((void));
void top _((void));
void up _((void));
void wleft _((void));
void wright _((void));
void block _((void));
void cut _((void));
void paste _((void));

/*
 *	Configurable help and key support.
 *
 *	The user is able to configure the editor with the help text 
 *	and keys that the user likes.  It is possible to define a 
 *	modual or modeless key interface and support multi-character 
 *	key sequences.
 *
 *	The configuration file layout is fairly simple.  All control 
 *	words begin on a line starting with a period (.).  Invalid 
 *	keywords are ignored.  The following keys words are used:
 *
 *	.help_text
 *		All the subsequent lines upto the next keyword are
 *		considered help text.  The terminating keyword is
 *		discarded.  There may be more than one .help_text
 *		given.
 *
 *	.insert_enter <string>
 *	.insert_exit <string>
 *		Enter and exit insert mode.  Use of .insert_enter will
 *		denote a modual user interface.
 *
 *	.delete_left <string>
 *	.delete_right <string>
 *		Delete character to the left or right of the cursor.
 *
 *	.block <string>
 *	.cut <string>
 *	.paste <string>
 *		Block on/off toggle, cut block, and paste before.
 *
 *	.cursor_up <string>
 *	.cursor_down <string>
 *	.cursor_left <string>
 *	.cursor_right <string>
 *		Cursor motion in four directions.  Typically the 
 *		arrow keys.
 *		
 *	.page_up <string>
 *	.page_down <string>
 *		Previous or next screen full of text.
 *		
 *	.word_left <string>
 *	.word_right <string>
 *		Move to word left or right of the current cursor.
 *
 *	.line_left <string>
 *	.line_right <string>
 *		Move to the beginning or end of the line.
 *
 *	.file_top <string>
 *	.file_bottom <string>
 *		Move to the top and bottom of the file buffer.
 *
 *	.help <string>
 *		Toggle the help text and ruler line on and off.
 *
 *	.quit <string>
 *		Exit the editor.
 *
 *	.redraw <string>
 *		Force a screen redraw.
 *
 *	.file_save <string>
 *		Save the file buffer to the current filename.
 *
 *	.itself  <character>
 *		The following character represents itself.  This is
 *		really a redundant keyword since any key not defined
 *		by a keyword, automatically represents itself.
 *
 *	.stty_erase
 *	.stty_kill
 *		Declare that the terminal's values for the erase and
 *		kill characters should be used in insert mode to 
 *		backspace-erase, or discard and restart input.
 *
 *	The parameters <string> and <character> can be any text
 *	other than whitspace (blank, tab, carriage-return, and newline).
 *	It is possible to specify control keys by prefixing the following
 *	characters with a caret (^):
 *
 *		@ a b c d e f g h i j k l m n o 
 *		p q r s t u v w x y z [ \ ] ^ _
 *	
 *	Also numeric and literal escapes are possible.  A numeric escape
 *	begins with a backslash followed by either an octal number with 
 *	leading '0', a hex number with leading '0x', or a decimal number.
 *	The value must be between 0 to 255.
 *
 *	A literal escape also begins with a backslash but is then followed
 *	by a character that is not a digit.
 *	
 *	eg.
 *		.insert_enter	i		<-- single character string
 *		.insert_exit	^[		<-- defines ASCII ESC
 *		.delete_right	\0x7f		<-- defines ASCII DEL
 *		.cursor_up	^[[A		<-- defines sequence ESC [ A
 *
 *	Note that the name of the editor determines whether or not
 *	the editor behaves in a modual or modeless fashion.
 */

#define K_INSERT_ENTER	(-101)
#define K_INSERT_EXIT	(-102)
#define K_DELETE_LEFT	(-103)
#define K_DELETE_RIGHT	(-104)

#define K_BLOCK		(-105)
#define K_CUT		(-106)
#define K_PASTE		(-107)

#define K_CURSOR_UP	(-200)
#define K_CURSOR_DOWN	(-201)
#define K_CURSOR_LEFT	(-202)
#define K_CURSOR_RIGHT	(-203)
#define K_PAGE_UP	(-204)
#define K_PAGE_DOWN	(-205)
#define K_WORD_LEFT	(-206)
#define K_WORD_RIGHT	(-207)
#define K_LINE_LEFT	(-208)
#define K_LINE_RIGHT	(-209)
#define K_FILE_TOP	(-210)
#define K_FILE_BOTTOM	(-211)

#define K_HELP_TEXT	(-300)
#define K_HELP		(-301)
#define K_QUIT		(-302)
#define K_REDRAW	(-303)
#define K_FILE_SAVE	(-304)

#define K_ITSELF	(-400)
#define K_STTY_ERASE	(-401)
#define K_STTY_KILL	(-402)

typedef struct keymap_t {
	short code;
	char *entry;
} keymap_t;

keymap_t *key_map;
keymap_t key_names[] = {
	{ K_INSERT_ENTER, ".insert_enter" },
	{ K_INSERT_EXIT, ".insert_exit" },
	{ K_DELETE_LEFT, ".delete_left" },
	{ K_DELETE_RIGHT, ".delete_right" },
	{ K_BLOCK, ".block" },
	{ K_CUT, ".cut" },
	{ K_PASTE, ".paste" },
	{ K_CURSOR_UP, ".cursor_up" },
	{ K_CURSOR_DOWN, ".cursor_down" },
	{ K_CURSOR_LEFT, ".cursor_left" },
	{ K_CURSOR_RIGHT, ".cursor_right" },
	{ K_PAGE_UP, ".page_up" },
	{ K_PAGE_DOWN, ".page_down" },
	{ K_WORD_LEFT, ".word_left" },
	{ K_WORD_RIGHT, ".word_right" },
	{ K_LINE_LEFT, ".line_left" },
	{ K_LINE_RIGHT, ".line_right" },
	{ K_FILE_TOP, ".file_top" },
	{ K_FILE_BOTTOM, ".file_bottom" },
	{ K_HELP_TEXT, ".help_text" },
	{ K_HELP, ".help" },
	{ K_QUIT, ".quit" },
	{ K_REDRAW, ".redraw" },
	{ K_FILE_SAVE, ".file_save" },
	{ K_ITSELF, ".itself" },
	{ K_STTY_ERASE, ".stty_erase" },
	{ K_STTY_KILL, ".stty_kill" },
	{ 0, NULL }
};

/* initkey() and encodekey() error values. */
#define INITKEY_OK	0
#define INITKEY_OPEN	1
#define INITKEY_MEMORY	2
#define INITKEY_SYNTAX	3
#define INITKEY_INVALID	4
#define INITKEY_RANGE	5

static char blank[] = " \t\r\n";

char *strlwr _((char *));
char *strdup _((char *));
int initkey _((char *, keymap_t **));
int encodekey _((char *, char **));
char *gethelp _((FILE *));
void finikey _((keymap_t *));
int getkey _((keymap_t *));

/*
 *	Convert a string to lower case.  Return the string pointer.
 */
char *
strlwr(str)
char *str;
{
	register char *s;
	for (s = str; *s != '\0'; ++s)
		*s = tolower(*s);
	return (str);
}

/*
 *	Make a duplicate of a string.  Return a pointer to an allocated
 *	copy of the string, or NULL if malloc() failed.
 */
char *
strdup(str)
char *str;
{
	char *new;
	if ((new = (char*) malloc(strlen(str)+1)) != NULL)
		(void) strcpy(new, str);
	return (new);
}	

/*
 *	Return an INITKEY_xxx error status.  Pass-back a pointer to
 *	an encoded string, or NULL for an error in 'strp'.  
 */
int
encodekey(str, strp)
char *str, **strp;
{
	long number;
	char *ptr, *p, *ctrl;
	static char control[] = "@abcdefghijklmnopqrstuvwxyz[\\]^_";
	*strp = NULL;
	if ((ptr = strdup(str)) == NULL)
		return (INITKEY_MEMORY);
	/* Duplicating the string is a cheap way of allocating
	 * enough memory to store the encoded string. The 
	 * process of encoding always generates a string less
	 * than or equal to the original string length.
	 */
	for (p = ptr; *str != '\0'; ++p) {
		switch (*str) {
		case '^':
			/* Non-ASCII dependant control key mapping. */
			++str;
			*str = tolower(*str);
			if ((ctrl = strchr(control, *str)) == NULL) {
				free(ptr);
				return (INITKEY_INVALID);
			}
			*p = (char) (ctrl - control);	
			++str;
			break;
		case '\\':
			/* Escapes. */
			++str;
			if (isdigit(*str)) {
				/* Numeric escapes allow for
				 *  octal	\0nnn
				 *  hex		\0xnn
				 *  decimal	\nnn 
				 */
				number = strtol(str, &str, 0);
				if (UCHAR_MAX <= number) {
					free(ptr);
					return (INITKEY_RANGE);
				}
				*p = (char) number;
				break;
			}
			/* Literal escapes. */
		default:
			/* Character. */
			*p = *str++;
		}
	}
	*p = '\0';
	*strp = ptr;
	return (INITKEY_OK);
}

/*
 *	Return a pointer to allocated memory containing help text, 
 *	or NULL if malloc() failed.
 */
char *
gethelp(fp)
FILE *fp;
{
	size_t helplen, buflen;
	char *help, *ptr, buf[BUFSIZ];
	help = (char*) malloc(helplen = 1);
	if (help == NULL)
		return (NULL);
	help[0] = '\0';
	while (fgets(buf, BUFSIZ, fp) != NULL && buf[0] != '.') {
		buflen = strlen(buf);
		ptr = (char*) realloc(help, helplen + buflen);	
		if (ptr == NULL) 
			break;
		help = ptr;
		(void) strcpy(help + helplen - 1, buf);
		helplen += buflen;
	}
	return (help);
}

/*
 *	Read a configuration file from either the current directory or
 *	the user's home directory.  Return an error status.  Pass-back
 *	either a pointer to a key mapping table, or NULL if an error
 *	occured.
 */
int
initkey(fn, keys)
char *fn;
keymap_t **keys;
{
	FILE *fp;
	int error, klen;
	keymap_t *kptr, *kn, *kp;
	char *word, buffer[BUFSIZ];
	kptr = NULL;
	error = INITKEY_OPEN;
	if ((fp = fopen(fn, "r")) == NULL) {
		if ((word = getenv("HOME")) == NULL)
			goto error_1;
		(void) sprintf(buffer, "%s/%s", word, fn);
		if ((fp = fopen(buffer, "r")) == NULL) {
			(void) sprintf(buffer, "%s\\%s", word, fn);
			if ((fp = fopen(buffer, "r")) == NULL)
				goto error_1;
		}
	}
	if ((kptr = (keymap_t*) malloc(sizeof (keymap_t))) == NULL) {
		error = INITKEY_MEMORY;
		goto error_2;
	}
	klen = 1;
	while (fgets(buffer, BUFSIZ, fp) != NULL) {
		if (buffer[0] != '.' || (word = strtok(buffer, blank)) == NULL)
			continue;
		(void) strlwr(word);
		for (kn = key_names; kn->entry != NULL; ++kn) {
			if (strcmp(word, kn->entry) == 0) {
				error = INITKEY_MEMORY;
				kp = (keymap_t*) realloc(
					kptr, ++klen * sizeof (keymap_t)
				);
				if (kp == NULL)
					goto error_2;
				kptr = kp;
				switch (kn->code) {
				case K_STTY_ERASE:
					buffer[0] = erasechar();
					buffer[1] = '\0';
					word = strdup(buffer);
					break;
				case K_STTY_KILL:
					buffer[0] = killchar();
					buffer[1] = '\0';
					word = strdup(buffer);
					break;
				case K_HELP_TEXT:
					word = gethelp(fp);
					break;
				default:
					word = strtok(NULL, blank);
					if (word == NULL) {
						error = INITKEY_SYNTAX;
						goto error_2;
					}
					error = encodekey(word, &word);
				}
				if (word == NULL)
					goto error_2;
				kptr[klen-2].entry = word;
				if (kn->code == K_ITSELF)
					kptr[klen-2].code = *word;
				else
					kptr[klen-2].code = kn->code;
				break;
			}
		}
	}
	error = INITKEY_OK;
error_2:
	(void) fclose(fp);
	/* Assert that last keymap_t in kptr[] is zero. */
	kptr[klen-1].code = 0;
	kptr[klen-1].entry = NULL;
	if (error != INITKEY_OK) {
		finikey(kptr);
		kptr = NULL;
	}
error_1:
	*keys = kptr;
	return (error);
}

void
finikey(keys)
keymap_t *keys;
{
	keymap_t *kp;
	if (keys != NULL) {
		for (kp = keys; kp->entry != NULL; ++kp)
			free(kp->entry);
		free(keys);
	}
}

int
getkey(keys)
keymap_t *keys;
{
	keymap_t *k;
	int submatch;
	static char buffer[128];
	static char *record = buffer;

	/* If recorded bytes remain, return next recorded byte. */
	if (*record != '\0')
		return (*record++);
	/* Reset record buffer. */
	record = buffer;
	do {
		/* Read and record one byte. */
		*record++ = getch();
		*record = '\0';

		/* If recorded bytes match any multi-byte sequence... */
		for (k = keys, submatch = 0; k->entry != NULL; ++k) {
			char *p, *q; 
			for (p = buffer, q = k->entry; *p == *q; ++p, ++q) {
				if (*p == '\0') {
					/* Return extended key code. */
					return (k->code);
				}
			}
			if (*p == '\0') {
				/* Recorded bytes match anchored substring. */
				submatch = 1;
			}
		}
		/* If recorded bytes matched an anchored substring, loop. */
	} while (submatch);
	/* Return first recorded byte. */
	record = buffer;
	return (*record++);
}

/* ASCII Control Codes */
#undef CTRL
#define CTRL(x)		((x) & 0x1f)

keytable_t table[] = {
	{ K_CURSOR_LEFT, left },
	{ K_CURSOR_RIGHT, right },
	{ K_CURSOR_DOWN, down },
	{ K_CURSOR_UP, up },
	{ K_WORD_LEFT, wleft },
	{ K_WORD_RIGHT, wright },
	{ K_PAGE_UP, pgup },
	{ K_PAGE_DOWN, pgdown },
	{ K_LINE_LEFT, lnbegin },
	{ K_LINE_RIGHT, lnend },
	{ K_FILE_TOP, top },
	{ K_FILE_BOTTOM, bottom },
	{ K_DELETE_LEFT, backsp },
	{ K_DELETE_RIGHT, delete },
	{ K_INSERT_ENTER, insert_mode },
	{ K_BLOCK, block },
	{ K_CUT, cut },
	{ K_PASTE, paste },
	{ K_FILE_SAVE, file },
	{ K_REDRAW, redraw },
	{ K_HELP, help },
	{ K_QUIT, quit },
	{ K_ITSELF, NULL },
	{ 0, NULL }
};

#ifdef POSIX

#include <termios.h>

/*
 *	Set the desired input mode.
 *
 *	FALSE enables immediate character processing (disable line processing
 *	and signals for INTR, QUIT, and SUSP).  TRUE enables line processing 
 *	and signals (disables immediate character processing).  In either 
 *	case flow control (XON/XOFF) is still active.  
 *
 *	If the termios function calls fail, then fall back on using 
 *	CURSES' cbreak()/nocbreak() functions; however signals will be
 *	still be in effect.
 */
void
lineinput(bf)
int bf;
{
	int error;
	struct termios term;
	error = tcgetattr(fileno(stdin), &term) < 0;
	if (!error) {
		if (bf)
			term.c_lflag |= ISIG | ICANON;
		else
			term.c_lflag &= ~(ISIG | ICANON);
		error = tcsetattr(fileno(stdin), TCSANOW, &term) < 0;
	}
	/* Fall back on CURSES functions that do almost what we need if
	 * either tcgetattr() or tcsetattr() fail.
	 */
	if (error) {
		if (bf)
			nocbreak();
		else
			cbreak();
	}
}

#else /* not POSIX */

#define lineinput(bf)	(bf ? nocbreak() : cbreak());

#endif /* POSIX */


char *
ptr(offset)
int offset;
{
	if (offset < 0)
		return (buf);
	return (buf+offset + (buf+offset < gap ? 0 : egap-gap));
}

int
pos(pointer)
char *pointer;
{
	return (pointer-buf - (pointer < egap ? 0 : egap-gap)); 
}

void
top()
{
	point = 0;
}

void
bottom()
{
	epage = point = pos(ebuf);
}

void
quit()
{
	done = 1;
}

void
redraw()
{
	int col;
	keymap_t *kp;
	clear();
	if (textline != HELPLINE) {
		move(HELPLINE, 0);
		/* Display all the help text entries. */
		for (kp = key_map; kp->entry != NULL; ++kp)
			if (kp->code == K_HELP_TEXT)
				addstr(kp->entry);
		ruler(COLS);
		getyx(stdscr, textline, col);
	}
	display();
}

void
movegap()
{
	char *p = ptr(point);
	while (p < gap)
		*--egap = *--gap;
	while (egap < p)
		*gap++ = *egap++;
	point = pos(egap);
}

int
prevline(offset)
int offset;
{
	char *p;
	while (buf < (p = ptr(--offset)) && *p != '\n')
		;
	return (buf < p ? ++offset : 0);
}

int
nextline(offset)
int offset;
{
	char *p;
	while ((p = ptr(offset++)) < ebuf && *p != '\n')	
		;
	return (p < ebuf ? offset : pos(ebuf));
}

int
adjust(offset, column)
int offset, column;
{
	char *p;
	int i = 0;
	while ((p = ptr(offset)) < ebuf && *p != '\n' && i < column) {
		i += *p == '\t' ? 8-(i&7) : 1;
		++offset;
	}
	return (offset);
}

void
left()
{
	if (0 < point)
		--point;
} 

void
right()
{
	if (point < pos(ebuf))
		++point;
}

void
up()
{
	point = adjust(prevline(prevline(point)-1), col);
}

void
down()
{
	point = adjust(nextline(point), col);
}

void
lnbegin()
{
	point = prevline(point);
}

void
lnend()
{
	point = nextline(point);
	left();
}

void
wleft()
{
	char *p;
	while (!isspace(*(p = ptr(point))) && buf < p)
		--point;
	while (isspace(*(p = ptr(point))) && buf < p)
		--point;
}

void
pgdown()
{
	page = point = prevline(epage-1);
	while (textline < row--)
		down();
	epage = pos(ebuf);
}

void
pgup()
{
	int i = LINES;
	while (textline < --i) {
		page = prevline(page-1); 
		up();
	}
}

void
wright()
{
	char *p;
	while (!isspace(*(p = ptr(point))) && p < ebuf)
		++point;
	while (isspace(*(p = ptr(point))) && p < ebuf)
		++point;
}

void
insert()
{
	movegap();
	if (gap < egap) {
		*gap++ = input == '\r' ? '\n' : input;
		modified = TRUE;
	}
	point = pos(egap);
}

keymap_t key_exit[] = {
	{ K_INSERT_EXIT, NULL },
	{ K_STTY_ERASE, NULL },
	{ 0, NULL }
};

void
insert_mode()
{
	int ch;
	movegap();
	while ((ch = getkey(key_exit)) != K_INSERT_EXIT) {
		if (ch == K_STTY_ERASE) {
			if (buf < gap) {
				--gap;
				modified = TRUE;
			}
		} else if (gap < egap) {
			*gap++ = ch == '\r' ? '\n' : ch;
			modified = TRUE;
		}
		point = pos(egap);
		display();
	}
}

void
backsp()
{
	movegap();
	if (buf < gap) {
		--gap;
		modified = TRUE;
	}
	point = pos(egap);
}

void
delete()
{
	movegap();
	if (egap < ebuf) {
		point = pos(++egap);
		modified = TRUE;
	}
}

void
file()
{
	if (!save(filename))
		save(HUP);
}

int
save(fn)
char *fn;
{
	FILE *fp;
	size_t length;
	int opoint, ok;
	fp = fopen(fn, "w");
	if ((ok = (fp != NULL))) {
		opoint = point;
		top();
		movegap();
		length = (size_t) (ebuf-egap);
		ok = fwrite(egap, sizeof (char), length, fp) == length;
		(void) fclose(fp);
		point = opoint;
		if (ok)
			modified = FALSE;
	}
	return (ok);
}

void
help()
{
	textline = textline == HELPLINE ? -1 : HELPLINE;
	redraw();
}

void
block()
{
	marker = marker == NOMARK ? point : NOMARK;
}

void
cut()
{
	char *p;
	int opoint = point;
	if (marker == NOMARK || point == marker)
		return;
	if (scrap != NULL) {
		free(scrap);
		scrap = NULL;
	}
	if (point < marker) {
		nscrap = marker - point;
		p = ptr(point);
	} else {
		nscrap = point - marker;
		p = ptr(marker);
		point = marker;
	}
	movegap();
	if ((scrap = malloc(nscrap)) == NULL) {
		point = opoint;
		beep();
	} else {
		(void) memcpy(scrap, p, nscrap);
		egap += nscrap;
		block();
		point = pos(egap);
		modified = TRUE;
	}
}

void
paste()
{
	if (nscrap < egap - gap) {
		movegap();
		memcpy(gap, scrap, nscrap);
		gap += nscrap;
		point = pos(egap);
		modified = TRUE;
	} else {
		beep();
	}
}

void
ruler(ncols)
int ncols;
{
	int r, c, col;
	char buffer[5];
	getyx(stdscr, r, c);
	for (col = 1; col <= ncols; ++col) {
		switch (col % 10) {
		case 0:
			sprintf(buffer, "%d", col);
			mvprintw(r, col - strlen(buffer), buffer);
			break;
		case 5:
			addch('5');
			break;
		default:
			addch('.');
		}
	}
}

void
display()
{
	char *p;
	int i, j;
	standout();
	mvprintw(MSGLINE, 0, "File \"%s\" ", filename);
	if (modified) {
		addstr("modified.");
	} else {
		i = point;
		j = epage;
		bottom(); 	
		printw("%d bytes.", point);
		point = i;
		epage = j;
	}
	standend();
	clrtoeol();
	if (point < page)
		page = prevline(point);
	if (epage <= point) {
		page = nextline(point); 
		i = (page == pos(ebuf) ? LINES-2 : LINES) - textline; 
		while (0 < i--)
			page = prevline(page-1);
	}
	move(textline, 0);
	i = textline;
	j = 0;
	epage = page;
	while (1) {
		if (point == epage) {
			row = i;
			col = j;
		}
		p = ptr(epage);
		if (LINES <= i || ebuf <= p)
			break;
		if (*p != '\r') {
			if (marker != NOMARK) {
				if ((marker <= epage && epage < point)
				|| (point <= epage && epage < marker))
					standout();
				else
					standend();
			}
			if (isprint(*p) || *p == '\t' || *p == '\n') {
				j += *p == '\t' ? 8-(j&7) : 1;
				addch(*p);
			} else {
				char *ctrl = unctrl(*p);
				j += (int) strlen(ctrl);
				addstr(ctrl);
			}
		}
		if (*p == '\n' || COLS <= j) {
			j -= COLS;
			if (j < 0)
				j = 0;
			++i;
		}
		++epage;
	}
	standend();
	clrtobot();
	if (++i < LINES)
		mvaddstr(i, 0, "<< EOF >>");
	move(row, col);
	refresh();
}

void
fatal(code)
int code;
{
	static char *exit_msg[] = {
		"Program terminated successfully.\n",
		"Unspecified error.\n",
		"usage:	ae <file>\n\tea <file>\n",
		"Program failed to initialize the screen.\n",
		"Problem with configuration file.\n",
	};
	if (curscr != NULL) {
		endwin();
		putchar('\n');
	}
	fputs(exit_msg[code], stderr);
	exit(code);
}

int
main(argc, argv)
int argc;
char **argv;
{
	FILE *fp;
	char *p = *argv;
	int i = (int) strlen(p);
	egap = ebuf = buf + BUF;
	if (initscr() == NULL)
		fatal(EXIT_INITSCR);
	if (argc < 2)
		fatal(EXIT_USAGE);
	/* Find basename. */
	while (0 <= i && p[i] != '\\' && p[i] != '/')
		--i;
	p += i+1;
	if (strncmp(p, "ae", 2) == 0 || strncmp(p, "AE", 2) == 0) {
		keymap_t *kp;
		if (initkey(AE_CONFIG, &key_map) != INITKEY_OK)
			fatal(EXIT_CONFIG);
		modeless = FALSE;
		/* Define insert mode keys from the master table. */
		for (kp = key_map; kp->entry != NULL; ++kp) {
			switch (kp->code) {
			case K_INSERT_EXIT:
				key_exit[0].entry = kp->entry;
				break;
			case K_STTY_ERASE:
				key_exit[1].entry = kp->entry;
				break;
			}
		}
	} else if (strncmp(p, "ea", 2) == 0 || strncmp(p, "EA", 2) == 0) {
		if (initkey(EA_CONFIG, &key_map) != INITKEY_OK)
			fatal(EXIT_CONFIG);
		modeless = TRUE;
	} else {
		fatal(EXIT_USAGE);
	}
	noecho();
	lineinput(FALSE);
	idlok(stdscr, TRUE);
	fp = fopen(filename = *++argv, "r");
	if (fp != NULL) {
		gap += fread(buf, sizeof (char), (size_t) BUF, fp);
		fclose(fp);
	}
	top();
	help();
	while (!done) {
		display();
		i = 0; 
		input = getkey(key_map); 
		while (table[i].key != 0 && input != table[i].key)
			++i;
		if (table[i].func != NULL) 
			(*table[i].func)();
		else if (modeless)
			insert();
	}
	if (scrap != NULL)
		free(scrap);
	finikey(key_map);
	endwin();
	putchar('\n');
	return (EXIT_OK);
}
----ae.rc----
.help_text
Left, right, up, down	h  j  k  l	Beginning and end of line	[  ]
Word left and right	H  L		Top and bottom of file		t  b
Page up and down	J  K		Delete left and right		X  x
Insert on and off	i  ESC		Help on and off			?
Save file		F		Block on and off		B
Redraw			R		Cut block to scrap		C
Quit			Q		Paste scrap into buffer		P
.end
.stty_erase
.cursor_up	k
.cursor_down	j
.cursor_left	h
.cursor_right	l
.page_up	K
.page_down	J
.word_left	H
.word_right	L
.line_left	[
.line_right	]
.file_top	t
.file_bottom	b
.delete_left	X
.delete_right	x
.insert_enter	i
.insert_exit	^[
.help		?
.block		B
.cut		C
.paste		P
.file_save	F
.redraw		R
.quit		Q
----ea.rc----
.help_text
Left, right, up, down	arrow keys	Beginning and end of line	^A ^D
Word left and right	^W ^E		Top and bottom of file		^T ^B
Page up and down	^P ^N		Delete left and right	 backspace ^X
Insert			typed keys	Help on and off			F1
Save file		^F		Block on and off		F2
Redraw			^R		Cut block to scrap		F3
Quit			^C		Paste scrap into buffer		F4
.end
.cursor_up	^[[A
.cursor_down	^[[B
.cursor_left	^[[D
.cursor_right	^[[C
.page_up	^P
.page_down	^N
.word_left	^W
.word_right	^E
.line_left	^A
.line_right	^D
.file_top	^T
.file_bottom	^B
.delete_left	^H
.delete_right	^X
.help		^[f1
.block		^[f2
.cut		^[f3
.paste		^[f4
.file_save	^F
.redraw		^R
.quit		^C
-- 
ant@mks.com                                                   Anthony C Howe 
Mortice Kern Systems Inc. 35 King St. N., Waterloo, Ontario, Canada, N2J 6W9
"Nice legs.  For a human that is." - Worf (Q-pid)