|
- /* vim128.c - a small modal editor for the Commodore 128, built with cc65.
- *
- * cl65 -t c128 -O -o vim128.prg vim128.c
- *
- * A port of the Power C / C64 editor (vim.c, vscreen.c, vview.c, vedit.c).
- * The Power C version needed hand-written machine code and direct screen
- * pokes; here cc65's conio draws the screen, so the same program works on the
- * 40 column VIC screen and on the 80 column VDC screen (it uses whatever
- * screensize() reports).
- *
- * move h j k l / arrows, 0 $ gg G
- * insert i a A I o O (RUN/STOP or ESC 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 PETSCII too: unshifted letters arrive as $41-$5a and
- * shifted ones as $c1-$da, which is what KL() and KU() below spell.
- */
-
- #include <conio.h>
- #include <cbm.h>
- #include <c128.h>
- #include <string.h>
-
- #define BUFMAX 19000u /* bytes of file text */
- #define MAXLN 600 /* lines */
- #define MAXCOL 80 /* longest line kept */
- #define NAMEMAX 20
-
- /* cc65 translates character literals to PETSCII for the c128 target, so 'h'
- is already $48 (what the unshifted key sends) and 'G' is $c7 (shifted). */
- #define KL(c) (c) /* unshifted letter, e.g. KL('h') */
- #define KU(c) (c) /* shifted letter, e.g. KU('G') */
-
- #define K_STOP 3
- #define K_ESC 27
- #define K_DEL 20
- #define K_RET 13
- #define K_DOWN 17
- #define K_UP 145
- #define K_RIGHT 29
- #define K_LEFT 157
-
- static unsigned char bfr[BUFMAX]; /* file text, $0d separated */
- static unsigned blen; /* bytes used */
- static unsigned lptr[MAXLN]; /* offset of each line start */
- static int nlines;
- static int cy, cx; /* cursor line and column */
- static int top, leftcol; /* first line / column on screen */
- static char fname[NAMEMAX + 1];
- static int mode; /* 0 normal, 1 insert */
- static int dirty; /* changed since the last save */
- static int pend; /* first key of dd / gg */
- static const char *note; /* one-shot status message */
- static unsigned char ybuf[MAXCOL]; /* the yanked line */
- static int ylen, yok;
- static int redraw; /* 1 cursor row, 2 whole screen */
- static int split; /* a long line was split on load */
- static unsigned char dev; /* disk device */
-
- static unsigned char sw, sh; /* screen width, height */
- static int rows; /* text rows = sh - 1 (last is status) */
- static int ltop, lleft, lcy, lcx;
-
- /* ------------------------------------------------------------------ screen */
-
- static void putstr(int r, int c, const char *s)
- {
- gotoxy(c, r);
- while (*s && c < sw) {
- cputc(*s++);
- c++;
- }
- }
-
- static char lstat[84];
-
- static void clrstat(int c)
- {
- lstat[0] = 0; /* status row no longer as drawn */
- /* the very last cell is left alone: writing it can make the screen
- editor wrap */
- gotoxy(c, sh - 1);
- while (c < sw - 1) {
- cputc(' ');
- c++;
- }
- }
-
- static void sinit(void)
- {
- screensize(&sw, &sh);
- rows = sh - 1;
- cbm_k_bsout(14); /* lower/upper case character set */
- cbm_k_bsout(8); /* ...and lock it against SHIFT+C= */
- bgcolor(COLOR_BLUE);
- bordercolor(COLOR_BLACK);
- textcolor(COLOR_WHITE);
- cursor(0);
- revers(0);
- clrscr();
- if (sw > 40) fast(); /* 2 MHz: only with the 80 column screen,
- the VIC display is blanked in fast mode */
- }
-
- static void sdone(void)
- {
- slow();
- revers(0);
- bgcolor(COLOR_BLACK);
- bordercolor(COLOR_LIGHTBLUE);
- textcolor(COLOR_LIGHTGREEN);
- clrscr();
- cursor(1);
- }
-
- static int skey(void)
- {
- return cgetc();
- }
-
- /* --------------------------------------------------------------- buffer */
-
- /* end offset (exclusive of the $0d) of line ln */
- static unsigned linend(int ln)
- {
- if (ln + 1 < nlines) return lptr[ln + 1] - 1;
- return blen;
- }
-
- static int linelen(int ln)
- {
- return (int) (linend(ln) - lptr[ln]);
- }
-
- static void clampx(void)
- {
- int len = linelen(cy);
- if (cx > len) cx = len;
- if (cx < 0) cx = 0;
- }
-
- static void buildidx(void)
- {
- unsigned i;
-
- nlines = 1;
- lptr[0] = 0;
- for (i = 0; i < blen; i++) {
- if (bfr[i] == 13 && nlines < MAXLN - 1) lptr[nlines++] = i + 1;
- }
- }
-
- static void adjust(int from, int delta) /* lptr[from..nlines-1] += delta */
- {
- int i;
- for (i = from; i < nlines; i++) lptr[i] += delta;
- }
-
- static void bmove(unsigned from, unsigned to, unsigned n)
- {
- unsigned char *s, *d;
-
- if (n == 0 || from == to) return;
- s = bfr + from;
- d = bfr + to;
- if (d < s) {
- while (n--) *d++ = *s++;
- } else {
- s += n;
- d += n;
- while (n--) *--d = *--s;
- }
- }
-
- static int bins(unsigned pos, int ch)
- {
- if (blen >= BUFMAX - 1) return 0;
- bmove(pos, pos + 1, blen - pos);
- bfr[pos] = ch;
- blen++;
- dirty = 1;
- return 1;
- }
-
- static void delrange(unsigned pos, unsigned n)
- {
- bmove(pos + n, pos, blen - pos - n);
- blen -= n;
- dirty = 1;
- }
-
- static int inschar(int ch)
- {
- if (linelen(cy) >= MAXCOL) {
- note = "line full (80)";
- return 0;
- }
- if (!bins(lptr[cy] + cx, ch)) return 0;
- adjust(cy + 1, 1);
- cx++;
- if (redraw < 1) redraw = 1;
- return 1;
- }
-
- static int newline(void)
- {
- unsigned pos;
- int i;
-
- if (nlines >= MAXLN - 1) return 0;
- pos = lptr[cy] + cx;
- if (!bins(pos, 13)) return 0;
- for (i = nlines; i > cy + 1; i--) lptr[i] = lptr[i - 1];
- lptr[cy + 1] = pos + 1;
- nlines++;
- adjust(cy + 2, 1);
- redraw = 2;
- cy++;
- cx = 0;
- return 1;
- }
-
- static int delchar(void)
- {
- if (cx >= linelen(cy)) return 0;
- delrange(lptr[cy] + cx, 1);
- adjust(cy + 1, -1);
- if (redraw < 1) redraw = 1;
- return 1;
- }
-
- static void bkspace(void)
- {
- int pl, i;
-
- if (cx > 0) {
- cx--;
- delchar();
- return;
- }
- if (cy == 0) return;
- pl = linelen(cy - 1);
- if (pl + linelen(cy) > MAXCOL) {
- note = "joined line would pass 80";
- return;
- }
- delrange(lptr[cy] - 1, 1);
- for (i = cy; i < nlines - 1; i++) lptr[i] = lptr[i + 1];
- nlines--;
- adjust(cy, -1);
- redraw = 2;
- cy--;
- cx = pl;
- }
-
- static void delline(void)
- {
- int i;
- unsigned s, e, n;
-
- s = lptr[cy];
- e = linend(cy);
- ylen = e - s;
- if (ylen > MAXCOL) ylen = MAXCOL;
- for (i = 0; i < ylen; i++) ybuf[i] = bfr[s + i];
- yok = 1;
-
- if (nlines == 1) {
- blen = 0;
- dirty = 1;
- } else if (cy + 1 < nlines) {
- n = lptr[cy + 1] - s;
- delrange(s, n);
- for (i = cy; i < nlines - 1; i++) lptr[i] = lptr[i + 1];
- nlines--;
- adjust(cy, -(int) n);
- } else { /* last line: take its leading $0d */
- delrange(s - 1, blen - s + 1);
- nlines--;
- cy--;
- }
- cx = 0;
- redraw = 2;
- }
-
- static void paste(void)
- {
- int i;
-
- if (!yok) return;
- cx = linelen(cy);
- if (!newline()) return;
- for (i = 0; i < ylen; i++) inschar(ybuf[i]);
- cx = 0;
- }
-
- /* cursor keys, the same in both modes. 1 if k was one of them */
- static int arrow(int k)
- {
- if (k == K_DOWN) {
- if (cy + 1 < nlines) { cy++; clampx(); }
- return 1;
- }
- if (k == K_UP) {
- if (cy > 0) { cy--; clampx(); }
- return 1;
- }
- if (k == K_RIGHT) {
- cx++;
- clampx();
- return 1;
- }
- if (k == K_LEFT) {
- if (cx > 0) cx--;
- return 1;
- }
- return 0;
- }
-
- /* ------------------------------------------------------------------- view */
-
- static void scroll(void)
- {
- if (cy < top) top = cy;
- if (cy > top + rows - 1) top = cy - rows + 1;
- if (top < 0) top = 0;
- if (cx < leftcol) leftcol = cx;
- if (cx > leftcol + sw - 1) leftcol = cx - sw + 1;
- if (leftcol < 0) leftcol = 0;
- }
-
- static unsigned char rlen[50]; /* columns drawn on each row last time */
-
- /* the character shown at screen row r, column col (blank past the line) */
- static int cellch(int r, int col)
- {
- int line = top + r;
- unsigned p;
- int ch = ' ';
-
- if (line < nlines) {
- p = lptr[line] + leftcol + col;
- if (p < linend(line)) ch = bfr[p];
- }
- if (ch < 32 || (ch >= 128 && ch < 160)) ch = ' ';
- return ch;
- }
-
- /* repaint row r from column 'from' to the end of the text, plus whatever old
- text must be blanked. Only the cells that can have changed are written:
- each character costs a real VDC/VIC access. */
- static void drawrow(int r, int from)
- {
- int line = top + r, vl = 0, col, end;
-
- if (line < nlines) {
- vl = linelen(line) - leftcol;
- if (vl < 0) vl = 0;
- if (vl > sw) vl = sw;
- }
- end = vl > rlen[r] ? vl : rlen[r];
- if (from < end) {
- gotoxy(from, r);
- for (col = from; col < end; col++) cputc(cellch(r, col));
- }
- rlen[r] = vl;
- }
-
- static void drawcell(int r, int col, int rev)
- {
- if (r < 0 || r >= rows || col < 0 || col >= sw) return;
- if (rev) revers(1);
- cputcxy(col, r, cellch(r, col));
- if (rev) revers(0);
- }
-
- static char *dnum(char *t, int v)
- {
- char d[6];
- int n = 0;
-
- if (v == 0) {
- *t++ = '0';
- return t;
- }
- while (v > 0 && n < 5) {
- d[n++] = '0' + (v % 10);
- v /= 10;
- }
- while (n > 0) *t++ = d[--n];
- return t;
- }
-
- static void dstat(void)
- {
- char t[84];
- const char *s;
- char *p;
- int i, n, pos;
-
- if (note) s = note;
- else if (mode) s = "-- insert --";
- else if (fname[0] == 0) s = "[no name]";
- else s = fname;
-
- n = 0;
- for (i = 0; s[i] && i < sw - 14; i++) t[n++] = s[i];
- pos = sw - 14; /* modified flag */
- while (n < pos) t[n++] = ' ';
- t[n++] = dirty ? '+' : ' ';
- t[n++] = ' ';
- p = dnum(t + n, cy + 1);
- *p++ = '/';
- p = dnum(p, nlines);
- while (p < t + sw - 1) *p++ = ' ';
- *p = 0;
-
- if (strcmp(t, lstat) == 0) return; /* unchanged: skip the writes */
- strcpy(lstat, t);
- revers(1);
- putstr(sh - 1, 0, t);
- revers(0);
- }
-
- static void dall(void)
- {
- int crow = cy - top;
- int ccol = cx - leftcol;
- int r, from;
-
- if (redraw == 2 || top != ltop || leftcol != lleft) {
- for (r = 0; r < rows; r++) drawrow(r, 0);
- } else {
- /* put the old cursor cell back to normal, then repaint what the edit
- touched: the cursor row from the old cursor column on */
- drawcell(lcy - top, lcx - lleft, 0);
- from = lcx - lleft;
- if (ccol < from) from = ccol;
- if (from < 0) from = 0;
- if (redraw == 1) drawrow(crow, from);
- }
- drawcell(crow, ccol, 1);
- if (crow >= 0 && crow < rows && ccol >= rlen[crow] && ccol < sw)
- rlen[crow] = ccol + 1; /* a cursor past the text must be blanked later */
- ltop = top;
- lleft = leftcol;
- lcy = cy;
- lcx = cx;
- redraw = 0;
- dstat();
- }
-
- /* ------------------------------------------------------------ prompt/disk */
-
- /* read a line of PETSCII into s, echoed from column c of the status row */
- static int getln(int c, char *s, int max)
- {
- int n = 0, k;
-
- s[0] = 0;
- for (;;) {
- k = skey();
- if (k == K_RET) break;
- if (k == K_STOP || k == K_ESC) {
- n = 0;
- s[0] = 0;
- break;
- }
- if (k == K_DEL) {
- if (n > 0) s[--n] = 0;
- } else {
- if (k >= 0xc1 && k <= 0xda) k -= 0x80;
- if (k >= 32 && k < 127 && n < max) {
- s[n++] = k;
- s[n] = 0;
- }
- }
- clrstat(c);
- putstr(sh - 1, c, s);
- }
- return n;
- }
-
- static int askyn(const char *q)
- {
- int k;
-
- clrstat(0);
- putstr(sh - 1, 0, q);
- for (;;) {
- k = skey();
- if (k == KL('y') || k == KU('Y')) return 1;
- if (k == KL('n') || k == KU('N') || k == K_STOP || k == K_ESC) return 0;
- }
- }
-
- static void askname(void)
- {
- clrstat(0);
- putstr(sh - 1, 0, "file: ");
- getln(6, fname, NAMEMAX);
- }
-
- /* the drive's error channel (open as file 15): the two digit code of the last
- command. 0-19 are fine, 62 is file not found. open of a missing file
- succeeds on a CBM drive - only this channel tells. */
- static int dosst(void)
- {
- unsigned char ch;
- int n = 0, d = 0;
-
- for (;;) {
- if (cbm_read(15, &ch, 1) != 1) break;
- if (ch == 13) break;
- if (d < 2 && ch >= '0' && ch <= '9') {
- n = n * 10 + ch - '0';
- d++;
- }
- }
- if (d < 2) return 99;
- return n;
- }
-
- /* build "name,s,r" (or "@0:name,s,w") in PETSCII */
- static void mkname(char *on, int save)
- {
- int i = 0, j = 0;
-
- if (save) {
- on[i++] = '@';
- on[i++] = '0';
- on[i++] = ':';
- }
- while (fname[j] && j < NAMEMAX) on[i++] = fname[j++];
- on[i++] = ',';
- on[i++] = 0x53; /* PETSCII S, not ASCII */
- on[i++] = ',';
- on[i++] = save ? 0x57 : 0x52; /* W or R */
- on[i] = 0;
- }
-
- /* 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 */
- static int loadf2(void);
- static int loadf(void)
- {
- int r;
- slow();
- r = loadf2();
- if (sw > 40) fast();
- return r;
- }
-
- static int loadf2(void)
- {
- unsigned char tmp[64];
- char on[32];
- int n, i, ch, c, prev = 0, col = 0;
-
- if (fname[0] == 0) return 1;
- mkname(on, 0);
-
- if (cbm_open(15, dev, 15, "") != 0) return 0;
- if (cbm_open(5, dev, 2, on) != 0) {
- cbm_close(15);
- return 0;
- }
- if (dosst() >= 20) {
- cbm_close(5);
- cbm_close(15);
- return 0;
- }
- blen = 0;
- split = 0;
- for (;;) {
- n = cbm_read(5, tmp, sizeof tmp);
- if (n <= 0) break;
- for (i = 0; i < n; i++) {
- ch = tmp[i];
- if (ch == 26) goto done;
- if (ch == 10 && prev == 13) { /* CRLF seen as two bytes */
- prev = ch;
- continue;
- }
- c = (ch == 10) ? 13 : ch;
- if (c != 13 && col >= MAXCOL) {
- if (blen < BUFMAX - 1) bfr[blen++] = 13;
- col = 0;
- split = 1;
- }
- if (blen < BUFMAX - 1) bfr[blen++] = c;
- col = (c == 13) ? 0 : col + 1;
- prev = ch;
- }
- }
- done:
- cbm_close(5);
- cbm_close(15); /* last: it closes everything */
- return 1;
- }
-
- /* write the buffer to fname, replacing any old copy. 1 on success */
- static int savef2(void);
- static int savef(void)
- {
- int r;
- slow();
- r = savef2();
- if (sw > 40) fast();
- return r;
- }
-
- static int savef2(void)
- {
- char on[32];
- unsigned char cr = 13;
- int ok;
-
- mkname(on, 1);
- if (cbm_open(15, dev, 15, "") != 0) return 0;
- if (cbm_open(5, dev, 3, on) != 0) {
- cbm_close(15);
- return 0;
- }
- if (blen > 0) cbm_write(5, bfr, blen);
- if (blen > 0 && bfr[blen - 1] != 13) cbm_write(5, &cr, 1);
- ok = (dosst() < 20);
- cbm_close(5);
- cbm_close(15);
- return ok;
- }
-
- /* the ":" command line. returns 1 when the editor should quit */
- static int doscmd(void)
- {
- char cmd[24];
- int n, p = 1, ok = 0, qf = 0;
-
- clrstat(0);
- putstr(sh - 1, 0, ":");
- n = getln(1, cmd, 20);
- clrstat(0);
- if (n == 0) return 0;
-
- if (cmd[0] == KL('q')) {
- if (cmd[1] == '!') return 1;
- if (dirty) {
- note = "unsaved - :q! quits";
- return 0;
- }
- return 1;
- }
- if (cmd[0] == KL('w')) ok = 1;
- if (cmd[0] == KL('x')) {
- ok = 1;
- qf = 1;
- }
- if (!ok) {
- note = "unknown command";
- return 0;
- }
- if (cmd[0] == KL('w') && cmd[1] == KL('q')) {
- qf = 1;
- p = 2;
- }
- while (cmd[p] == ' ') p++;
- if (cmd[p]) {
- n = 0;
- while (cmd[p] && n < NAMEMAX) 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 main(void)
- {
- int k, ok, p;
-
- dev = *(unsigned char *) 0x00ba; /* device we were loaded from */
- if (dev < 8) dev = 8;
-
- sinit();
-
- blen = 0;
- nlines = 1;
- lptr[0] = 0;
- cy = cx = top = leftcol = 0;
- fname[0] = 0;
- mode = dirty = pend = ylen = yok = split = 0;
- note = 0;
- redraw = 2;
- ltop = lleft = lcy = 0;
-
- for (;;) { /* a file to edit */
- askname();
- 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) { /* keep the name: :w creates it */
- nlines = 1;
- blen = 0;
- note = "new file";
- }
- if (split) note = "long lines split at 80";
- clrscr();
- memset(rlen, 0, sizeof rlen);
- lstat[0] = 0;
-
- for (;;) {
- scroll();
- dall();
- k = skey();
- note = 0;
-
- if (mode) { /* ---- insert mode */
- if (k == K_STOP || k == K_ESC) {
- mode = 0;
- if (cx > 0) cx--;
- } else if (k == K_RET) {
- newline();
- } else if (k == K_DEL) {
- bkspace();
- } else if (arrow(k)) {
- ;
- } else if ((k >= 32 && k < 128) || k >= 161) {
- inschar(k);
- }
- continue;
- }
-
- if (pend) { /* ---- second key of dd / gg */
- p = pend;
- pend = 0;
- if (p == KL('d') && k == KL('d')) delline();
- if (p == KL('g') && k == KL('g')) {
- cy = 0;
- cx = 0;
- }
- clampx();
- continue;
- }
-
- if (arrow(k)) {
- ;
- } else if (k == KL('h')) {
- if (cx > 0) cx--;
- } else if (k == KL('l')) {
- cx++;
- clampx();
- } else if (k == KL('j')) {
- arrow(K_DOWN);
- } else if (k == KL('k')) {
- arrow(K_UP);
- } else if (k == '0') {
- cx = 0;
- } else if (k == '$') {
- cx = linelen(cy);
- } else if (k == KU('G')) {
- cy = nlines - 1;
- cx = 0;
- } else if (k == KL('d')) {
- pend = KL('d');
- } else if (k == KL('g')) {
- pend = KL('g');
- } else if (k == KL('x')) {
- delchar();
- clampx();
- } else if (k == KL('p')) {
- paste();
- } else if (k == KL('i')) {
- mode = 1;
- } else if (k == KL('a')) {
- cx++;
- clampx();
- mode = 1;
- } else if (k == KU('A')) {
- cx = linelen(cy);
- mode = 1;
- } else if (k == KU('I')) {
- cx = 0;
- mode = 1;
- } else if (k == KL('o')) {
- cx = linelen(cy);
- newline();
- mode = 1;
- } else if (k == KU('O')) {
- cx = 0;
- newline();
- cy--;
- mode = 1;
- } else if (k == ':') {
- if (doscmd() == 1) {
- sdone();
- return 0;
- }
- }
- }
- }
|