/* vim.c - a small vi/vim-style screen editor for the Commodore 64 * * Core half of the program: text buffer, cursor motion, key handling * and saving. Drawing lives in vscreen.c because Power C's compiler * reports "program too big" when the whole editor is one unit. The two * halves share functions only - every piece of state stays private to * this file and is handed to draw() as arguments - because sharing * variables across Power C object files proved unreliable. * * Modal editing, like vi: * * COMMAND mode INSERT mode * i insert here any key type text * a append after char RETURN split the line * o open line below DEL backspace * O open line above RUN/STOP back to COMMAND mode * x delete character cursor keys move * h j k l or cursor keys CLR/HOME start of line * 0 start of line * $ end of line * : command line * * : commands * :w write "notes" to disk (SEQ file, drive 8) * :wq write and quit * :q quit * * Special C characters, on the same keys the Power C manual gives for ced: * * { SHIFT + } SHIFT - \ English pound key * ~ C= (logo) P _ C= (logo) @ | C= (logo) * * * The Kernal already produces the codes Power C expects for these * ($DB, $DD, $5C, $AF, $A4, $DF - read from the key tables at $EB81), * so insert mode only has to accept them. The character ROM has no * brace or bar glyphs, so on screen they appear as the nearest glyph. * * ESCAPE is the C64 RUN/STOP key, which the Kernal reports in the * zero-page STOP flag at $91. * * The text buffer holds 2000 bytes with 13 ($0D) as the line break. * Keys come from the Kernal keyboard buffer at $0277, count at $c6. * * Build with build.bat in this folder: * * cc vim.c -> vim.o * cc vscreen.c -> vscreen.o * link -s * > vim.o * > vscreen.o * > ^ (stdlib.l + syslib.l) * > * output file name: vim * * Then run with: x64sc -autostart "vim.d81:vim" * or from BASIC: LOAD"VIM",8,1 : RUN * * Note: this Power C build rejects both $7C and $DC as "illegal * character" for the | operator, so nested tests are used instead of ||. */ #include static char text[2000]; /* document, 13 = line break */ static int tlen; /* bytes in use */ static int pos; /* cursor, byte offset into text */ static int mode; /* 0 command, 1 insert, 2 : prompt */ static char fname[17]; /* file name used by :w */ static char cmd[9]; /* text after the ':' */ static int cmdlen; static int quit; static int msgcode; /* message for draw(): 0 none, 1-4 */ static char *defnam = "notes"; /* ---------- provided by vscreen.c ---------- */ int draw(); int clrscr(); /* ---------- buffer helpers ---------- */ static lstart(n) /* offset of the first byte of line n */ int n; { int i; int c; if (n <= 0) return 0; c = 0; i = 0; while (i < tlen) { if (text[i] == 13) { c = c + 1; if (c == n) return i + 1; } i = i + 1; } return tlen; } static lcount() /* number of line breaks */ { int i; int c; c = 0; for (i = 0; i < tlen; i++) if (text[i] == 13) c = c + 1; return c; } static lnof() /* line holding the cursor */ { int i; int c; c = 0; for (i = 0; i < pos && i < tlen; i++) if (text[i] == 13) c = c + 1; return c; } static intend(o) /* offset of the line end */ int o; { int e; e = o; while (e < tlen && text[e] != 13) e = e + 1; return e; } static inschr(ch) int ch; { int i; if (tlen >= 1999) { msgcode = 1; return; } for (i = tlen; i > pos; i--) text[i] = text[i - 1]; text[pos] = ch; tlen = tlen + 1; pos = pos + 1; } static delat() { int i; if (pos >= tlen) return; for (i = pos; i < tlen - 1; i++) text[i] = text[i + 1]; tlen = tlen - 1; } static backsp() { if (pos <= 0) return; pos = pos - 1; delat(); } /* ---------- cursor motion ---------- */ static movleft() { if (pos > 0) pos = pos - 1; } static movright() { if (pos < tlen) pos = pos + 1; } static movdown() { int ln; int cs; int o; int e; ln = lnof(); if (ln + 1 > lcount()) return; cs = pos - lstart(ln); o = lstart(ln + 1); e = intend(o); pos = o + cs; if (pos > e) pos = e; } static movup() { int ln; int cs; int o; int e; ln = lnof(); if (ln <= 0) return; cs = pos - lstart(ln); o = lstart(ln - 1); e = intend(o); pos = o + cs; if (pos > e) pos = e; } static linehome() { pos = lstart(lnof()); } static lineend() { pos = intend(lstart(lnof())); } /* ---------- saving ---------- */ static savfil() { FILE *f; int i; f = fopen(fname, "w"); if (!f) { msgcode = 2; return; } if (ferror(f)) { msgcode = 2; fclose(f); return; } for (i = 0; i < tlen; i++) fputc(text[i], f); fclose(f); msgcode = 3; } /* ---------- : command line ---------- */ static docmd() { if (cmdlen == 0) return; if (cmd[0] == 'w') { if (cmdlen > 1 && cmd[1] == 'q') { savfil(); quit = 1; return; } savfil(); return; } if (cmd[0] == 'q') { quit = 1; return; } msgcode = 4; } /* ---------- key handlers ---------- */ static insk(k) int k; { if (k == 3) { /* ESC: leave insert mode */ mode = 0; if (pos > 0) pos = pos - 1; return; } if (k == 13) { inschr(13); return; } if (k == 20) { backsp(); return; } if (k == 157) { movleft(); return; } if (k == 29) { movright(); return; } if (k == 145) { movup(); return; } if (k == 17) { movdown(); return; } if (k == 19) { linehome(); return; } if (k >= 32 && k < 128) { inschr(k); return; } if (k == 219) { inschr(k); return; } /* SHIFT + : { */ if (k == 221) { inschr(k); return; } /* SHIFT - : } */ if (k == 223) { inschr(k); return; } /* logo * : | */ if (k == 175) { inschr(k); return; } /* logo P : ~ */ if (k == 164) { inschr(k); return; } /* logo @ : _ */ } static cmdk(k) int k; { if (k >= 65 && k <= 90) k = k + 32; /* letters arrive as PETSCII $41-$5A */ if (k == ':') { mode = 2; cmdlen = 0; cmd[0] = 0; return; } if (k == 'i') { mode = 1; return; } if (k == 'a') { if (pos < tlen && text[pos] != 13) pos = pos + 1; mode = 1; return; } if (k == 'o') { pos = intend(lstart(lnof())); inschr(13); mode = 1; return; } if (k == 'O') { pos = lstart(lnof()); inschr(13); pos = pos - 1; mode = 1; return; } if (k == 'x') { delat(); return; } if (k == 'h') { movleft(); return; } if (k == 157) { movleft(); return; } if (k == 'l') { movright(); return; } if (k == 29) { movright(); return; } if (k == 'j') { movdown(); return; } if (k == 17) { movdown(); return; } if (k == 'k') { movup(); return; } if (k == 145) { movup(); return; } if (k == '0') { linehome(); return; } if (k == 19) { linehome(); return; } if (k == '$') { lineend(); return; } } /* ---------- keyboard ---------- */ static getkey() /* one byte from the Kernal keyboard buffer, $0277, count at $c6 */ { char *cnt; char *buf; int i; int k; cnt = (char *) 198; buf = (char *) 631; if (*cnt == 0) return 0; if (*cnt > 10) { *cnt = 0; return 0; } k = buf[0]; for (i = 0; i < 9; i++) buf[i] = buf[i + 1]; *cnt = *cnt - 1; return k; } static waitkey() { char *stop; int k; for (;;) { stop = (char *) 145; /* $91: RUN/STOP flag */ if (*stop) { *stop = 0; return 3; } k = getkey(); if (k) return k; } } /* ---------- main loop ---------- */ main() { int k; int i; tlen = 0; pos = 0; mode = 0; cmdlen = 0; quit = 0; msgcode = 0; i = 0; while (defnam[i]) { fname[i] = defnam[i]; i = i + 1; } fname[i] = 0; while (!quit) { draw(text, tlen, pos, mode, fname, cmd, cmdlen, msgcode); msgcode = 0; k = waitkey(); if (mode == 2) { if (k == 13) { mode = 0; docmd(); } else if (k == 20) { if (cmdlen > 0) cmdlen = cmdlen - 1; } else if (k >= 32 && cmdlen < 8) { if (k >= 65 && k <= 90) k = k + 32; cmd[cmdlen] = k; cmdlen = cmdlen + 1; } continue; } if (mode == 1) { insk(k); continue; } cmdk(k); } clrscr(); printf("bye\n"); }