|
- /* vim.c - a small modal editor for the commodore 64, written for power c.
- *
- * Increment 2 edits: it reads a petscii SEQ file into one buffer, keeps a line
- * index, shows 24 lines plus a status row, and supports
- * move h j k l / arrows, 0 $ gg G
- * insert i a A I o O (RUN/STOP leaves insert mode, DEL backspaces)
- * change x dd p
- * colon :w :w name :wq :x :q :q!
- *
- * The buffer holds the file exactly as it sits on disk: petscii bytes with $0d
- * between lines. Keys are compared as numbers, never as character constants,
- * so nothing depends on how the compiler treats literals. (Unshifted letters
- * arrive as $41-$5a, so h = 72; shifted ones as $c1-$da, so G = 199.)
- */
-
- char *malloc();
- char *bfr; /* file text, $0d separated */
- unsigned blen; /* bytes used */
- unsigned lptr[600]; /* offset of each line start */
- int nlines;
- int cy, cx; /* cursor line and column */
- int top; /* first line on screen */
- int leftcol; /* horizontal scroll */
- char fname[24];
- int mode; /* 0 normal, 1 insert */
- int dirty; /* buffer changed since the last save */
- int pend; /* first key of a two key command: d or g */
- char *note; /* one-shot message for the status row */
- char ybuf[160]; /* the yanked line */
- int ylen, yok;
- int redraw; /* 1 cursor row, 2 whole screen */
-
- /* screen rows are 0..23, the status row is 24 */
-
- /* shorten fname to what fits */
- shortn(n)
- int n;
- {
- int i;
-
- i = 0;
- while (fname[i] && i < n) i++;
- fname[i] = 0;
- return 0;
- }
-
- /* a message that waits for a key */
- msg(s)
- char *s;
- {
- sclr(24, 0, 40);
- sps(24, 0, s);
- skey();
- return 0;
- }
-
- /* read a line of lowercase petscii into s, echoed from column c of row 24 */
- getln(c, s, max)
- int c, max;
- char *s;
- {
- int n, k;
-
- n = 0;
- s[0] = 0;
- for (;;) {
- k = skey();
- if (k == 13) break;
- if (k == 3) {
- n = 0;
- s[0] = 0;
- break;
- }
- if (k == 20) {
- if (n > 0) {
- n--;
- s[n] = 0;
- }
- } else {
- if (k >= 0xc1 && k <= 0xda) k = k - 0x80;
- if (k >= 32 && k != 127 && n < max) {
- s[n] = k;
- n++;
- s[n] = 0;
- }
- }
- sclr(24, c, max);
- sps(24, c, s);
- }
- return n;
- }
-
- /* a yes/no question on the status row: 1 for y, 0 for n or run/stop */
- askyn(q)
- char *q;
- {
- int k;
-
- sclr(24, 0, 40);
- sps(24, 0, q);
- for (;;) {
- k = skey();
- if (k == 89) return 1; /* y */
- if (k == 217) return 1; /* Y */
- if (k == 78) return 0; /* n */
- if (k == 206) return 0; /* N */
- if (k == 3) return 0;
- }
- }
-
- /* ask which file to open */
- askname()
- {
- sclr(24, 0, 40);
- sps(24, 0, "file: ");
- if (getln(6, fname, 20) == 0) fname[0] = 0;
- return 0;
- }
-
- /* the drive's error channel (already open as file 15): the two digit code of
- the last command. 0-19 are fine ("00, ok"), 62 is file not found. open() of
- a missing file succeeds on a cbm drive - only the error channel tells.
- do not close 15 while another file is open: closing the command channel
- closes every file on the drive. */
- dosst()
- {
- int ch, n, d;
-
- n = 0;
- d = 0;
- for (;;) {
- ch = getc(15);
- if (ch < 0) break;
- if (ch == 13) break;
- if (d < 2) {
- if (ch >= 48 && ch <= 57) {
- n = n * 10 + ch - 48;
- d++;
- }
- }
- }
- if (d < 2) return 99;
- return n;
- }
-
- int split; /* a long line was split on load */
-
- /* read fname into bfr. 1 on success, 0 if the file is not there.
- lines longer than 80 are split - the editor's line limit */
- loadf()
- {
- int f, ch, prev, c, col;
- char on[28];
- int i;
-
- if (fname[0] == 0) return 1;
- i = 0;
- while (fname[i] && i < 20) {
- on[i] = fname[i];
- i++;
- }
- on[i++] = 44; /* , */
- on[i++] = 83; /* S (petscii, not ascii) */
- on[i++] = 44; /* , */
- on[i++] = 82; /* R */
- on[i] = 0;
-
- if (open(15, 8, 15, "") == 0) return 0; /* status channel first */
- f = open(5, 8, 2, on);
- if (f == 0) {
- close(15);
- return 0;
- }
- if (dosst() >= 20) {
- close(5);
- close(15);
- return 0;
- }
- blen = 0;
- prev = 0;
- col = 0;
- split = 0;
- for (;;) {
- ch = getc(5);
- if (ch < 0) break; /* end of file */
- if (ch == 26) break;
- if (ch == 10 && prev == 13) { /* crlf seen as two bytes */
- prev = ch;
- continue;
- }
- if (ch == 13) c = 13;
- else if (ch == 10) c = 13;
- else c = ch;
- if (c != 13 && col >= 80) { /* 80 columns at most */
- if (blen < 18999) bfr[blen++] = 13;
- col = 0;
- split = 1;
- }
- if (blen < 18999) bfr[blen++] = c;
- if (c == 13) col = 0;
- else col++;
- prev = ch;
- }
- close(5);
- close(15); /* last: it closes everything */
- return 1;
- }
-
- /* find the start of every line */
- buildidx()
- {
- unsigned i;
-
- nlines = 1;
- lptr[0] = 0;
- for (i = 0; i < blen; i++) {
- if (bfr[i] == 13) {
- if (nlines < 599) {
- lptr[nlines] = i + 1;
- nlines++;
- }
- }
- }
- return 0;
- }
-
- /* the ":" command line. returns 1 when the editor should quit */
- doscmd()
- {
- int n, p, ok, qf;
- char cmd[24];
-
- sclr(24, 0, 40);
- spc(24, 0, 58); /* : */
- n = getln(1, cmd, 20);
- sclr(24, 0, 40);
- if (n == 0) return 0;
-
- if (cmd[0] == 81) { /* q, q! */
- if (cmd[1] == 33) return 1;
- if (dirty) {
- note = "unsaved - :q! quits";
- return 0;
- }
- return 1;
- }
-
- ok = 0;
- qf = 0;
- p = 1;
- if (cmd[0] == 87) ok = 1; /* w, wq, w name */
- if (cmd[0] == 88) { /* x */
- ok = 1;
- qf = 1;
- }
- if (ok == 0) {
- note = "unknown command";
- return 0;
- }
- if (cmd[0] == 87 && cmd[1] == 81) {
- qf = 1;
- p = 2;
- }
- while (cmd[p] == 32) p++;
- if (cmd[p]) {
- n = 0;
- while (cmd[p] && n < 20) fname[n++] = cmd[p++];
- fname[n] = 0;
- }
- if (fname[0] == 0) {
- note = "no file name";
- return 0;
- }
- if (savef()) {
- dirty = 0;
- note = "written";
- return qf;
- }
- note = "cannot write";
- return 0;
- }
-
- main()
- {
- int k, ok, p;
-
- sinit();
- mlinit(); /* ML helpers to $c000: before malloc */
- bfr = malloc(19000);
- if (bfr == 0) {
- sps(0, 0, "out of memory");
- skey();
- return;
- }
-
- blen = 0;
- nlines = 1;
- lptr[0] = 0;
- cy = 0;
- cx = 0;
- top = 0;
- leftcol = 0;
- fname[0] = 0;
- mode = 0;
- dirty = 0;
- pend = 0;
- note = 0;
- ylen = 0;
- yok = 0;
- split = 0;
- redraw = 2;
-
- for (;;) { /* a file to edit */
- askname();
- shortn(20);
- if (fname[0] == 0) { /* no name: an empty buffer */
- ok = 1;
- break;
- }
- ok = loadf();
- if (ok) break;
- if (askyn("not found - create it? (y/n) ")) break;
- }
- buildidx();
- if (ok == 0) { /* keep the name: :w creates it */
- nlines = 1;
- blen = 0;
- note = "new file";
- }
- if (split) note = "long lines split at 80";
-
- for (;;) {
- scroll();
- dall();
- k = skey();
- note = 0;
-
- if (mode) { /* ---- insert mode */
- if (k == 3) { /* RUN/STOP = Esc */
- mode = 0;
- if (cx > 0) cx--;
- } else if (k == 13) {
- newline();
- } else if (k == 20) {
- bkspace();
- } else if (arrow(k)) {
- cx = cx;
- } else if (k >= 32 && k < 128) {
- inschar(k);
- } else if (k >= 161) {
- inschar(k);
- }
- continue;
- }
-
- if (pend) { /* ---- second key of dd / gg */
- p = pend;
- pend = 0;
- if (p == 68 && k == 68) delline();
- if (p == 71 && k == 71) {
- cy = 0;
- cx = 0;
- }
- clampx();
- continue;
- }
-
- if (arrow(k)) {
- cx = cx;
- } else if (k == 72) { /* h */
- if (cx > 0) cx--;
- } else if (k == 76) { /* l */
- cx++;
- clampx();
- } else if (k == 74) { /* j */
- arrow(17);
- } else if (k == 75) { /* k */
- arrow(145);
- } else if (k == 48) { /* 0 */
- cx = 0;
- } else if (k == 36) { /* $ */
- cx = linend(cy) - lptr[cy];
- } else if (k == 199) { /* G */
- cy = nlines - 1;
- cx = 0;
- } else if (k == 68) { /* d.. */
- pend = 68;
- } else if (k == 71) { /* g.. */
- pend = 71;
- } else if (k == 88) { /* x */
- delchar();
- clampx();
- } else if (k == 80) { /* p */
- paste();
- } else if (k == 73) { /* i */
- mode = 1;
- } else if (k == 65) { /* a */
- cx++;
- clampx();
- mode = 1;
- } else if (k == 193) { /* A */
- cx = linend(cy) - lptr[cy];
- mode = 1;
- } else if (k == 201) { /* I */
- cx = 0;
- mode = 1;
- } else if (k == 79) { /* o */
- cx = linend(cy) - lptr[cy];
- newline();
- mode = 1;
- } else if (k == 207) { /* O */
- cx = 0;
- newline();
- cy--;
- mode = 1;
- } else if (k == 58) { /* : */
- if (doscmd() == 1) {
- sdone();
- return;
- }
- }
- }
- }
|