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

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

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

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

#define HELP_LINE	7

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

int done;
int row, col;
int point, page, epage;
int input;
int helpline;
char buf[BUF];
char *ebuf;
char *gap = buf;
char *egap;
char *filename;
keytable_t *table;

/*
 *	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 can not 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 can not 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 nextline();
int pos();
int prevline();
int save();
char *ptr();

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

#if TERMCAP
/*
 *	Function Key Support for BSD CURSES.
 *
 *	BSD CURSES does not support function keys as nicely as System V 
 *	or XPG CURSES.  So I've provided some functions that can decode
 *	multi-byte function keys that are commonly supported by BSD's
 *	termcap file.
 *
 *	The KEY_xxxx macro constants are not defined in the same manner
 *	as System V or XPG.  Also the key_table[] can be customised to
 *	support additional key sequences that might not be supported by
 *	termcap.  Create a new key code and provide the key sequence 
 *	string; initkey() will not alter the provided sequence provided
 *	it doesn't match a termcap capability name (see KEY_BACKSPACE).
 */

#include <sys/types.h>

int initkey();
int getkey();

#define KEY_DOWN        (-11)
#define KEY_UP          (-12)
#define KEY_LEFT        (-13)
#define KEY_RIGHT       (-14)
#define KEY_BACKSPACE   (-15)
#define KEY_DC		(-16)
#define KEY_F0          (-20)
#define KEY_F(n)        (KEY_F0-(n))

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

char key_buffer[1024];

key_entry_t key_table[] = {
	{ KEY_DOWN, "kd" },
	{ KEY_UP, "ku" },
	{ KEY_LEFT, "kl" },
	{ KEY_RIGHT, "kr" },
	{ KEY_BACKSPACE, "\b" },
	{ KEY_DC, "\177" },
	{ KEY_F(0), "k0" },
	{ KEY_F(1), "k1" },
	{ KEY_F(2), "k2" },
	{ KEY_F(3), "k3" },
	{ KEY_F(4), "k4" },
	{ KEY_F(5), "k5" },
	{ KEY_F(6), "k6" },
	{ KEY_F(7), "k7" },
	{ KEY_F(8), "k8" },
	{ KEY_F(9), "k9" },
	{ 0, NULL }
};

int
initkey()
{
	key_entry_t *k;
	char *ptr, *kbuf, *tname;
	static char buffer[1024];
	
	if ((tname = (char*) getenv("TERM")) == NULL 
	|| tgetent(buffer, tname) != 1)
		return (0);
	for (kbuf = key_buffer, k = key_table; k->entry != NULL; ++k) {
		ptr = (char*) tgetstr(k->entry, &kbuf);
		if (ptr != NULL)
			k->entry = ptr;
	}
	return (1);
}

int
getkey()
{
	key_entry_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 = key_table, 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++);
}

#else /* not TERMCAP */

#define initkey()	keypad(stdscr,1)
#define getkey()	getch()

#endif /* TERMCAP */

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

char help_ea[] = "\
Left, right, up, down\tarrow keys\tBeginning and end of line\t^A ^D\n\
Word left and right\t^W ^E\t\tTop and bottom of file\t\t^T ^B\n\
Page up and down\t^P ^N\t\tDelete left and right\tbackspace DEL\n\
Insert\t\t\ttyped keys\tHelp on and off\t\t\tF1\n\
Save file\t\t^F\t\tRedraw\t\t\t\t^R\n\
Quit\t\t\t^C\t\tFlip to VI-style\t\t^Z\n\
....5...10....5...20....5...30....5...40....5...50....5...60....5...70....5...80";

keytable_t modeless[] = {
	{ KEY_LEFT, left },
	{ KEY_RIGHT, right },
	{ KEY_DOWN, down },
	{ KEY_UP, up },
	{ CTRL('w'), wleft },
	{ CTRL('e'), wright },
	{ CTRL('n'), pgdown },
	{ CTRL('p'), pgup },
	{ CTRL('a'), lnbegin },
	{ CTRL('d'), lnend },
	{ CTRL('t'), top },
	{ CTRL('b'), bottom },
	{ KEY_BACKSPACE, backsp },
	{ '\b', backsp },
	{ KEY_DC, delete },
	{ DEL, delete },
	{ CTRL('f'), file },
	{ CTRL('r'), redraw },
	{ CTRL('c'), quit },
	{ CTRL('z'), flip },
	{ KEY_F(1), help },
	{ 0, insert }
};

