From e5d0fb542be011ff6c2653b4b33d7c33d6b22d9a Mon Sep 17 00:00:00 2001 From: Daniel Covington Date: Mon, 9 Nov 2026 11:31:10 -0500 Subject: [PATCH] Port the vim clone to the Commodore 128 with cc65 Single-file conio-based editor (vim128.c) that runs on the 40 or 80 column screen, with incremental redraw and 2 MHz mode in 80 column mode. Co-Authored-By: Claude Sonnet 5.5 --- vim/build128.bat | 24 ++ vim/vim128.c | 818 +++++++++++++++++++++++++++++++++++++++++++++++ vim/vim128.prg | Bin 0 -> 9710 bytes 3 files changed, 842 insertions(+) create mode 100644 vim/build128.bat create mode 100644 vim/vim128.c create mode 100644 vim/vim128.prg diff --git a/vim/build128.bat b/vim/build128.bat new file mode 100644 index 0000000..f2e4bc5 --- /dev/null +++ b/vim/build128.bat @@ -0,0 +1,24 @@ +@echo off +REM build128.bat [-run] - build the C128 editor with cc65 (vim128.prg). +REM build128.bat compile only +REM build128.bat -run compile, then start it in VICE x128 in 80-column mode +REM (attach a disk image for a source file) +setlocal +set "CC65=D:\Development\Commodore\cc65-snapshot-win32" +set "VICE=C:\Program Files\GTK3VICE-3.10-win64\bin" +if exist "%CC65%\bin\cl65.exe" set "PATH=%CC65%\bin;%PATH%" +set "CC65_HOME=%CC65%" +cd /d "%~dp0" + +echo Compiling vim128.c ... +cl65 -t c128 -O -m vim128.map -o vim128.prg vim128.c +if errorlevel 1 ( + echo BUILD FAILED + exit /b 1 +) +echo Build OK: vim128.prg + +if "%1"=="-run" ( + echo Launching in VICE x128 ... + "%VICE%\x128.exe" -80col -autostart vim128.prg -autostartprgmode 1 +) diff --git a/vim/vim128.c b/vim/vim128.c new file mode 100644 index 0000000..9b0a249 --- /dev/null +++ b/vim/vim128.c @@ -0,0 +1,818 @@ +/* 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; + } + } + } +} diff --git a/vim/vim128.prg b/vim/vim128.prg new file mode 100644 index 0000000000000000000000000000000000000000..294b6319ef3e07c0ebc476488dfeb3fd2f7865f5 GIT binary patch literal 9710 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 literal 0 HcmV?d00001