From: Alan Cox Date: Fri, 23 Feb 2018 00:45:43 +0000 (+0000) Subject: vile: a VI Like Editor - WIP X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=488789e2c31e63aca9dfc0faea2c86490ff12e22;p=FUZIX.git vile: a VI Like Editor - WIP Not yet ready for Fuzix as we probably want to avoid stdio and curses (but maybe not - need to see on size). Either way our cursors lacks some of the funky features this uses at the moment I believe (eg keypad) --- diff --git a/Applications/util/vile.c b/Applications/util/vile.c new file mode 100644 index 00000000..1156ea96 --- /dev/null +++ b/Applications/util/vile.c @@ -0,0 +1,796 @@ +/* + * VILE - VI Like Editor + * + * Based upon: + * + * ae.c Anthony's Editor Mar '92 + * + * Public Domain 1991, 1992 by Anthony Howe. All rights released. + * + * ANSIfied, and extended eventually for Fuzix Alan Cox 2018 + * + * Copyright 2018 Alan Cox + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#ifndef BUF +#define BUF 16384 +#endif /* BUF */ + +#ifndef HUP +#define HUP "ae.hup" +#endif /* HUP */ + +typedef struct keytable_t { + int key; + int flags; +#define NORPT 1 +#define KEEPRPT 2 + int (*func)(void); +} keytable_t; + +int done; +int row, col; +int indexp, page, epage; +int input; +int repeat; +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 point = ptr(indexp) and point < gap or egap <= point + * + * o page <= indexp < epage + * + * o 0 <= indexp <= 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. + * + * + * TODO for Fuzix + * + * Make more vi like + * Implement regexps + * Add some minimal : commands (:w notably and :n) + * Yank/paste + * Remove stdio and curses use + * ZZ + * Use uint8, uint when we can for speed + * Use memmove not loops + * Macros out of the command blocks we have - may need them all to return + * an error status + * : blank lines vi style + * status bars etc in modeless mode + * long line support (including fixing insert_mode) + * + * Can we rewrite most of the editor functions in some kind of bytecode + * / keycode + * + * Can we do async redraw (if you type during redraw we skip updating and + * defer the work ?). Need to look at this post curses + */ + +int adjust(int, int); +int nextline(int); +int pos(char *); +int prevline(int); +int save(char *); +char *ptr(int); + +void display(void); +void movegap(void); +int insertch(char); +int fleft(char); +int fright(char); + +int backsp(void); +int bottom(void); +int delete(void); +int delete_line(void); +int down(void); +int file(void); +int insert(void); +int insert_mode(void); +int insert_before(void); +int append_mode(void); +int append_end(void); +int left(void); +int lnbegin(void); +int lnend(void); +int pgdown(void); +int pgup(void); +int pgup_half(void); +int redraw(void); +int right(void); +int replace(void); +int quit(void); +int flip(void); +int top(void); +int up(void); +int wleft(void); +int wright(void); +int noop(void); +int digit(void); +int open_after(void); +int open_before(void); +int delete_left(void); +int join(void); +int eword(void); +int findleft(void); +int findright(void); + +#undef CTRL +#define CTRL(x) ((x) & 0x1f) + +keytable_t modeless[] = { + { KEY_LEFT, 0, left }, + { KEY_RIGHT, 0, right }, + { KEY_DOWN, 0, down }, + { KEY_UP, 0, up }, + { CTRL('w'), 0, wleft }, + { CTRL('e'), 0, wright }, + { CTRL('n'), 0, pgdown }, + { CTRL('p'), 0, pgup }, + { CTRL('a'), NORPT, lnbegin }, + { CTRL('d'), NORPT, lnend }, + { CTRL('t'), NORPT, top }, + { CTRL('b'), NORPT, bottom }, + { KEY_BACKSPACE, 0, backsp }, + { '\b', 0, backsp }, + { KEY_DC, 0, delete }, + { CTRL('f'), NORPT, file }, + { CTRL('r'), NORPT, redraw }, + { CTRL('\\'), NORPT, quit }, + { CTRL('z'), NORPT, flip }, + { 0, 0, insert } +}; + +/* + * Basic vi commands missing (note not all below exactly match vi yet + * in their behaviour - much testing is needed + * + * CTRL-D/CTRL-U half screen scrolls + * CTRL-F/CTRL-B implemented but not exactly as vi + * nG goto line n + * W and S word skip with punctuation + * [] and () setence and paragraph skip + * H M L top/niddle/bottom of screen + * R replace mode + * t swap two characters + * cw/cc/c. change word, change line, change one char (+c$ etc) + * C change to end of line + * s substitute + * u undo + * dw,dd,de,d$ delete word/space, delete line, delete word, delete + * to eol (and d. I think delete char) d^ delete to + * start of line + * D synonum for d$ (aka c$ = C) + * f/Fc move forward and back to char c + * % move to associated bracket pair + * . repeat last text changing command + * + * All : functionality + * All yank/put functionality + * All regexps /?nN + * + * Try to write ops as far as possible in terms of each other and a few + * non-command 'ops. The goal is to make a lot of this macrocode for size. + */ + +keytable_t modual[] = { + { KEY_LEFT, 0, left }, + { KEY_RIGHT, 0, right }, + { KEY_DOWN, 0, down }, + { KEY_UP, 0, up }, + { 27, NORPT, noop }, + { 'h', 0, left }, + { '\b', 0, left }, + { 'j', 0, down }, + { '\n', 0, down }, + { 'k', 0, up }, + { 'l', 0, right }, + { ' ', 0, right }, + { 'b', 0, wleft }, + { 'e', 0, eword }, + { CTRL('F'), 0, pgdown }, /* Need D for half screen too */ + { CTRL('B'), 0, pgup }, + { CTRL('U'), 0, pgup_half }, + { 'w', 0, wright }, + { '^', NORPT, lnbegin }, + { '$', NORPT, lnend }, + { 't', NORPT, top }, /* Should be 0G */ + { 'b', NORPT, bottom }, /* Should be G b is begin of word */ + { 'i', NORPT, insert_mode }, + { 'I', NORPT, insert_before }, + { 'J', 0, join }, + { 'x', 0, delete }, + { 'X', 0, delete_left }, + { 'o', 0, open_after }, + { 'O', 0, open_before }, + { 'W', NORPT, file }, + { 'R', NORPT, redraw }, + { 'Q', NORPT, quit }, + { 'Z', NORPT, flip }, + { 'd', 0, delete_line }, /* Should be dd */ + { 'a', NORPT, append_mode }, + { 'A', NORPT, append_end }, + { 'r', 0, replace }, + { 'F', NORPT, findleft }, + { 'f', NORPT, findright }, + { '0', KEEPRPT, digit }, + { '1', KEEPRPT, digit }, + { '2', KEEPRPT, digit }, + { '3', KEEPRPT, digit }, + { '4', KEEPRPT, digit }, + { '5', KEEPRPT, digit }, + { '6', KEEPRPT, digit }, + { '7', KEEPRPT, digit }, + { '8', KEEPRPT, digit }, + { '9', KEEPRPT, digit }, + { 0, KEEPRPT, noop } +}; + + +char *ptr(int offset) +{ + if (offset < 0) + return (buf); + return (buf+offset + (buf+offset < gap ? 0 : egap-gap)); +} + +int pos(char *pointer) +{ + return (pointer-buf - (pointer < egap ? 0 : egap-gap)); +} + +int top(void) +{ + indexp = 0; + return 0; +} + +int bottom(void) +{ + epage = indexp = pos(ebuf); + return 0; +} + +int quit(void) +{ + done = 1; + return 0; +} + +int redraw(void) +{ + clear(); + display(); + return 0; +} + +int digit(void) +{ + repeat = repeat * 10 + input - '0'; + return 0; +} + +void movegap(void) +{ + char *p = ptr(indexp); + while (p < gap) + *--egap = *--gap; + while (egap < p) + *gap++ = *egap++; + indexp = pos(egap); +} + +int prevline(int offset) +{ + char *p; + while (buf < (p = ptr(--offset)) && *p != '\n') + ; + return (buf < p ? ++offset : 0); +} + +int nextline(int offset) +{ + char *p; + while ((p = ptr(offset++)) < ebuf && *p != '\n') + ; + return (p < ebuf ? offset : pos(ebuf)); +} + +int adjust(int offset, int 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); +} + +int left(void) +{ + if (0 < indexp) { + --indexp; + return 0; + } + return 1; +} + +int right(void) +{ + if (indexp < pos(ebuf)) { + ++indexp; + return 0; + } + return 1; +} + +int up(void) +{ + indexp = adjust(prevline(prevline(indexp)-1), col); + return 0; /* FIXME */ +} + +int down(void) +{ + indexp = adjust(nextline(indexp), col); + return 0; /* FIXME */ +} + +int lnbegin(void) +{ + indexp = prevline(indexp); + return 0; +} + +int lnend(void) +{ + indexp = nextline(indexp); + return left(); /* FIXME? if on end of last lie already ? */ +} +/* We want to the top of the next page: in theory I believe we shouldn't + move at all unless we can move this far. Need to save pointers and test ? */ +int pgdown(void) +{ + /* Go to bottom of our page */ + while(row < LINES) { + row++; + down(); + } + lnbegin(); + page = indexp; + epage = pos(ebuf); + return 0; +} + +/* Move to the bottom of the previous page, unless cursor is within 1st page */ +/* Ditto need to save pointers and test ? */ +int pgup(void) +{ + int i = LINES; + /* Not quite right but will do for now */ + if (page == 0) + return 1; + /* Go to the bottom of the page */ + while(row < LINES) { + row++; + down(); + } + /* Now go up a page, moving the page marker as we do + FIXME: just do the difference!!! */ + while (0 < --i) { + page = prevline(page-1); + up(); + } + return 0; +} + +/* TODO FIXME */ +int pgup_half(void) +{ + int i = LINES/2; + while (0 < --i) { + page = prevline(page-1); + up(); + } + return 0; +} + +int wleft(void) +{ + char *p; + while (!isspace(*(p = ptr(indexp))) && buf < p) + --indexp; + while (isspace(*(p = ptr(indexp))) && buf < p) + --indexp; + return p == buf; +} + +int eword(void) +{ + char *p; + while (!isspace(*(p = ptr(indexp))) && p < ebuf) + ++indexp; + return p == ebuf; +} + +int wright(void) +{ + char *p; + eword(); + while (isspace(*(p = ptr(indexp))) && p < ebuf) + ++indexp; + return p == ebuf; +} + +int fleft(char c) +{ + char *p; + if (*(p = ptr(indexp)) == c && buf < p) + --indexp; + while (*(p = ptr(indexp)) != c && buf < p) + --indexp; + return p == buf; +} + +int fright(char c) +{ + char *p; + if (*(p = ptr(indexp)) == c && p < ebuf) + ++indexp; + while (*(p = ptr(indexp)) != c && p < ebuf) + ++indexp; + return p == ebuf; +} + +int findleft(void) +{ + int c = getch(); + if (c < 0 || c > 255) + return 1; + return fleft(c); +} + +int findright(void) +{ + int c = getch(); + if (c < 0 || c > 255) + return 1; + return fright(c); +} + +/* Do we need a filter on this and insert_mode ? */ +int insertch(char ch) +{ + movegap(); + if (gap < egap) { + *gap++ = ch == '\r' ? '\n' : ch; + indexp = pos(egap); + return 0; + } + return 1; +} + +int insert(void) +{ + return insertch(input); +} + +int insert_before(void) +{ + lnbegin(); + display(); + return insert_mode(); +} + +/* Need to do a replace mode version */ +int insert_mode(void) +{ + int ch; + movegap(); + while ((ch = getch()) != 27) { + if (ch == 0) + continue; + if (ch == '\f') + break; + if (ch == '\b') { + if (buf < gap) + --gap; + } else if (gap < egap) { + *gap++ = ch == '\r' ? '\n' : ch; + } + indexp = pos(egap); + display(); + } + return 0; +} + +int append_mode(void) +{ + if (!right()) { + display(); + return insert_mode(); + } + return 0; +} + +int append_end(void) +{ + lnend(); + return append_mode(); +} + +int replace(void) +{ + int c = getch(); + /* FIXME: saner filter ? */ + if (c < 0 || c > 255) + return 1; + if (!delete()) + return insertch(c); + return 0; +} + +int delete_line(void) +{ + lnbegin(); + while(egap < ebuf - 1 && egap[1] != '\n') + indexp = pos(++egap); + return 0; +} + +int backsp(void) +{ + movegap(); + if (buf < gap) { + --gap; + indexp = pos(egap); + return 0; + } + return 1; +} + +int delete(void) +{ + movegap(); + if (egap < ebuf) { + indexp = pos(++egap); + return 0; + } + return 1; +} + +int delete_left(void) +{ + if (!left()) + return delete(); + return 1; +} + +int join(void) +{ + lnend(); + if (egap != ebuf) + return delete(); + return 1; +} + +int open_before(void) +{ + lnend(); + if (!insertch('\n')) + return append_mode(); + return 1; +} + +int open_after(void) +{ + lnbegin(); + if (!insertch('\n') && !up()) + return append_mode(); + return 0; +} + +int file(void) +{ + if (!save(filename)) + save(HUP); + return 0; +} + +int save(char *fn) +{ + FILE *fp; + int i, ok; + size_t length; + fp = fopen(fn, "w"); + if ((ok = fp != NULL)) { + i = indexp; + indexp = 0; + movegap(); + length = (size_t) (ebuf-egap); + ok = fwrite(egap, sizeof (char), length, fp) == length; + (void) fclose(fp); + indexp = i; + } + return (ok); +} + +int flip(void) +{ + table = table == modual ? modeless : modual; + return 0; +} + +int noop(void) +{ + return 0; +} + +void display(void) +{ + char *p; + int i, j; + if (indexp < page) + page = prevline(indexp); + if (epage <= indexp) { + page = nextline(indexp); + i = page == pos(ebuf) ? LINES-2 : LINES; + while (0 < i--) + page = prevline(page-1); + } + move(0, 0); + i = j = 0; + epage = page; + while (1) { + if (indexp == 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(); + while(++i < LINES) + mvaddstr(i, 0, "~"); + move(row, col); + refresh(); +} + +int main(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, "vi", 2) == 0) + table = modual; + else if (strncmp(p, "ev", 2) == 0 || strncmp(p, "ev", 2) == 0) + table = modeless; + else + return (2); + if (initscr() == NULL) + return (3); + raw(); + noecho(); + idlok(stdscr, 1); + keypad(stdscr, 1); + fp = fopen(filename = *++argv, "r"); + if (fp != NULL) { + gap += fread(buf, sizeof (char), (size_t) BUF, fp); + fclose(fp); + } + top(); + while (!done) { + display(); + i = 0; + input = getch(); + while (table[i].key != 0 && input != table[i].key) + ++i; + if (!repeat || (table[i].flags & NORPT)) + (*table[i].func)(); + else while(repeat--) + (*table[i].func)(); + if (!(table[i].flags & KEEPRPT)) + repeat = 0; + } + endwin(); + return (0); +} diff --git a/Applications/util/vile.doc b/Applications/util/vile.doc new file mode 100644 index 00000000..a5e5dc61 --- /dev/null +++ b/Applications/util/vile.doc @@ -0,0 +1,137 @@ +This is now obsolete but retained for the moment + + +NAME + ae Ant's Editor Mar '92 + + +USAGE + ae + ea + +where is the text file to edit or create. + + +DESCRIPTION +<< ae >> is a full screen, VI style text editor. The source should be +portable to any environment that provides a K&R C compiler and a +CURSES library. << ea >> is a full screen, EMACS style text editor. + +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. +Non-printable characters may have unpredictable results depending +on the implementation of CURSES. + + +COMMANDS + +VI 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 enter insert mode, formfeed to quit +x delete character under the cursor +W write buffer to file +R refresh the screen +Q quit +Z switch to EMACS style + +EMACS 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 +delete delete character under the cursor +^F write buffer to file +^R refresh the screen +^\ quit +^Z switch to VI style + + +EXIT STATUS +0 success +1 general error +2 usage error +3 CURSES screen initialization failed + + +INSTALLATION +Requires K&R C and a CURSES library for the given target machine. + +The file creation MODE should be set at compile time to 0600 for +Unix systems, or 0 for the Atari ST and PC. + +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. + +The character constants '\b', '\f', '\n', '\r', '\t' are used +in order to provide more portable code, since the compiler should +handle the translation of them into the native character set. +Note that '\f' (formfeed) was used to exit insert mode because +K&R C had no escape constant for the escape-key. + +Note that the "int " definition at the top of the source was for +the benifit of Turbo C, which returns lots of warnings. WatCom C +should return no warnings provided your curses.h has no NULL +expression defined for functions. System V and BSD should also +generate no warnings. The source should pass basic linting. +The source still looks like a mess when passed through our +C beautifier (your milleage may vary). + +On EBCDIC machines text files are made up of fixed length records. +My code should be portable in terms of character set and compiler +independance, however the Buffer Gap Scheme assumes a stream file +format used for UNIX text files and most personal computers. A bit +of alteration may be required if the C library routinues for read +and write() do not hide this fact. Switching to the stream i/o +library may solve this problem. One other problem that was not +addressed concerns block mode terminals. + +My goals for this project was to learn and experiment with the +Buffer Gap Scheme [Fin80][net90], write a useful and *portable* +programme, and meet the requirements of the IOCCC. I initially +planned to have a mini CURSES built-in like the IOCCC Tetris entry +from a previous year, however this was not as portable as using a +CURSES library with TERMINFO/TERMCAP support. + + +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. + + +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 + +