char help_ae[] = "\
Left, right, up, down\th  j  k  l    \tBeginning and end of line\t[  ]\n\
Word left and right\tH  L\t\tTop and bottom of file\t\tt  b\n\
Page up and down\tJ  K\t\tDelete left and right\t\tX  x\n\
Insert on and off\ti  ^L\t\tHelp on and off\t\t\t? \n\
Save file\t\tF\t\tRedraw\t\t\t\tR\n\
Quit\t\t\tQ\t\tFlip to EMACS-style\t\tZ\n\
....5...10....5...20....5...30....5...40....5...50....5...60....5...70....5...80";

keytable_t modual[] = {
	{ 'h', left },
	{ 'j', down },
	{ 'k', up },
	{ 'l', right },
	{ 'H', wleft },
	{ 'J', pgdown },
	{ 'K', pgup },
	{ 'L', wright },
	{ '[', lnbegin },
	{ ']', lnend },
	{ 't', top },
	{ 'b', bottom },
	{ 'i', insert_mode },
	{ 'x', delete },
	{ 'X', backsp },
	{ 'F', file },
	{ 'R', redraw },
	{ 'Q', quit },
	{ 'Z', flip },
	{ '?', help },
	{ 0, movegap }
};

#ifdef POSIX

#include <termios.h>

/*
 *	Set the desired input mode.
 *
 *	FALSE enables immediate character processing (disable signals 
 *	and line processing.)  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' raw()/noraw() functions; however flow control will be 
 *	affected.
 */
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)
			noraw();
		else
			raw();
	}
}

#else /* not POSIX */

#define lineinput(bf)	(bf ? noraw() : raw());

#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()
{
	clear();
	if (helpline == HELP_LINE)
		mvaddstr(0, 0, table == modual ? help_ae : help_ea);
	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 (0 < row--)
		down();
	epage = pos(ebuf);
}

void
pgup()
{
	int i = LINES;
	while (0 < --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;
	point = pos(egap);
}

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

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

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

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

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

void
flip()
{
	table = table == modual ? modeless : modual;
	if (helpline == HELP_LINE) 
		mvaddstr(0, 0, table == modual ? help_ae: help_ea);
}

void
help()
{
	helpline = helpline == 0 ? HELP_LINE : 0;
	redraw();
}
	
void
display()
{
	char *p;
	int i, j;
	if (point < page)
		page = prevline(point);
	if (epage <= point) {
		page = nextline(point); 
		i = (page == pos(ebuf) ? LINES-2 : LINES) - helpline; 
		while (0 < i--)
			page = prevline(page-1);
	}
	move(helpline, 0);
	i = helpline;
	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') {
			addch(*p);
			j += *p == '\t' ? 8-(j&7) : 1;
		}
		if (*p == '\n' || COLS <= j) {
			++i;
			j = 0;
		}
		++epage;
	}
	clrtobot();
	if (++i < LINES)
		mvaddstr(i, 0, "<< EOF >>");
	move(row, col);
	refresh();
}

int
main(argc, argv)
int argc;
char **argv;
{
	FILE *fp;
	char *p = *argv;
	int i = (int) strlen(p);
	egap = ebuf = buf + BUF;
	if (argc < 2)
		return (2);
	/* Find basename. */
	while (0 <= i && p[i] != '\\' && p[i] != '/')
		--i;
	p += i+1;
	if (strncmp(p, "ae", 2) == 0 || strncmp(p, "AE", 2) == 0)
		table = modual;
	else if (strncmp(p, "ea", 2) == 0 || strncmp(p, "EA", 2) == 0)
		table = modeless;
	else
		return (2);
	if (initscr() == NULL)
		return (3);
	noecho();
	lineinput(FALSE);
	idlok(stdscr, TRUE);
	initkey();
	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(); 
		while (table[i].key != 0 && input != table[i].key)
			++i;
		(*table[i].func)();
	}
	endwin();
	return (0);
}
