Просмотр исходного кода

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 <noreply@anthropic.com>
main
Daniel Covington 4 дней назад
Родитель
Сommit
e5d0fb542b
3 измененных файлов: 842 добавлений и 0 удалений
  1. +24
    -0
      vim/build128.bat
  2. +818
    -0
      vim/vim128.c
  3. Двоичные данные
      vim/vim128.prg

+ 24
- 0
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
)

+ 818
- 0
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 <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;
}
}
}
}

Двоичные данные
vim/vim128.prg Просмотреть файл


Загрузка…
Отмена
Сохранить

Powered by TurnKey Linux.