/* 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; } } } }