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