----eaea.c---- /* * ae.c Anthony's Editor May '92 * * Public Domain 1991, 1992 by Anthony Howe. All rights released. */ #include #include #include #include #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 (\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 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 /* * 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); }