From 6e6c16aa23995426c3bd5178c180bb0bcc958203 Mon Sep 17 00:00:00 2001 From: Daniel Covington Date: Sat, 7 Nov 2026 08:36:13 -0500 Subject: [PATCH] vim: paint and edit in machine code, redraw only changed rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The single-row painter became vml.s at $C000: one sys() paints all 24 rows, memmove and line-index fix-ups run in ML, and the keyboard buffer is popped with IRQs masked. dall() repaints only the cursor row or the whole screen, so j went from 17 to 0-3 jiffies. Loading now checks the drive error channel and splits lines over 80 columns. The temporary jiffy counters used to measure the speedup are removed; the numbers are in NOTES.md. Generated with Codebuff 🤖 Co-Authored-By: Codebuff --- vim/NOTES.md | 40 ++++- vim/build-ml.ps1 | 89 ++++------ vim/shots/run.png | Bin 1683 -> 1715 bytes vim/vedit.c | 54 +++--- vim/vedit.pet | 2 +- vim/vim.c | 92 +++++++++- vim/vim.pet | 2 +- vim/vml.bin | Bin 0 -> 660 bytes vim/vml.c | 205 +++++++++++++++++++--- vim/vml.pet | 2 +- vim/vml.s | 439 ++++++++++++++++++++++++++++++++++++++++++++++ vim/vmlwrap.c | 134 ++++++++++++++ vim/vrow.bin | Bin 76 -> 0 bytes vim/vrow.s | 69 -------- vim/vscreen.c | 23 +-- vim/vscreen.pet | 2 +- vim/vview.c | 57 +++--- vim/vview.pet | 2 +- vim/work.d81 | Bin 819200 -> 819200 bytes vim/work.stamp | 10 +- 20 files changed, 993 insertions(+), 229 deletions(-) create mode 100644 vim/vml.bin create mode 100644 vim/vml.s create mode 100644 vim/vmlwrap.c delete mode 100644 vim/vrow.bin delete mode 100644 vim/vrow.s diff --git a/vim/NOTES.md b/vim/NOTES.md index ad9dbd7..2ea8f14 100644 --- a/vim/NOTES.md +++ b/vim/NOTES.md @@ -116,6 +116,40 @@ run driven from the command line — but the keys race the program's start: type a filename and it can be eaten by BASIC before the program runs. +## Machine language helpers (vml.s) - what bit us + +- **`sys()` is using zero page `$22-$2A` while your routine runs.** The manual + lists 34-42 as "temporary storage" for ML, but sys() keeps where to store + A/X/Y back in there: our routine left a screen pointer in `$25`, so sys() + wrote X/Y into screen RAM (`$0404`) and then jumped to `$0002`. Every entry in + `vml.s` saves `$22-$2A`, `$4B-$60`, `$FB-$FC` on entry and restores them + before returning (`zsave`/`zrest`). The old single-row painter had the same + bug; it only showed as one wrong cell at the start of the status row. +- **Do not put anything at `$CF00`.** Power C's C stack starts just under + `$D000`, and it is set up before `main()` runs, so `highmem(0xc000)` cannot + move it. The code goes at `$C000` (copied there by `mlinit()` after + `highmem(0xc000)`, before `malloc`), the parameter block at `$C400`, the + zero page save area at `$C410`. +- **Every Power C function call is expensive.** Filling the parameter block + with one helper call per field made a full repaint cost 7 jiffies; writing + through one pointer inline brought it down. Keep C out of per-row and + per-byte loops: one `sys()` paints all 24 rows (`scrn`), memmove (`bmove`) + and line-index fix-ups (`ladj`) are ML too. +- **Key repeat.** The Power C shell leaves `$028A` set to repeat every key, and + VICE `-keybuf` holds each key for wall-clock time while the machine runs at + warp, so one `j` became seven. `sinit()` puts `$028A` back to 0 (C64 + default: only cursor keys, space and DEL repeat). +- Popping the KERNAL keyboard buffer is done in ML with `SEI`, so the IRQ + cannot append a key half way through the shift. + +Measured with the jiffy clock at `$a2` and a temporary `d e` readout +on the status row (since removed): + +| | before | after | +|---|---|---| +| `j` (repaint) | 17 jiffies | 0-3 | +| insert a char near the top of a 9 KB file | 103 jiffies | 14 | + ## C64 screen facts the editor relies on - Screen matrix `$0400`, colour RAM `$D800` (both directly writable). @@ -126,7 +160,11 @@ run `$60-$7F` minus `$20`, `$A0-$BF` minus `$40`, `$C0-$FF` minus `$80`. - Reverse video is the high bit: screen code `+ $80`. That is the cursor block and the status row. -- `$00CC = 0` stops the KERNAL cursor blinking over the editor's own cursor. +- `$00CC` non-zero stops the KERNAL cursor blinking (0 turns it **on**); with + it left at 0 the KERNAL blinked one cell of the status row. +- **Closing the command channel (15) closes every file on the drive.** Open 15 + first, `open()` the file, read the status from 15 (a missing file still + opens - only the status says `62`), read the file, close it, close 15 last. - Arrow keys are PETSCII `$11` down, `$91` up, `$1D` right, `$9D` left; RETURN `$0D`, DEL `$14`. diff --git a/vim/build-ml.ps1 b/vim/build-ml.ps1 index a588c15..80411c7 100644 --- a/vim/build-ml.ps1 +++ b/vim/build-ml.ps1 @@ -1,31 +1,47 @@ -# build-ml.ps1 - assemble vrow.s with cc65 and turn the result into vml.c, the -# Power C unit that holds the machine code as a byte array and calls it. +# build-ml.ps1 - assemble vml.s with cc65 and generate vml.c, the Power C unit +# that carries the machine code as a byte array plus its C wrappers. # # powershell -NoProfile -File build-ml.ps1 # -# Run it whenever vrow.s changes, then build.ps1 as usual. vml.c is generated: -# edit vrow.s, not vml.c. +# Run it whenever vml.s or vmlwrap.c changes, then build.ps1 as usual. +# vml.c is generated: edit vml.s (the code) or vmlwrap.c (the C side). +# +# The code is assembled for $C000. At start-up mlinit() calls highmem($C000) so +# Power C keeps below it, and copies the array up there. Entry addresses come +# from ld65's label file and are written into vml.c as #defines. $ErrorActionPreference = "Stop" $here = $PSScriptRoot -$bin = "D:\Development\Commodore\cc65-snapshot-win32\bin" -$ca65 = Join-Path $bin "ca65.exe" -$ld65 = Join-Path $bin "ld65.exe" -$addr = 0xC020 # where the routine lives; the parameter block is at $C000 -$cfg = Join-Path $env:TEMP "vrow.cfg" +$ca65 = (Get-Command ca65 -ErrorAction SilentlyContinue).Source +if (-not $ca65) { throw "ca65 not found on PATH (cc65 bin folder)" } +$ld65 = Join-Path (Split-Path $ca65) "ld65.exe" + +$tmp = Join-Path $env:TEMP "vml-build" +New-Item -ItemType Directory -Force -Path $tmp | Out-Null +$cfg = Join-Path $tmp "vml.cfg" @" -MEMORY { M: start = `$C020, size = `$0100, file = "%O"; } +MEMORY { M: start = `$C000, size = `$0400, file = "%O"; } SEGMENTS { CODE: load = M, type = ro; } "@ | Set-Content $cfg -Encoding ascii -$obj = Join-Path $env:TEMP "vrow.o" -$out = Join-Path $here "vrow.bin" -& $ca65 (Join-Path $here "vrow.s") -o $obj +$obj = Join-Path $tmp "vml.o" +$lbl = Join-Path $tmp "vml.lbl" +$out = Join-Path $here "vml.bin" +& $ca65 (Join-Path $here "vml.s") -o $obj if ($LASTEXITCODE) { throw "ca65 failed" } -& $ld65 -C $cfg -o $out $obj +& $ld65 -C $cfg -o $out -Ln $lbl $obj if ($LASTEXITCODE) { throw "ld65 failed" } +# entry points: "al 00C000 .scrn" +$defs = @() +foreach ($ln in (Get-Content $lbl)) { + if ($ln -match '^al\s+([0-9A-Fa-f]+)\s+\.(scrn|row|bmove|ladj|kpop)$') { + $defs += ("#define ML{0} 0x{1}" -f $matches[2].ToUpper(), $matches[1].Substring(2).ToLower()) + } +} +if ($defs.Count -ne 5) { throw "expected 5 entry labels in $lbl, got $($defs.Count)" } + $b = [IO.File]::ReadAllBytes($out) $rows = @() for ($i = 0; $i -lt $b.Length; $i += 12) { @@ -33,49 +49,22 @@ for ($i = 0; $i -lt $b.Length; $i += 12) { $rows += " " + (($b[$i..($i + $n - 1)] | ForEach-Object { "$_" }) -join ", ") } $arr = $rows -join ",`n" +$wrap = Get-Content (Join-Path $here "vmlwrap.c") -Raw @" -/* vml.c - GENERATED by build-ml.ps1 from vrow.s. Do not edit by hand. +/* vml.c - GENERATED by build-ml.ps1 from vml.s and vmlwrap.c. Do not edit. * - * The machine code row painter, kept as a byte array and called in place - * through sys(). It is position independent; its parameters go through zero - * page scratch `$22-`$28, which Power C leaves to ML routines. + * The machine code helpers as a byte array. mlinit() copies them to `$C000. */ +$($defs -join "`n") +#define MLSIZE $($b.Length) + char mlcode[$($b.Length)] = { $arr }; -/* nothing to install: the code runs where the array sits */ -mlinit() -{ - return 0; -} - -/* paint the screen row at address dst: len bytes from src, cursor at column cur - (255 = none), rev = 128 for an all reverse row */ -mlrow(src, len, dst, cur, rev) -unsigned src, dst; -int len, cur, rev; -{ - char *p; - char a, x, y; - - p = (char *) 0x22; - p[0] = src & 255; - p[1] = src >> 8; - p[2] = len; - p[3] = dst & 255; - p[4] = dst >> 8; - p[5] = cur; - p[6] = rev; - a = 0; - x = 0; - y = 0; - sys((unsigned) mlcode, &a, &x, &y); /* c64 form: no bank argument */ - return 0; -} -"@ | Set-Content (Join-Path $here "vml.c") -Encoding utf8 - -Write-Host ("vrow.bin {0} bytes -> vml.c" -f $b.Length) +$wrap +"@ | Set-Content (Join-Path $here "vml.c") -Encoding ascii +Write-Host ("vml.bin {0} bytes -> vml.c ({1})" -f $b.Length, ($defs -join '; ')) diff --git a/vim/shots/run.png b/vim/shots/run.png index a485291332862fcc23d9c9f1d313cd361f9e144b..d526e0d9662e1ac16f95c09fc795c579e1c5a695 100644 GIT binary patch delta 1179 zcmV;M1Z4Y@4YLi9BtLpdL_t(|+U?!jaoac;fKe*r4zy*gONfhCO3;?Eu7P>d48!mO zfTVtqIOok9-4;slkq~Izx~}Ux0Gji|5Rrc(k$@hNfE#~6RX(C$z`sBLU%k!qai9F} zpI_hJLEyCh`}I%T0hpTT#}oVb=eZ!{J}!VWfJ&rH&~uadI10Iw{nGuXf^)U!2ikM% zx~`9W0k0*X;RH4}d8Ho7k8ig&fv!F7+~cMGoEx>he+KGg31~1OFD>n)PSPo77|+MO zdp_>FPmq7POH+G`etzIwKF;tWOOMbaA{HG{fK*j30gWfKlwCke%RF1x&{LLJ-tig0 zk{ckj$?%k+UQu*MS5=;*uVeg1Qg zYoC8Ua7NVxV2>l_qnB`Ak8N@XuHaJF0UWjLGItT0Wgfs8z>Y`PB}drr+~+?v=}+BH z9Dv%n4gerX^WOsKnTy*VjGP5ILJoG)F^ zxqt43Y+H{Ya-ZM$89(C}Gkz`v8w}dg1z5w|?5`hJ97M>MO7{V*+*<2cI5cMySNTu@Q>0yV&fMz zeqaeGc=^XZV&exi76AK*?Za+$A2we^vJG^{Bk&`aB<#aBeq4|109*$E2tbG*R~%I9 z$pGt4aUFm!WCytD8Nk#w!?{U+?$5V=Av}OHfQrrnN&>RwaR3A$h-Dps_o-qDXn5pa zvWzpnKLG1&0|f|T8z_-sACZF|k$@oq2$60xk$?@6Z!(d95Rq~(k$@19axam95Cb3p zhLiCGD1VN0>$W7q}?5H$e@e+V2N t003h65Ezp&1vHbe1Pha81tAw8%5Mg^mavuihc5sC002ovPDHLkV1mRtLt_8{ literal 1683 zcmcIldrZ?;7`-ipRR$CZfhp4Z4~YsYj)y4%Ey^fWp@3#=yoz%h1QjS{NG*NL#i?_I zxd6+8fRzaY3LT@QEuj<^p+E{nU=n$?fVDhZwYI;~(snf&$u<|GGrP&nJ^7MzzI*R? z?su;z#1mYdcRB+AT#2#KNdUmu_UVpvvPVRmx=jF_Qi#z}r;Df7XQwGucz{nlQsR_6PD{M!GH{gO%|ijtS#9%;TFg$t%w&TLiV?pI z37~d+z{ge&wfra%jeGm>>QtOZ778NJ(yyn%aApP8e`(GM8(Lzfa^!BVB!+GDms@r= z%PR6xkk6m3Sy{3`kCR7?5v08A#oG57AxAKGwK%g$g;Xw}>AmuIhl{SPV!J*qN%bZ2 zXFzVl7Z1euMvL`R*=y@(FbrIWB6U?O?lW@v%xyhaNG<%SgnBm|HHyLABX3|7d|it@ zSU}=Tbg|Y7lUJY0XYnJ#?ulDy`tSM^z-!iA=37vEsO0&a_M}hE9KD|t@kD$o2wS^@ zZYv+p+DkzCpEvS4nJxWVFt;La6NC|JgSy*m7P${~D->&(o(eGq`43`#(r?Nbk_*C5 z2(i3wX8NFc60$@t&Y|;$4eSp${BMtvIb3Tpe!q3-@wXAm(L#aIN+cH{03LKwosr5r zFA#7C0AY8}pRkfI6SH$#ADt9=1gYDP@k-N)A5&?J;R=2u4;6QIOgHBfEa-&$T; zN?i^X59#2?e~?V6nx#f*q(o(!N8Y|aq*!2TI-sV#B8Aytqw=ihC^$piq;}RJOKZM9 zTP7(qMz?Pl%u_bde%OlD(hCnuy?kmOYP=Pf zOfhRjJv07jvntE5fh(}6`y58?Xd7QvTc9lv{K`u^BPJ2a9L|5TQr zU89SYp>;g9Q6MZW(k_##ZFg6p;|)U29K|nx-69m=k}o-umvwNk3wF9mgt<6m zG4hIeD7GIwfsVLYAi!@2#;m5Q-i}Vp#lf3W7bx`|;pX<9Yi5c-Sc_$Y)44`@$!3P; zTy+>6k%>p3eUxLaZ4wR4DA(B=#|}o!e5l!bw>w}6J>GlSAHkbpfTcz$?X6+UGk>wq z7Tj9@Zmu_pzC!$UMBkGBFE8UwHviUi``y0IDeb;!&8;ISb?U1k@c)(s(Ubh*M@SnW XXSLciUYVDBfqf7^h>vbPntAyTOWZ)K diff --git a/vim/vedit.c b/vim/vedit.c index 1b3f457..9d54f4c 100644 --- a/vim/vedit.c +++ b/vim/vedit.c @@ -8,6 +8,7 @@ extern char *note; extern char fname[]; extern char ybuf[]; extern int ylen, yok; +extern int redraw; /* 1 cursor row, 2 whole screen */ /* end offset (exclusive of the $0d) of line ln */ linend(ln) @@ -35,10 +36,8 @@ bins(pos, ch) unsigned pos; int ch; { - unsigned i; - if (blen >= 18999) return 0; - for (i = blen; i > pos; i--) bfr[i] = bfr[i - 1]; + mlmove((unsigned) bfr + pos, (unsigned) bfr + pos + 1, blen - pos); bfr[pos] = ch; blen++; dirty = 1; @@ -49,39 +48,43 @@ int ch; delrange(pos, n) unsigned pos, n; { - unsigned i; - - for (i = pos; i + n < blen; i++) bfr[i] = bfr[i + n]; + mlmove((unsigned) bfr + pos + n, (unsigned) bfr + pos, blen - pos - n); blen = blen - n; dirty = 1; return 0; } +#define MAXCOL 80 /* longest line the editor keeps */ + /* type one character at the cursor */ inschar(ch) int ch; { - int l; - + if (linend(cy) - lptr[cy] >= MAXCOL) { + note = "line full (80)"; + return 0; + } if (bins(lptr[cy] + cx, ch) == 0) return 0; - for (l = cy + 1; l < nlines; l++) lptr[l]++; + mladj((unsigned) &lptr[cy + 1], nlines - cy - 1, 1); cx++; + if (redraw < 1) redraw = 1; return 1; } /* split the line at the cursor */ newline() { - int l; unsigned pos; if (nlines >= 599) return 0; pos = lptr[cy] + cx; if (bins(pos, 13) == 0) return 0; - for (l = nlines; l > cy + 1; l--) lptr[l] = lptr[l - 1]; + mlmove((unsigned) &lptr[cy + 1], (unsigned) &lptr[cy + 2], + (nlines - cy - 1) * 2); lptr[cy + 1] = pos + 1; nlines++; - for (l = cy + 2; l < nlines; l++) lptr[l]++; + mladj((unsigned) &lptr[cy + 2], nlines - cy - 2, 1); + redraw = 2; cy++; cx = 0; return 1; @@ -90,18 +93,17 @@ newline() /* delete the character under the cursor */ delchar() { - int l; - if (cx >= linend(cy) - lptr[cy]) return 0; delrange(lptr[cy] + cx, 1); - for (l = cy + 1; l < nlines; l++) lptr[l]--; + mladj((unsigned) &lptr[cy + 1], nlines - cy - 1, 65535); + if (redraw < 1) redraw = 1; return 1; } /* backspace: delete before the cursor, or join onto the previous line */ bkspace() { - int l, pl; + int pl; if (cx > 0) { cx--; @@ -110,10 +112,16 @@ bkspace() } if (cy == 0) return 0; pl = linend(cy - 1) - lptr[cy - 1]; + if (pl + linend(cy) - lptr[cy] > MAXCOL) { + note = "joined line would pass 80"; + return 0; + } delrange(lptr[cy] - 1, 1); - for (l = cy; l < nlines - 1; l++) lptr[l] = lptr[l + 1]; + mlmove((unsigned) &lptr[cy + 1], (unsigned) &lptr[cy], + (nlines - cy - 1) * 2); nlines--; - for (l = cy; l < nlines; l++) lptr[l]--; + mladj((unsigned) &lptr[cy], nlines - cy, 65535); + redraw = 2; cy--; cx = pl; return 0; @@ -122,13 +130,13 @@ bkspace() /* dd: yank the current line and remove it */ delline() { - int l, i; + int i; unsigned s, e, n; s = lptr[cy]; e = linend(cy); ylen = e - s; - if (ylen > 159) ylen = 159; + if (ylen > MAXCOL) ylen = MAXCOL; for (i = 0; i < ylen; i++) ybuf[i] = bfr[s + i]; yok = 1; @@ -138,15 +146,17 @@ delline() } else if (cy + 1 < nlines) { n = lptr[cy + 1] - s; delrange(s, n); - for (l = cy; l < nlines - 1; l++) lptr[l] = lptr[l + 1]; + mlmove((unsigned) &lptr[cy + 1], (unsigned) &lptr[cy], + (nlines - cy - 1) * 2); nlines--; - for (l = cy; l < nlines; l++) lptr[l] -= n; + mladj((unsigned) &lptr[cy], nlines - cy, 0 - n); } else { /* last line: take its leading $0d */ delrange(s - 1, blen - s + 1); nlines--; cy--; } cx = 0; + redraw = 2; return 0; } diff --git a/vim/vedit.pet b/vim/vedit.pet index 8165488..fcc19ed 100644 --- a/vim/vedit.pet +++ b/vim/vedit.pet @@ -1 +1 @@ -/* VEDIT.C - BUFFER EDITING FOR THE C64 VIM CLONE: INSERT, DELETE, SAVE. */ EXTERN CHAR *BFR; EXTERN UNSIGNED BLEN; EXTERN UNSIGNED LPTR[]; EXTERN INT NLINES, CY, CX, TOP, LEFTCOL, MODE, DIRTY; EXTERN CHAR *NOTE; EXTERN CHAR FNAME[]; EXTERN CHAR YBUF[]; EXTERN INT YLEN, YOK; /* END OFFSET (EXCLUSIVE OF THE $0D) OF LINE LN */ LINEND(LN) INT LN; Û IF (LN + 1 < NLINES) RETURN LPTR[LN + 1] - 1; RETURN BLEN; Ý /* KEEP CX INSIDE THE CURRENT LINE */ CLAMPX() Û INT LEN; LEN = LINEND(CY) - LPTR[CY]; IF (CX > LEN) CX = LEN; IF (CX < 0) CX = 0; RETURN 0; Ý /* ------------------------------------------------------------- EDITING */ /* INSERT BYTE CH AT BUFFER OFFSET POS. 1 ON SUCCESS, 0 IF THE BUFFER IS FULL */ BINS(POS, CH) UNSIGNED POS; INT CH; Û UNSIGNED I; IF (BLEN >= 18999) RETURN 0; FOR (I = BLEN; I > POS; I--) BFR[I] = BFR[I - 1]; BFR[POS] = CH; BLEN++; DIRTY = 1; RETURN 1; Ý /* REMOVE N BYTES AT OFFSET POS */ DELRANGE(POS, N) UNSIGNED POS, N; Û UNSIGNED I; FOR (I = POS; I + N < BLEN; I++) BFR[I] = BFR[I + N]; BLEN = BLEN - N; DIRTY = 1; RETURN 0; Ý /* TYPE ONE CHARACTER AT THE CURSOR */ INSCHAR(CH) INT CH; Û INT L; IF (BINS(LPTR[CY] + CX, CH) == 0) RETURN 0; FOR (L = CY + 1; L < NLINES; L++) LPTR[L]++; CX++; RETURN 1; Ý /* SPLIT THE LINE AT THE CURSOR */ NEWLINE() Û INT L; UNSIGNED POS; IF (NLINES >= 599) RETURN 0; POS = LPTR[CY] + CX; IF (BINS(POS, 13) == 0) RETURN 0; FOR (L = NLINES; L > CY + 1; L--) LPTR[L] = LPTR[L - 1]; LPTR[CY + 1] = POS + 1; NLINES++; FOR (L = CY + 2; L < NLINES; L++) LPTR[L]++; CY++; CX = 0; RETURN 1; Ý /* DELETE THE CHARACTER UNDER THE CURSOR */ DELCHAR() Û INT L; IF (CX >= LINEND(CY) - LPTR[CY]) RETURN 0; DELRANGE(LPTR[CY] + CX, 1); FOR (L = CY + 1; L < NLINES; L++) LPTR[L]--; RETURN 1; Ý /* BACKSPACE: DELETE BEFORE THE CURSOR, OR JOIN ONTO THE PREVIOUS LINE */ BKSPACE() Û INT L, PL; IF (CX > 0) Û CX--; DELCHAR(); RETURN 0; Ý IF (CY == 0) RETURN 0; PL = LINEND(CY - 1) - LPTR[CY - 1]; DELRANGE(LPTR[CY] - 1, 1); FOR (L = CY; L < NLINES - 1; L++) LPTR[L] = LPTR[L + 1]; NLINES--; FOR (L = CY; L < NLINES; L++) LPTR[L]--; CY--; CX = PL; RETURN 0; Ý /* DD: YANK THE CURRENT LINE AND REMOVE IT */ DELLINE() Û INT L, I; UNSIGNED S, E, N; S = LPTR[CY]; E = LINEND(CY); YLEN = E - S; IF (YLEN > 159) YLEN = 159; 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 (L = CY; L < NLINES - 1; L++) LPTR[L] = LPTR[L + 1]; NLINES--; FOR (L = CY; L < NLINES; L++) LPTR[L] -= N; Ý ELSE Û /* LAST LINE: TAKE ITS LEADING $0D */ DELRANGE(S - 1, BLEN - S + 1); NLINES--; CY--; Ý CX = 0; RETURN 0; Ý /* P: PUT THE YANKED LINE BELOW THE CURSOR LINE */ PASTE() Û INT I; IF (YOK == 0) RETURN 0; CX = LINEND(CY) - LPTR[CY]; IF (NEWLINE() == 0) RETURN 0; FOR (I = 0; I < YLEN; I++) INSCHAR(YBUF[I]); CX = 0; RETURN 0; Ý /* CURSOR KEYS, THE SAME IN BOTH MODES. 1 IF K WAS ONE OF THEM */ ARROW(K) INT K; Û IF (K == 17) Û IF (CY + 1 < NLINES) Û CY++; CLAMPX(); Ý RETURN 1; Ý IF (K == 145) Û IF (CY > 0) Û CY--; CLAMPX(); Ý RETURN 1; Ý IF (K == 29) Û CX++; CLAMPX(); RETURN 1; Ý IF (K == 157) Û IF (CX > 0) CX--; RETURN 1; Ý RETURN 0; Ý /* WRITE THE BUFFER TO FNAME, REPLACING ANY OLD COPY. 1 ON SUCCESS */ SAVEF() Û CHAR ON[32]; INT I, F; UNSIGNED J; ON[0] = 64; /* @ */ ON[1] = 48; /* 0 */ ON[2] = 58; /* : */ I = 3; J = 0; WHILE (FNAME[J] && J < 20) ON[I++] = FNAME[J++]; ON[I++] = 44; /* , */ ON[I++] = 83; /* Ó (PETSCII, NOT ASCII) */ ON[I++] = 44; /* , */ ON[I++] = 87; /* × */ ON[I] = 0; F = OPEN(5, 8, 3, ON); IF (F == 0) RETURN 0; FOR (J = 0; J < BLEN; J++) PUTC(BFR[J], 5); IF (BLEN > 0) Û IF (BFR[BLEN - 1] != 13) PUTC(13, 5); Ý CLOSE(5); RETURN 1; Ý \ No newline at end of file +/* VEDIT.C - BUFFER EDITING FOR THE C64 VIM CLONE: INSERT, DELETE, SAVE. */ EXTERN CHAR *BFR; EXTERN UNSIGNED BLEN; EXTERN UNSIGNED LPTR[]; EXTERN INT NLINES, CY, CX, TOP, LEFTCOL, MODE, DIRTY; EXTERN CHAR *NOTE; EXTERN CHAR FNAME[]; EXTERN CHAR YBUF[]; EXTERN INT YLEN, YOK; EXTERN INT REDRAW; /* 1 CURSOR ROW, 2 WHOLE SCREEN */ /* END OFFSET (EXCLUSIVE OF THE $0D) OF LINE LN */ LINEND(LN) INT LN; Û IF (LN + 1 < NLINES) RETURN LPTR[LN + 1] - 1; RETURN BLEN; Ý /* KEEP CX INSIDE THE CURRENT LINE */ CLAMPX() Û INT LEN; LEN = LINEND(CY) - LPTR[CY]; IF (CX > LEN) CX = LEN; IF (CX < 0) CX = 0; RETURN 0; Ý /* ------------------------------------------------------------- EDITING */ /* INSERT BYTE CH AT BUFFER OFFSET POS. 1 ON SUCCESS, 0 IF THE BUFFER IS FULL */ BINS(POS, CH) UNSIGNED POS; INT CH; Û IF (BLEN >= 18999) RETURN 0; MLMOVE((UNSIGNED) BFR + POS, (UNSIGNED) BFR + POS + 1, BLEN - POS); BFR[POS] = CH; BLEN++; DIRTY = 1; RETURN 1; Ý /* REMOVE N BYTES AT OFFSET POS */ DELRANGE(POS, N) UNSIGNED POS, N; Û MLMOVE((UNSIGNED) BFR + POS + N, (UNSIGNED) BFR + POS, BLEN - POS - N); BLEN = BLEN - N; DIRTY = 1; RETURN 0; Ý #DEFINE ÍÁØÃÏÌ 80 /* LONGEST LINE THE EDITOR KEEPS */ /* TYPE ONE CHARACTER AT THE CURSOR */ INSCHAR(CH) INT CH; Û IF (LINEND(CY) - LPTR[CY] >= ÍÁØÃÏÌ) Û NOTE = "LINE FULL (80)"; RETURN 0; Ý IF (BINS(LPTR[CY] + CX, CH) == 0) RETURN 0; MLADJ((UNSIGNED) &LPTR[CY + 1], NLINES - CY - 1, 1); CX++; IF (REDRAW < 1) REDRAW = 1; RETURN 1; Ý /* SPLIT THE LINE AT THE CURSOR */ NEWLINE() Û UNSIGNED POS; IF (NLINES >= 599) RETURN 0; POS = LPTR[CY] + CX; IF (BINS(POS, 13) == 0) RETURN 0; MLMOVE((UNSIGNED) &LPTR[CY + 1], (UNSIGNED) &LPTR[CY + 2], (NLINES - CY - 1) * 2); LPTR[CY + 1] = POS + 1; NLINES++; MLADJ((UNSIGNED) &LPTR[CY + 2], NLINES - CY - 2, 1); REDRAW = 2; CY++; CX = 0; RETURN 1; Ý /* DELETE THE CHARACTER UNDER THE CURSOR */ DELCHAR() Û IF (CX >= LINEND(CY) - LPTR[CY]) RETURN 0; DELRANGE(LPTR[CY] + CX, 1); MLADJ((UNSIGNED) &LPTR[CY + 1], NLINES - CY - 1, 65535); IF (REDRAW < 1) REDRAW = 1; RETURN 1; Ý /* BACKSPACE: DELETE BEFORE THE CURSOR, OR JOIN ONTO THE PREVIOUS LINE */ BKSPACE() Û INT PL; IF (CX > 0) Û CX--; DELCHAR(); RETURN 0; Ý IF (CY == 0) RETURN 0; PL = LINEND(CY - 1) - LPTR[CY - 1]; IF (PL + LINEND(CY) - LPTR[CY] > ÍÁØÃÏÌ) Û NOTE = "JOINED LINE WOULD PASS 80"; RETURN 0; Ý DELRANGE(LPTR[CY] - 1, 1); MLMOVE((UNSIGNED) &LPTR[CY + 1], (UNSIGNED) &LPTR[CY], (NLINES - CY - 1) * 2); NLINES--; MLADJ((UNSIGNED) &LPTR[CY], NLINES - CY, 65535); REDRAW = 2; CY--; CX = PL; RETURN 0; Ý /* DD: YANK THE CURRENT LINE AND REMOVE IT */ DELLINE() Û INT I; UNSIGNED S, E, N; S = LPTR[CY]; E = LINEND(CY); YLEN = E - S; IF (YLEN > ÍÁØÃÏÌ) YLEN = ÍÁØÃÏÌ; 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); MLMOVE((UNSIGNED) &LPTR[CY + 1], (UNSIGNED) &LPTR[CY], (NLINES - CY - 1) * 2); NLINES--; MLADJ((UNSIGNED) &LPTR[CY], NLINES - CY, 0 - N); Ý ELSE Û /* LAST LINE: TAKE ITS LEADING $0D */ DELRANGE(S - 1, BLEN - S + 1); NLINES--; CY--; Ý CX = 0; REDRAW = 2; RETURN 0; Ý /* P: PUT THE YANKED LINE BELOW THE CURSOR LINE */ PASTE() Û INT I; IF (YOK == 0) RETURN 0; CX = LINEND(CY) - LPTR[CY]; IF (NEWLINE() == 0) RETURN 0; FOR (I = 0; I < YLEN; I++) INSCHAR(YBUF[I]); CX = 0; RETURN 0; Ý /* CURSOR KEYS, THE SAME IN BOTH MODES. 1 IF K WAS ONE OF THEM */ ARROW(K) INT K; Û IF (K == 17) Û IF (CY + 1 < NLINES) Û CY++; CLAMPX(); Ý RETURN 1; Ý IF (K == 145) Û IF (CY > 0) Û CY--; CLAMPX(); Ý RETURN 1; Ý IF (K == 29) Û CX++; CLAMPX(); RETURN 1; Ý IF (K == 157) Û IF (CX > 0) CX--; RETURN 1; Ý RETURN 0; Ý /* WRITE THE BUFFER TO FNAME, REPLACING ANY OLD COPY. 1 ON SUCCESS */ SAVEF() Û CHAR ON[32]; INT I, F; UNSIGNED J; ON[0] = 64; /* @ */ ON[1] = 48; /* 0 */ ON[2] = 58; /* : */ I = 3; J = 0; WHILE (FNAME[J] && J < 20) ON[I++] = FNAME[J++]; ON[I++] = 44; /* , */ ON[I++] = 83; /* Ó (PETSCII, NOT ASCII) */ ON[I++] = 44; /* , */ ON[I++] = 87; /* × */ ON[I] = 0; F = OPEN(5, 8, 3, ON); IF (F == 0) RETURN 0; FOR (J = 0; J < BLEN; J++) PUTC(BFR[J], 5); IF (BLEN > 0) Û IF (BFR[BLEN - 1] != 13) PUTC(13, 5); Ý CLOSE(5); RETURN 1; Ý \ No newline at end of file diff --git a/vim/vim.c b/vim/vim.c index 689f413..130e00a 100644 --- a/vim/vim.c +++ b/vim/vim.c @@ -28,6 +28,7 @@ int pend; /* first key of a two key command: d or g */ char *note; /* one-shot message for the status row */ char ybuf[160]; /* the yanked line */ int ylen, yok; +int redraw; /* 1 cursor row, 2 whole screen */ /* screen rows are 0..23, the status row is 24 */ @@ -89,6 +90,24 @@ char *s; return n; } +/* a yes/no question on the status row: 1 for y, 0 for n or run/stop */ +askyn(q) +char *q; +{ + int k; + + sclr(24, 0, 40); + sps(24, 0, q); + for (;;) { + k = skey(); + if (k == 89) return 1; /* y */ + if (k == 217) return 1; /* Y */ + if (k == 78) return 0; /* n */ + if (k == 206) return 0; /* N */ + if (k == 3) return 0; + } +} + /* ask which file to open */ askname() { @@ -98,10 +117,39 @@ askname() return 0; } -/* read fname into bfr. 1 on success, 0 if it could not be opened */ +/* the drive's error channel (already open as file 15): the two digit code of + the last command. 0-19 are fine ("00, ok"), 62 is file not found. open() of + a missing file succeeds on a cbm drive - only the error channel tells. + do not close 15 while another file is open: closing the command channel + closes every file on the drive. */ +dosst() +{ + int ch, n, d; + + n = 0; + d = 0; + for (;;) { + ch = getc(15); + if (ch < 0) break; + if (ch == 13) break; + if (d < 2) { + if (ch >= 48 && ch <= 57) { + n = n * 10 + ch - 48; + d++; + } + } + } + if (d < 2) return 99; + return n; +} + +int split; /* a long line was split on load */ + +/* 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 */ loadf() { - int f, ch, prev, c; + int f, ch, prev, c, col; char on[28]; int i; @@ -117,10 +165,21 @@ loadf() on[i++] = 82; /* R */ on[i] = 0; + if (open(15, 8, 15, "") == 0) return 0; /* status channel first */ f = open(5, 8, 2, on); - if (f == 0) return 0; + if (f == 0) { + close(15); + return 0; + } + if (dosst() >= 20) { + close(5); + close(15); + return 0; + } blen = 0; prev = 0; + col = 0; + split = 0; for (;;) { ch = getc(5); if (ch < 0) break; /* end of file */ @@ -132,10 +191,18 @@ loadf() if (ch == 13) c = 13; else if (ch == 10) c = 13; else c = ch; + if (c != 13 && col >= 80) { /* 80 columns at most */ + if (blen < 18999) bfr[blen++] = 13; + col = 0; + split = 1; + } if (blen < 18999) bfr[blen++] = c; + if (c == 13) col = 0; + else col++; prev = ch; } close(5); + close(15); /* last: it closes everything */ return 1; } @@ -218,7 +285,7 @@ main() int k, ok, p; sinit(); - mlinit(); /* machine code row painter */ + mlinit(); /* ML helpers to $c000: before malloc */ bfr = malloc(19000); if (bfr == 0) { sps(0, 0, "out of memory"); @@ -240,16 +307,27 @@ main() note = 0; ylen = 0; yok = 0; + split = 0; + redraw = 2; - askname(); - ok = loadf(); - shortn(20); + for (;;) { /* a file to edit */ + askname(); + shortn(20); + 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 == 0) { /* keep the name: :w creates it */ nlines = 1; blen = 0; note = "new file"; } + if (split) note = "long lines split at 80"; for (;;) { scroll(); diff --git a/vim/vim.pet b/vim/vim.pet index c957569..ad00526 100644 --- a/vim/vim.pet +++ b/vim/vim.pet @@ -1 +1 @@ -/* VIM.C - A SMALL MODAL EDITOR FOR THE COMMODORE 64, WRITTEN FOR POWER C. * * ÉNCREMENT 2 EDITS: IT READS A PETSCII ÓÅÑ FILE INTO ONE BUFFER, KEEPS A LINE * INDEX, SHOWS 24 LINES PLUS A STATUS ROW, AND SUPPORTS * MOVE H J K L / ARROWS, 0 $ GG Ç * INSERT I A Á É O Ï (ÒÕÎ/ÓÔÏÐ LEAVES INSERT MODE, ÄÅÌ BACKSPACES) * CHANGE X DD P * COLON :W :W NAME :WQ :X :Q :Q! * * ÔHE BUFFER HOLDS THE FILE EXACTLY AS IT SITS ON DISK: PETSCII BYTES WITH $0D * BETWEEN LINES. ËEYS ARE COMPARED AS NUMBERS, NEVER AS CHARACTER CONSTANTS, * SO NOTHING DEPENDS ON HOW THE COMPILER TREATS LITERALS. (ÕNSHIFTED LETTERS * ARRIVE AS $41-$5A, SO H = 72; SHIFTED ONES AS $C1-$DA, SO Ç = 199.) */ CHAR *MALLOC(); CHAR *BFR; /* FILE TEXT, $0D SEPARATED */ UNSIGNED BLEN; /* BYTES USED */ UNSIGNED LPTR[600]; /* OFFSET OF EACH LINE START */ INT NLINES; INT CY, CX; /* CURSOR LINE AND COLUMN */ INT TOP; /* FIRST LINE ON SCREEN */ INT LEFTCOL; /* HORIZONTAL SCROLL */ CHAR FNAME[24]; INT MODE; /* 0 NORMAL, 1 INSERT */ INT DIRTY; /* BUFFER CHANGED SINCE THE LAST SAVE */ INT PEND; /* FIRST KEY OF A TWO KEY COMMAND: D OR G */ CHAR *NOTE; /* ONE-SHOT MESSAGE FOR THE STATUS ROW */ CHAR YBUF[160]; /* THE YANKED LINE */ INT YLEN, YOK; /* SCREEN ROWS ARE 0..23, THE STATUS ROW IS 24 */ /* SHORTEN FNAME TO WHAT FITS */ SHORTN(N) INT N; Û INT I; I = 0; WHILE (FNAME[I] && I < N) I++; FNAME[I] = 0; RETURN 0; Ý /* A MESSAGE THAT WAITS FOR A KEY */ MSG(S) CHAR *S; Û SCLR(24, 0, 40); SPS(24, 0, S); SKEY(); RETURN 0; Ý /* READ A LINE OF LOWERCASE PETSCII INTO S, ECHOED FROM COLUMN C OF ROW 24 */ GETLN(C, S, MAX) INT C, MAX; CHAR *S; Û INT N, K; N = 0; S[0] = 0; FOR (;;) Û K = SKEY(); IF (K == 13) BREAK; IF (K == 3) Û N = 0; S[0] = 0; BREAK; Ý IF (K == 20) Û IF (N > 0) Û N--; S[N] = 0; Ý Ý ELSE Û IF (K >= 0XC1 && K <= 0XDA) K = K - 0X80; IF (K >= 32 && K != 127 && N < MAX) Û S[N] = K; N++; S[N] = 0; Ý Ý SCLR(24, C, MAX); SPS(24, C, S); Ý RETURN N; Ý /* ASK WHICH FILE TO OPEN */ ASKNAME() Û SCLR(24, 0, 40); SPS(24, 0, "FILE: "); IF (GETLN(6, FNAME, 20) == 0) FNAME[0] = 0; RETURN 0; Ý /* READ FNAME INTO BFR. 1 ON SUCCESS, 0 IF IT COULD NOT BE OPENED */ LOADF() Û INT F, CH, PREV, C; CHAR ON[28]; INT I; IF (FNAME[0] == 0) RETURN 1; I = 0; WHILE (FNAME[I] && I < 20) Û ON[I] = FNAME[I]; I++; Ý ON[I++] = 44; /* , */ ON[I++] = 83; /* Ó (PETSCII, NOT ASCII) */ ON[I++] = 44; /* , */ ON[I++] = 82; /* Ò */ ON[I] = 0; F = OPEN(5, 8, 2, ON); IF (F == 0) RETURN 0; BLEN = 0; PREV = 0; FOR (;;) Û CH = GETC(5); IF (CH < 0) BREAK; /* END OF FILE */ IF (CH == 26) BREAK; IF (CH == 10 && PREV == 13) Û /* CRLF SEEN AS TWO BYTES */ PREV = CH; CONTINUE; Ý IF (CH == 13) C = 13; ELSE IF (CH == 10) C = 13; ELSE C = CH; IF (BLEN < 18999) BFR[BLEN++] = C; PREV = CH; Ý CLOSE(5); RETURN 1; Ý /* FIND THE START OF EVERY LINE */ BUILDIDX() Û UNSIGNED I; NLINES = 1; LPTR[0] = 0; FOR (I = 0; I < BLEN; I++) Û IF (BFR[I] == 13) Û IF (NLINES < 599) Û LPTR[NLINES] = I + 1; NLINES++; Ý Ý Ý RETURN 0; Ý /* THE ":" COMMAND LINE. RETURNS 1 WHEN THE EDITOR SHOULD QUIT */ DOSCMD() Û INT N, P, OK, QF; CHAR CMD[24]; SCLR(24, 0, 40); SPC(24, 0, 58); /* : */ N = GETLN(1, CMD, 20); SCLR(24, 0, 40); IF (N == 0) RETURN 0; IF (CMD[0] == 81) Û /* Q, Q! */ IF (CMD[1] == 33) RETURN 1; IF (DIRTY) Û NOTE = "UNSAVED - :Q! QUITS"; RETURN 0; Ý RETURN 1; Ý OK = 0; QF = 0; P = 1; IF (CMD[0] == 87) OK = 1; /* W, WQ, W NAME */ IF (CMD[0] == 88) Û /* X */ OK = 1; QF = 1; Ý IF (OK == 0) Û NOTE = "UNKNOWN COMMAND"; RETURN 0; Ý IF (CMD[0] == 87 && CMD[1] == 81) Û QF = 1; P = 2; Ý WHILE (CMD[P] == 32) P++; IF (CMD[P]) Û N = 0; WHILE (CMD[P] && N < 20) 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 K, OK, P; SINIT(); MLINIT(); /* MACHINE CODE ROW PAINTER */ BFR = MALLOC(19000); IF (BFR == 0) Û SPS(0, 0, "OUT OF MEMORY"); SKEY(); RETURN; Ý BLEN = 0; NLINES = 1; LPTR[0] = 0; CY = 0; CX = 0; TOP = 0; LEFTCOL = 0; FNAME[0] = 0; MODE = 0; DIRTY = 0; PEND = 0; NOTE = 0; YLEN = 0; YOK = 0; ASKNAME(); OK = LOADF(); SHORTN(20); BUILDIDX(); IF (OK == 0) Û /* KEEP THE NAME: :W CREATES IT */ NLINES = 1; BLEN = 0; NOTE = "NEW FILE"; Ý FOR (;;) Û SCROLL(); DALL(); K = SKEY(); NOTE = 0; IF (MODE) Û /* ---- INSERT MODE */ IF (K == 3) Û /* ÒÕÎ/ÓÔÏÐ = ÅSC */ MODE = 0; IF (CX > 0) CX--; Ý ELSE IF (K == 13) Û NEWLINE(); Ý ELSE IF (K == 20) Û BKSPACE(); Ý ELSE IF (ARROW(K)) Û CX = CX; Ý ELSE IF (K >= 32 && K < 128) Û INSCHAR(K); Ý ELSE IF (K >= 161) Û INSCHAR(K); Ý CONTINUE; Ý IF (PEND) Û /* ---- SECOND KEY OF DD / GG */ P = PEND; PEND = 0; IF (P == 68 && K == 68) DELLINE(); IF (P == 71 && K == 71) Û CY = 0; CX = 0; Ý CLAMPX(); CONTINUE; Ý IF (ARROW(K)) Û CX = CX; Ý ELSE IF (K == 72) Û /* H */ IF (CX > 0) CX--; Ý ELSE IF (K == 76) Û /* L */ CX++; CLAMPX(); Ý ELSE IF (K == 74) Û /* J */ ARROW(17); Ý ELSE IF (K == 75) Û /* K */ ARROW(145); Ý ELSE IF (K == 48) Û /* 0 */ CX = 0; Ý ELSE IF (K == 36) Û /* $ */ CX = LINEND(CY) - LPTR[CY]; Ý ELSE IF (K == 199) Û /* Ç */ CY = NLINES - 1; CX = 0; Ý ELSE IF (K == 68) Û /* D.. */ PEND = 68; Ý ELSE IF (K == 71) Û /* G.. */ PEND = 71; Ý ELSE IF (K == 88) Û /* X */ DELCHAR(); CLAMPX(); Ý ELSE IF (K == 80) Û /* P */ PASTE(); Ý ELSE IF (K == 73) Û /* I */ MODE = 1; Ý ELSE IF (K == 65) Û /* A */ CX++; CLAMPX(); MODE = 1; Ý ELSE IF (K == 193) Û /* Á */ CX = LINEND(CY) - LPTR[CY]; MODE = 1; Ý ELSE IF (K == 201) Û /* É */ CX = 0; MODE = 1; Ý ELSE IF (K == 79) Û /* O */ CX = LINEND(CY) - LPTR[CY]; NEWLINE(); MODE = 1; Ý ELSE IF (K == 207) Û /* Ï */ CX = 0; NEWLINE(); CY--; MODE = 1; Ý ELSE IF (K == 58) Û /* : */ IF (DOSCMD() == 1) Û SDONE(); RETURN; Ý Ý Ý Ý \ No newline at end of file +/* VIM.C - A SMALL MODAL EDITOR FOR THE COMMODORE 64, WRITTEN FOR POWER C. * * ÉNCREMENT 2 EDITS: IT READS A PETSCII ÓÅÑ FILE INTO ONE BUFFER, KEEPS A LINE * INDEX, SHOWS 24 LINES PLUS A STATUS ROW, AND SUPPORTS * MOVE H J K L / ARROWS, 0 $ GG Ç * INSERT I A Á É O Ï (ÒÕÎ/ÓÔÏÐ LEAVES INSERT MODE, ÄÅÌ BACKSPACES) * CHANGE X DD P * COLON :W :W NAME :WQ :X :Q :Q! * * ÔHE BUFFER HOLDS THE FILE EXACTLY AS IT SITS ON DISK: PETSCII BYTES WITH $0D * BETWEEN LINES. ËEYS ARE COMPARED AS NUMBERS, NEVER AS CHARACTER CONSTANTS, * SO NOTHING DEPENDS ON HOW THE COMPILER TREATS LITERALS. (ÕNSHIFTED LETTERS * ARRIVE AS $41-$5A, SO H = 72; SHIFTED ONES AS $C1-$DA, SO Ç = 199.) */ CHAR *MALLOC(); CHAR *BFR; /* FILE TEXT, $0D SEPARATED */ UNSIGNED BLEN; /* BYTES USED */ UNSIGNED LPTR[600]; /* OFFSET OF EACH LINE START */ INT NLINES; INT CY, CX; /* CURSOR LINE AND COLUMN */ INT TOP; /* FIRST LINE ON SCREEN */ INT LEFTCOL; /* HORIZONTAL SCROLL */ CHAR FNAME[24]; INT MODE; /* 0 NORMAL, 1 INSERT */ INT DIRTY; /* BUFFER CHANGED SINCE THE LAST SAVE */ INT PEND; /* FIRST KEY OF A TWO KEY COMMAND: D OR G */ CHAR *NOTE; /* ONE-SHOT MESSAGE FOR THE STATUS ROW */ CHAR YBUF[160]; /* THE YANKED LINE */ INT YLEN, YOK; INT REDRAW; /* 1 CURSOR ROW, 2 WHOLE SCREEN */ /* SCREEN ROWS ARE 0..23, THE STATUS ROW IS 24 */ /* SHORTEN FNAME TO WHAT FITS */ SHORTN(N) INT N; Û INT I; I = 0; WHILE (FNAME[I] && I < N) I++; FNAME[I] = 0; RETURN 0; Ý /* A MESSAGE THAT WAITS FOR A KEY */ MSG(S) CHAR *S; Û SCLR(24, 0, 40); SPS(24, 0, S); SKEY(); RETURN 0; Ý /* READ A LINE OF LOWERCASE PETSCII INTO S, ECHOED FROM COLUMN C OF ROW 24 */ GETLN(C, S, MAX) INT C, MAX; CHAR *S; Û INT N, K; N = 0; S[0] = 0; FOR (;;) Û K = SKEY(); IF (K == 13) BREAK; IF (K == 3) Û N = 0; S[0] = 0; BREAK; Ý IF (K == 20) Û IF (N > 0) Û N--; S[N] = 0; Ý Ý ELSE Û IF (K >= 0XC1 && K <= 0XDA) K = K - 0X80; IF (K >= 32 && K != 127 && N < MAX) Û S[N] = K; N++; S[N] = 0; Ý Ý SCLR(24, C, MAX); SPS(24, C, S); Ý RETURN N; Ý /* A YES/NO QUESTION ON THE STATUS ROW: 1 FOR Y, 0 FOR N OR RUN/STOP */ ASKYN(Q) CHAR *Q; Û INT K; SCLR(24, 0, 40); SPS(24, 0, Q); FOR (;;) Û K = SKEY(); IF (K == 89) RETURN 1; /* Y */ IF (K == 217) RETURN 1; /* Ù */ IF (K == 78) RETURN 0; /* N */ IF (K == 206) RETURN 0; /* Î */ IF (K == 3) RETURN 0; Ý Ý /* ASK WHICH FILE TO OPEN */ ASKNAME() Û SCLR(24, 0, 40); SPS(24, 0, "FILE: "); IF (GETLN(6, FNAME, 20) == 0) FNAME[0] = 0; RETURN 0; Ý /* THE DRIVE'S ERROR CHANNEL (ALREADY OPEN AS FILE 15): THE TWO DIGIT CODE OF THE LAST COMMAND. 0-19 ARE FINE ("00, OK"), 62 IS FILE NOT FOUND. OPEN() OF A MISSING FILE SUCCEEDS ON A CBM DRIVE - ONLY THE ERROR CHANNEL TELLS. DO NOT CLOSE 15 WHILE ANOTHER FILE IS OPEN: CLOSING THE COMMAND CHANNEL CLOSES EVERY FILE ON THE DRIVE. */ DOSST() Û INT CH, N, D; N = 0; D = 0; FOR (;;) Û CH = GETC(15); IF (CH < 0) BREAK; IF (CH == 13) BREAK; IF (D < 2) Û IF (CH >= 48 && CH <= 57) Û N = N * 10 + CH - 48; D++; Ý Ý Ý IF (D < 2) RETURN 99; RETURN N; Ý INT SPLIT; /* A LONG LINE WAS SPLIT ON LOAD */ /* 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 */ LOADF() Û INT F, CH, PREV, C, COL; CHAR ON[28]; INT I; IF (FNAME[0] == 0) RETURN 1; I = 0; WHILE (FNAME[I] && I < 20) Û ON[I] = FNAME[I]; I++; Ý ON[I++] = 44; /* , */ ON[I++] = 83; /* Ó (PETSCII, NOT ASCII) */ ON[I++] = 44; /* , */ ON[I++] = 82; /* Ò */ ON[I] = 0; IF (OPEN(15, 8, 15, "") == 0) RETURN 0; /* STATUS CHANNEL FIRST */ F = OPEN(5, 8, 2, ON); IF (F == 0) Û CLOSE(15); RETURN 0; Ý IF (DOSST() >= 20) Û CLOSE(5); CLOSE(15); RETURN 0; Ý BLEN = 0; PREV = 0; COL = 0; SPLIT = 0; FOR (;;) Û CH = GETC(5); IF (CH < 0) BREAK; /* END OF FILE */ IF (CH == 26) BREAK; IF (CH == 10 && PREV == 13) Û /* CRLF SEEN AS TWO BYTES */ PREV = CH; CONTINUE; Ý IF (CH == 13) C = 13; ELSE IF (CH == 10) C = 13; ELSE C = CH; IF (C != 13 && COL >= 80) Û /* 80 COLUMNS AT MOST */ IF (BLEN < 18999) BFR[BLEN++] = 13; COL = 0; SPLIT = 1; Ý IF (BLEN < 18999) BFR[BLEN++] = C; IF (C == 13) COL = 0; ELSE COL++; PREV = CH; Ý CLOSE(5); CLOSE(15); /* LAST: IT CLOSES EVERYTHING */ RETURN 1; Ý /* FIND THE START OF EVERY LINE */ BUILDIDX() Û UNSIGNED I; NLINES = 1; LPTR[0] = 0; FOR (I = 0; I < BLEN; I++) Û IF (BFR[I] == 13) Û IF (NLINES < 599) Û LPTR[NLINES] = I + 1; NLINES++; Ý Ý Ý RETURN 0; Ý /* THE ":" COMMAND LINE. RETURNS 1 WHEN THE EDITOR SHOULD QUIT */ DOSCMD() Û INT N, P, OK, QF; CHAR CMD[24]; SCLR(24, 0, 40); SPC(24, 0, 58); /* : */ N = GETLN(1, CMD, 20); SCLR(24, 0, 40); IF (N == 0) RETURN 0; IF (CMD[0] == 81) Û /* Q, Q! */ IF (CMD[1] == 33) RETURN 1; IF (DIRTY) Û NOTE = "UNSAVED - :Q! QUITS"; RETURN 0; Ý RETURN 1; Ý OK = 0; QF = 0; P = 1; IF (CMD[0] == 87) OK = 1; /* W, WQ, W NAME */ IF (CMD[0] == 88) Û /* X */ OK = 1; QF = 1; Ý IF (OK == 0) Û NOTE = "UNKNOWN COMMAND"; RETURN 0; Ý IF (CMD[0] == 87 && CMD[1] == 81) Û QF = 1; P = 2; Ý WHILE (CMD[P] == 32) P++; IF (CMD[P]) Û N = 0; WHILE (CMD[P] && N < 20) 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 K, OK, P; SINIT(); MLINIT(); /* ÍÌ HELPERS TO $C000: BEFORE MALLOC */ BFR = MALLOC(19000); IF (BFR == 0) Û SPS(0, 0, "OUT OF MEMORY"); SKEY(); RETURN; Ý BLEN = 0; NLINES = 1; LPTR[0] = 0; CY = 0; CX = 0; TOP = 0; LEFTCOL = 0; FNAME[0] = 0; MODE = 0; DIRTY = 0; PEND = 0; NOTE = 0; YLEN = 0; YOK = 0; SPLIT = 0; REDRAW = 2; FOR (;;) Û /* A FILE TO EDIT */ ASKNAME(); SHORTN(20); 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 == 0) Û /* KEEP THE NAME: :W CREATES IT */ NLINES = 1; BLEN = 0; NOTE = "NEW FILE"; Ý IF (SPLIT) NOTE = "LONG LINES SPLIT AT 80"; FOR (;;) Û SCROLL(); DALL(); K = SKEY(); NOTE = 0; IF (MODE) Û /* ---- INSERT MODE */ IF (K == 3) Û /* ÒÕÎ/ÓÔÏÐ = ÅSC */ MODE = 0; IF (CX > 0) CX--; Ý ELSE IF (K == 13) Û NEWLINE(); Ý ELSE IF (K == 20) Û BKSPACE(); Ý ELSE IF (ARROW(K)) Û CX = CX; Ý ELSE IF (K >= 32 && K < 128) Û INSCHAR(K); Ý ELSE IF (K >= 161) Û INSCHAR(K); Ý CONTINUE; Ý IF (PEND) Û /* ---- SECOND KEY OF DD / GG */ P = PEND; PEND = 0; IF (P == 68 && K == 68) DELLINE(); IF (P == 71 && K == 71) Û CY = 0; CX = 0; Ý CLAMPX(); CONTINUE; Ý IF (ARROW(K)) Û CX = CX; Ý ELSE IF (K == 72) Û /* H */ IF (CX > 0) CX--; Ý ELSE IF (K == 76) Û /* L */ CX++; CLAMPX(); Ý ELSE IF (K == 74) Û /* J */ ARROW(17); Ý ELSE IF (K == 75) Û /* K */ ARROW(145); Ý ELSE IF (K == 48) Û /* 0 */ CX = 0; Ý ELSE IF (K == 36) Û /* $ */ CX = LINEND(CY) - LPTR[CY]; Ý ELSE IF (K == 199) Û /* Ç */ CY = NLINES - 1; CX = 0; Ý ELSE IF (K == 68) Û /* D.. */ PEND = 68; Ý ELSE IF (K == 71) Û /* G.. */ PEND = 71; Ý ELSE IF (K == 88) Û /* X */ DELCHAR(); CLAMPX(); Ý ELSE IF (K == 80) Û /* P */ PASTE(); Ý ELSE IF (K == 73) Û /* I */ MODE = 1; Ý ELSE IF (K == 65) Û /* A */ CX++; CLAMPX(); MODE = 1; Ý ELSE IF (K == 193) Û /* Á */ CX = LINEND(CY) - LPTR[CY]; MODE = 1; Ý ELSE IF (K == 201) Û /* É */ CX = 0; MODE = 1; Ý ELSE IF (K == 79) Û /* O */ CX = LINEND(CY) - LPTR[CY]; NEWLINE(); MODE = 1; Ý ELSE IF (K == 207) Û /* Ï */ CX = 0; NEWLINE(); CY--; MODE = 1; Ý ELSE IF (K == 58) Û /* : */ IF (DOSCMD() == 1) Û SDONE(); RETURN; Ý Ý Ý Ý \ No newline at end of file diff --git a/vim/vml.bin b/vim/vml.bin new file mode 100644 index 0000000000000000000000000000000000000000..f24a5981c07b98a1df937bd4934e385903c570ad GIT binary patch literal 660 zcmX|<&r2Io5XYa}#Hs}ig~)o_?z)>FQ1sA)M~^MLN$fAHO`^FhG*_YM(aRJsLUQP- z2utWlBP?tV9s{YoEJ-eXe}ac5G?%gzN-ew4PPFy%zVl|@n>X{Br)*v{7TS!zHwTF; z8K2tQP{e0d1|=-cK^aR~*pabp=g8i%^RUxopf_V+oNfp6GV0Yd=rw6%48#xIhFyxL zVY&GSQdsO3ur%E8_8itKL)fDwlCfBwI>SBeVVwahk)JVTumeI6BP|>m@&~Uf=O2lvrQcChO zZC-m1M!Tb;kVYlypejL`3Z-UlYqkm<>+OX?1g0khR3bn($%W}z#YxrmqeSR1z8pMk zseQ$hno7S@i;WMx#lAzj?#=frq>4A)U$05y^?16j_rw=W_0!(nPc8k)VO_6KdY5rf z-6;;5D{;`>I~)u*#=&&s94yqkDjGwr_DDv}wm_57ATd6g!R%2>t?L~oew|vueP8np zhuyjc;yab9?iHF zP2do&I@y=LUCxT1zW-*c zvejgj#>}~TlHdfikvum~mL?X4$15D8<5f=KcH)M2BiUZM`FrjPhtOT+5LNyHECMi| literal 0 HcmV?d00001 diff --git a/vim/vml.c b/vim/vml.c index 67d61ac..c0aea0c 100644 --- a/vim/vml.c +++ b/vim/vml.c @@ -1,46 +1,205 @@ -/* vml.c - GENERATED by build-ml.ps1 from vrow.s. Do not edit by hand. +/* vml.c - GENERATED by build-ml.ps1 from vml.s and vmlwrap.c. Do not edit. * - * The machine code row painter, kept as a byte array and called in place - * through sys(). It is position independent; its parameters go through zero - * page scratch $22-$28, which Power C leaves to ML routines. + * The machine code helpers as a byte array. mlinit() copies them to $C000. */ -char mlcode[76] = { - 165, 40, 9, 32, 160, 39, 145, 37, 136, 16, 251, 160, - 0, 196, 36, 240, 45, 177, 34, 201, 32, 176, 2, 169, - 32, 201, 192, 144, 4, 233, 128, 176, 22, 201, 160, 144, - 4, 233, 64, 176, 14, 201, 96, 144, 4, 233, 32, 176, - 6, 201, 64, 144, 2, 233, 64, 5, 40, 145, 37, 200, - 208, 207, 165, 39, 201, 255, 240, 7, 168, 177, 37, 73, - 128, 145, 37, 96 +#define MLBMOVE 0xc161 +#define MLKPOP 0xc237 +#define MLLADJ 0xc1e3 +#define MLROW 0xc102 +#define MLSCRN 0xc000 +#define MLSIZE 660 + +char mlcode[660] = { + 32, 86, 194, 162, 15, 189, 0, 196, 149, 75, 202, 16, + 248, 24, 165, 81, 101, 89, 133, 91, 165, 82, 105, 0, + 133, 92, 165, 91, 10, 133, 93, 165, 92, 42, 133, 94, + 24, 165, 93, 101, 77, 133, 93, 165, 94, 101, 78, 133, + 94, 169, 0, 133, 37, 169, 4, 133, 38, 166, 89, 240, + 14, 24, 165, 37, 105, 40, 133, 37, 144, 2, 230, 38, + 202, 208, 242, 165, 89, 133, 95, 169, 0, 133, 40, 133, + 36, 169, 255, 133, 39, 165, 91, 197, 79, 165, 92, 229, + 80, 176, 114, 160, 0, 177, 93, 133, 251, 200, 177, 93, + 133, 252, 24, 165, 91, 105, 1, 170, 165, 92, 105, 0, + 228, 79, 208, 15, 197, 80, 208, 11, 165, 83, 133, 41, + 165, 84, 133, 42, 76, 155, 192, 160, 2, 177, 93, 56, + 233, 1, 133, 41, 200, 177, 93, 233, 0, 133, 42, 24, + 165, 251, 101, 85, 133, 251, 165, 252, 101, 86, 133, 252, + 56, 165, 41, 229, 251, 170, 165, 42, 229, 252, 144, 10, + 208, 4, 224, 41, 144, 2, 162, 40, 134, 36, 24, 165, + 75, 101, 251, 133, 34, 165, 76, 101, 252, 133, 35, 165, + 95, 197, 87, 208, 4, 165, 88, 133, 39, 32, 21, 193, + 165, 95, 197, 90, 176, 33, 230, 95, 230, 91, 208, 2, + 230, 92, 24, 165, 93, 105, 2, 133, 93, 144, 2, 230, + 94, 24, 165, 37, 105, 40, 133, 37, 144, 2, 230, 38, + 76, 79, 192, 76, 117, 194, 32, 86, 194, 162, 6, 189, + 0, 196, 149, 34, 202, 16, 248, 32, 21, 193, 76, 117, + 194, 165, 40, 9, 32, 160, 39, 145, 37, 136, 16, 251, + 160, 0, 196, 36, 240, 45, 177, 34, 201, 32, 176, 2, + 169, 32, 201, 192, 144, 4, 233, 128, 176, 22, 201, 160, + 144, 4, 233, 64, 176, 14, 201, 96, 144, 4, 233, 32, + 176, 6, 201, 64, 144, 2, 233, 64, 5, 40, 145, 37, + 200, 208, 207, 165, 39, 201, 40, 176, 7, 168, 177, 37, + 73, 128, 145, 37, 96, 32, 86, 194, 173, 0, 196, 133, + 34, 173, 1, 196, 133, 35, 173, 2, 196, 133, 37, 173, + 3, 196, 133, 38, 173, 4, 196, 133, 75, 173, 5, 196, + 133, 76, 165, 37, 197, 34, 165, 38, 229, 35, 144, 52, + 24, 165, 35, 101, 76, 133, 35, 24, 165, 38, 101, 76, + 133, 38, 164, 75, 240, 9, 136, 177, 34, 145, 37, 192, + 0, 208, 247, 166, 76, 240, 18, 198, 35, 198, 38, 160, + 0, 136, 177, 34, 145, 37, 192, 0, 208, 247, 202, 208, + 238, 76, 117, 194, 160, 0, 166, 76, 240, 14, 177, 34, + 145, 37, 200, 208, 249, 230, 35, 230, 38, 202, 208, 242, + 166, 75, 240, 229, 177, 34, 145, 37, 200, 202, 208, 248, + 76, 117, 194, 32, 86, 194, 162, 5, 189, 0, 196, 149, + 75, 202, 16, 248, 165, 75, 133, 34, 165, 76, 133, 35, + 165, 77, 133, 75, 165, 78, 133, 76, 165, 79, 133, 77, + 165, 80, 133, 78, 165, 75, 5, 76, 240, 38, 160, 0, + 24, 177, 34, 101, 77, 145, 34, 200, 177, 34, 101, 78, + 145, 34, 24, 165, 34, 105, 2, 133, 34, 144, 2, 230, + 35, 165, 75, 208, 2, 198, 76, 198, 75, 76, 8, 194, + 76, 117, 194, 120, 165, 198, 240, 22, 162, 0, 173, 119, + 2, 72, 189, 120, 2, 157, 119, 2, 232, 224, 9, 208, + 245, 198, 198, 104, 88, 96, 88, 169, 0, 96, 162, 8, + 181, 34, 157, 16, 196, 202, 16, 248, 162, 21, 181, 75, + 157, 25, 196, 202, 16, 248, 165, 251, 141, 47, 196, 165, + 252, 141, 48, 196, 96, 162, 8, 189, 16, 196, 149, 34, + 202, 16, 248, 162, 21, 189, 25, 196, 149, 75, 202, 16, + 248, 173, 47, 196, 133, 251, 173, 48, 196, 133, 252, 96 }; -/* nothing to install: the code runs where the array sits */ +/* vmlwrap.c - the C side of the machine code helpers. build-ml.ps1 appends + * this to the generated byte array to make vml.c; edit it here. + * + * Parameters go through a block at $c400 (not $cf00: the C stack lives just below $d000), then sys() jumps to the entry. + * On the c64 sys() takes no bank argument: sys(addr, &a, &x, &y). + */ + +extern char *bfr; +extern unsigned blen; +extern unsigned lptr[]; +extern int nlines, top, leftcol; + +/* move the code to $c000 and keep power c's heap and stack below it. + must run before malloc() */ mlinit() { + char *d; + int i; + + highmem(0xc000); + d = (char *) 0xc000; + for (i = 0; i < MLSIZE; i++) d[i] = mlcode[i]; return 0; } -/* paint the screen row at address dst: len bytes from src, cursor at column cur - (255 = none), rev = 128 for an all reverse row */ +/* call an entry, return what it left in A */ +mlgo(addr) +unsigned addr; +{ + char a, x, y; + + a = 0; + x = 0; + y = 0; + sys(addr, &a, &x, &y); + return a & 255; +} + +/* The wrappers fill the parameter block through one pointer instead of + calling a helper per field: every power c function call costs a lot, and + mlscr runs on every key. Words are stored low byte first. */ + +/* paint one row at screen address dst: len bytes from src, cursor at column + cur (255 = none), rev = 128 for an all reverse row */ mlrow(src, len, dst, cur, rev) unsigned src, dst; int len, cur, rev; { char *p; - char a, x, y; - p = (char *) 0x22; - p[0] = src & 255; + p = (char *) 0xc400; + p[0] = src; p[1] = src >> 8; p[2] = len; - p[3] = dst & 255; + p[3] = dst; p[4] = dst >> 8; p[5] = cur; p[6] = rev; - a = 0; - x = 0; - y = 0; - sys((unsigned) mlcode, &a, &x, &y); /* c64 form: no bank argument */ + mlgo(MLROW); return 0; } + +/* paint screen rows r0..r1 from the buffer; the cursor sits at screen row + crow (255 = none), column ccol */ +mlscr(r0, r1, crow, ccol) +int r0, r1, crow, ccol; +{ + char *p; + unsigned v; + + p = (char *) 0xc400; + v = (unsigned) bfr; + p[0] = v; + p[1] = v >> 8; + v = (unsigned) lptr; + p[2] = v; + p[3] = v >> 8; + p[4] = nlines; + p[5] = nlines >> 8; + p[6] = top; + p[7] = top >> 8; + p[8] = blen; + p[9] = blen >> 8; + p[10] = leftcol; + p[11] = leftcol >> 8; + p[12] = crow; + p[13] = ccol; + p[14] = r0; + p[15] = r1; + mlgo(MLSCRN); + return 0; +} + +/* memmove: n bytes from 'from' to 'to', overlap safe */ +mlmove(from, to, n) +unsigned from, to, n; +{ + char *p; + + if (n == 0) return 0; + p = (char *) 0xc400; + p[0] = from; + p[1] = from >> 8; + p[2] = to; + p[3] = to >> 8; + p[4] = n; + p[5] = n >> 8; + mlgo(MLBMOVE); + return 0; +} + +/* add d to n words starting at address a (line index fix-ups) */ +mladj(a, n, d) +unsigned a, n, d; +{ + char *p; + + if (n == 0) return 0; + p = (char *) 0xc400; + p[0] = a; + p[1] = a >> 8; + p[2] = n; + p[3] = n >> 8; + p[4] = d; + p[5] = d >> 8; + mlgo(MLLADJ); + return 0; +} + +/* next key from the kernal buffer, or 0 */ +mlkey() +{ + return mlgo(MLKPOP); +} + diff --git a/vim/vml.pet b/vim/vml.pet index ef693a7..5e573ab 100644 --- a/vim/vml.pet +++ b/vim/vml.pet @@ -1 +1 @@ -/* VML.C - ÇÅÎÅÒÁÔÅÄ BY BUILD-ML.PS1 FROM VROW.S. ÄO NOT EDIT BY HAND. * * ÔHE MACHINE CODE ROW PAINTER, KEPT AS A BYTE ARRAY AND CALLED IN PLACE * THROUGH SYS(). ÉT IS POSITION INDEPENDENT; ITS PARAMETERS GO THROUGH ZERO * PAGE SCRATCH $22-$28, WHICH ÐOWER à LEAVES TO ÍÌ ROUTINES. */ CHAR MLCODE[76] = Û 165, 40, 9, 32, 160, 39, 145, 37, 136, 16, 251, 160, 0, 196, 36, 240, 45, 177, 34, 201, 32, 176, 2, 169, 32, 201, 192, 144, 4, 233, 128, 176, 22, 201, 160, 144, 4, 233, 64, 176, 14, 201, 96, 144, 4, 233, 32, 176, 6, 201, 64, 144, 2, 233, 64, 5, 40, 145, 37, 200, 208, 207, 165, 39, 201, 255, 240, 7, 168, 177, 37, 73, 128, 145, 37, 96 Ý; /* NOTHING TO INSTALL: THE CODE RUNS WHERE THE ARRAY SITS */ MLINIT() Û RETURN 0; Ý /* PAINT THE SCREEN ROW AT ADDRESS DST: LEN BYTES FROM SRC, CURSOR AT COLUMN CUR (255 = NONE), REV = 128 FOR AN ALL REVERSE ROW */ MLROW(SRC, LEN, DST, CUR, REV) UNSIGNED SRC, DST; INT LEN, CUR, REV; Û CHAR *P; CHAR A, X, Y; P = (CHAR *) 0X22; P[0] = SRC & 255; P[1] = SRC >> 8; P[2] = LEN; P[3] = DST & 255; P[4] = DST >> 8; P[5] = CUR; P[6] = REV; A = 0; X = 0; Y = 0; SYS((UNSIGNED) MLCODE, &A, &X, &Y); /* C64 FORM: NO BANK ARGUMENT */ RETURN 0; Ý \ No newline at end of file +/* VML.C - ÇÅÎÅÒÁÔÅÄ BY BUILD-ML.PS1 FROM VML.S AND VMLWRAP.C. ÄO NOT EDIT. * * ÔHE MACHINE CODE HELPERS AS A BYTE ARRAY. MLINIT() COPIES THEM TO $Ã000. */ #DEFINE ÍÌÂÍÏÖÅ 0XC161 #DEFINE ÍÌËÐÏÐ 0XC237 #DEFINE ÍÌÌÁÄÊ 0XC1E3 #DEFINE ÍÌÒÏ× 0XC102 #DEFINE ÍÌÓÃÒÎ 0XC000 #DEFINE ÍÌÓÉÚÅ 660 CHAR MLCODE[660] = Û 32, 86, 194, 162, 15, 189, 0, 196, 149, 75, 202, 16, 248, 24, 165, 81, 101, 89, 133, 91, 165, 82, 105, 0, 133, 92, 165, 91, 10, 133, 93, 165, 92, 42, 133, 94, 24, 165, 93, 101, 77, 133, 93, 165, 94, 101, 78, 133, 94, 169, 0, 133, 37, 169, 4, 133, 38, 166, 89, 240, 14, 24, 165, 37, 105, 40, 133, 37, 144, 2, 230, 38, 202, 208, 242, 165, 89, 133, 95, 169, 0, 133, 40, 133, 36, 169, 255, 133, 39, 165, 91, 197, 79, 165, 92, 229, 80, 176, 114, 160, 0, 177, 93, 133, 251, 200, 177, 93, 133, 252, 24, 165, 91, 105, 1, 170, 165, 92, 105, 0, 228, 79, 208, 15, 197, 80, 208, 11, 165, 83, 133, 41, 165, 84, 133, 42, 76, 155, 192, 160, 2, 177, 93, 56, 233, 1, 133, 41, 200, 177, 93, 233, 0, 133, 42, 24, 165, 251, 101, 85, 133, 251, 165, 252, 101, 86, 133, 252, 56, 165, 41, 229, 251, 170, 165, 42, 229, 252, 144, 10, 208, 4, 224, 41, 144, 2, 162, 40, 134, 36, 24, 165, 75, 101, 251, 133, 34, 165, 76, 101, 252, 133, 35, 165, 95, 197, 87, 208, 4, 165, 88, 133, 39, 32, 21, 193, 165, 95, 197, 90, 176, 33, 230, 95, 230, 91, 208, 2, 230, 92, 24, 165, 93, 105, 2, 133, 93, 144, 2, 230, 94, 24, 165, 37, 105, 40, 133, 37, 144, 2, 230, 38, 76, 79, 192, 76, 117, 194, 32, 86, 194, 162, 6, 189, 0, 196, 149, 34, 202, 16, 248, 32, 21, 193, 76, 117, 194, 165, 40, 9, 32, 160, 39, 145, 37, 136, 16, 251, 160, 0, 196, 36, 240, 45, 177, 34, 201, 32, 176, 2, 169, 32, 201, 192, 144, 4, 233, 128, 176, 22, 201, 160, 144, 4, 233, 64, 176, 14, 201, 96, 144, 4, 233, 32, 176, 6, 201, 64, 144, 2, 233, 64, 5, 40, 145, 37, 200, 208, 207, 165, 39, 201, 40, 176, 7, 168, 177, 37, 73, 128, 145, 37, 96, 32, 86, 194, 173, 0, 196, 133, 34, 173, 1, 196, 133, 35, 173, 2, 196, 133, 37, 173, 3, 196, 133, 38, 173, 4, 196, 133, 75, 173, 5, 196, 133, 76, 165, 37, 197, 34, 165, 38, 229, 35, 144, 52, 24, 165, 35, 101, 76, 133, 35, 24, 165, 38, 101, 76, 133, 38, 164, 75, 240, 9, 136, 177, 34, 145, 37, 192, 0, 208, 247, 166, 76, 240, 18, 198, 35, 198, 38, 160, 0, 136, 177, 34, 145, 37, 192, 0, 208, 247, 202, 208, 238, 76, 117, 194, 160, 0, 166, 76, 240, 14, 177, 34, 145, 37, 200, 208, 249, 230, 35, 230, 38, 202, 208, 242, 166, 75, 240, 229, 177, 34, 145, 37, 200, 202, 208, 248, 76, 117, 194, 32, 86, 194, 162, 5, 189, 0, 196, 149, 75, 202, 16, 248, 165, 75, 133, 34, 165, 76, 133, 35, 165, 77, 133, 75, 165, 78, 133, 76, 165, 79, 133, 77, 165, 80, 133, 78, 165, 75, 5, 76, 240, 38, 160, 0, 24, 177, 34, 101, 77, 145, 34, 200, 177, 34, 101, 78, 145, 34, 24, 165, 34, 105, 2, 133, 34, 144, 2, 230, 35, 165, 75, 208, 2, 198, 76, 198, 75, 76, 8, 194, 76, 117, 194, 120, 165, 198, 240, 22, 162, 0, 173, 119, 2, 72, 189, 120, 2, 157, 119, 2, 232, 224, 9, 208, 245, 198, 198, 104, 88, 96, 88, 169, 0, 96, 162, 8, 181, 34, 157, 16, 196, 202, 16, 248, 162, 21, 181, 75, 157, 25, 196, 202, 16, 248, 165, 251, 141, 47, 196, 165, 252, 141, 48, 196, 96, 162, 8, 189, 16, 196, 149, 34, 202, 16, 248, 162, 21, 189, 25, 196, 149, 75, 202, 16, 248, 173, 47, 196, 133, 251, 173, 48, 196, 133, 252, 96 Ý; /* VMLWRAP.C - THE à SIDE OF THE MACHINE CODE HELPERS. BUILD-ML.PS1 APPENDS * THIS TO THE GENERATED BYTE ARRAY TO MAKE VML.C; EDIT IT HERE. * * ÐARAMETERS GO THROUGH A BLOCK AT $C400 (NOT $CF00: THE à STACK LIVES JUST BELOW $D000), THEN SYS() JUMPS TO THE ENTRY. * ÏN THE C64 SYS() TAKES NO BANK ARGUMENT: SYS(ADDR, &A, &X, &Y). */ EXTERN CHAR *BFR; EXTERN UNSIGNED BLEN; EXTERN UNSIGNED LPTR[]; EXTERN INT NLINES, TOP, LEFTCOL; /* MOVE THE CODE TO $C000 AND KEEP POWER C'S HEAP AND STACK BELOW IT. MUST RUN BEFORE MALLOC() */ MLINIT() Û CHAR *D; INT I; HIGHMEM(0XC000); D = (CHAR *) 0XC000; FOR (I = 0; I < ÍÌÓÉÚÅ; I++) D[I] = MLCODE[I]; RETURN 0; Ý /* CALL AN ENTRY, RETURN WHAT IT LEFT IN Á */ MLGO(ADDR) UNSIGNED ADDR; Û CHAR A, X, Y; A = 0; X = 0; Y = 0; SYS(ADDR, &A, &X, &Y); RETURN A & 255; Ý /* ÔHE WRAPPERS FILL THE PARAMETER BLOCK THROUGH ONE POINTER INSTEAD OF CALLING A HELPER PER FIELD: EVERY POWER C FUNCTION CALL COSTS A LOT, AND MLSCR RUNS ON EVERY KEY. ×ORDS ARE STORED LOW BYTE FIRST. */ /* PAINT ONE ROW AT SCREEN ADDRESS DST: LEN BYTES FROM SRC, CURSOR AT COLUMN CUR (255 = NONE), REV = 128 FOR AN ALL REVERSE ROW */ MLROW(SRC, LEN, DST, CUR, REV) UNSIGNED SRC, DST; INT LEN, CUR, REV; Û CHAR *P; P = (CHAR *) 0XC400; P[0] = SRC; P[1] = SRC >> 8; P[2] = LEN; P[3] = DST; P[4] = DST >> 8; P[5] = CUR; P[6] = REV; MLGO(ÍÌÒÏ×); RETURN 0; Ý /* PAINT SCREEN ROWS R0..R1 FROM THE BUFFER; THE CURSOR SITS AT SCREEN ROW CROW (255 = NONE), COLUMN CCOL */ MLSCR(R0, R1, CROW, CCOL) INT R0, R1, CROW, CCOL; Û CHAR *P; UNSIGNED V; P = (CHAR *) 0XC400; V = (UNSIGNED) BFR; P[0] = V; P[1] = V >> 8; V = (UNSIGNED) LPTR; P[2] = V; P[3] = V >> 8; P[4] = NLINES; P[5] = NLINES >> 8; P[6] = TOP; P[7] = TOP >> 8; P[8] = BLEN; P[9] = BLEN >> 8; P[10] = LEFTCOL; P[11] = LEFTCOL >> 8; P[12] = CROW; P[13] = CCOL; P[14] = R0; P[15] = R1; MLGO(ÍÌÓÃÒÎ); RETURN 0; Ý /* MEMMOVE: N BYTES FROM 'FROM' TO 'TO', OVERLAP SAFE */ MLMOVE(FROM, TO, N) UNSIGNED FROM, TO, N; Û CHAR *P; IF (N == 0) RETURN 0; P = (CHAR *) 0XC400; P[0] = FROM; P[1] = FROM >> 8; P[2] = TO; P[3] = TO >> 8; P[4] = N; P[5] = N >> 8; MLGO(ÍÌÂÍÏÖÅ); RETURN 0; Ý /* ADD D TO N WORDS STARTING AT ADDRESS A (LINE INDEX FIX-UPS) */ MLADJ(A, N, D) UNSIGNED A, N, D; Û CHAR *P; IF (N == 0) RETURN 0; P = (CHAR *) 0XC400; P[0] = A; P[1] = A >> 8; P[2] = N; P[3] = N >> 8; P[4] = D; P[5] = D >> 8; MLGO(ÍÌÌÁÄÊ); RETURN 0; Ý /* NEXT KEY FROM THE KERNAL BUFFER, OR 0 */ MLKEY() Û RETURN MLGO(ÍÌËÐÏÐ); Ý \ No newline at end of file diff --git a/vim/vml.s b/vim/vml.s new file mode 100644 index 0000000..57ff091 --- /dev/null +++ b/vim/vml.s @@ -0,0 +1,439 @@ +; vml.s - machine code helpers for the c64 vim clone (called from power c). +; +; Assembled for $C000. vml.c holds the bytes; mlinit() calls highmem($C000) +; so power c keeps its heap and stack below, then copies the code up here. +; Parameters are written by C into a block at $C400 (PB) before each call; +; entry addresses come from the ld65 label file (see build-ml.ps1). +; +; Zero page: 34-42 ($22-$2A), 75-96 ($4B-$60) and 251-252 ($FB-$FC) are the +; temporaries power c leaves to machine language - but sys() itself is using +; some of them while our code runs (it keeps where to store A/X/Y back), so +; every entry that touches zero page saves all three ranges first (zsave) and +; puts them back before it returns (zrest). Without that, sys() wrote X/Y +; through our screen pointer and then jumped into zero page. +; +; Entries +; scrn paint screen rows r0..r1 straight from the buffer and line index +; row paint one row from a byte string (the status row) +; bmove overlapping block move (memmove) +; ladj add a 16 bit delta to n consecutive words (line index fix-up) +; kpop take one key from the kernal keyboard buffer, irqs masked: A = key or 0 + +SAVE = $C410 ; zsave's copy of the zero page we use +PB = $C400 ; above the code; NOT $CF00 - power c's stack lives under $D000 + +; paint's inputs +src = $22 ; 2 text to show (petscii) +len = $24 ; 1 bytes of it, 0..40 +dst = $25 ; 2 screen address of the row +cur = $27 ; 1 column to invert, $FF = none +rev = $28 ; 1 $00 normal, $80 whole row reverse +endp = $29 ; 2 scrn: end offset of the line + +; scrn's copy of its parameter block: PB+0..PB+15 -> $4B..$5A +bfr = $4B ; 2 buffer address +lptr = $4D ; 2 address of lptr[0] +nl = $4F ; 2 number of lines +top = $51 ; 2 first line on screen +blen = $53 ; 2 bytes in buffer +lcol = $55 ; 2 horizontal scroll +crow = $57 ; 1 screen row of the cursor ($FF none) +ccol = $58 ; 1 screen column of the cursor +r0 = $59 ; 1 first row to paint +r1 = $5A ; 1 last row to paint (inclusive) +ln = $5B ; 2 line being painted +lp = $5D ; 2 -> lptr[ln] +rowc = $5F ; 1 screen row being painted +st = $FB ; 2 start offset of the visible text + + .segment "CODE" + +; ---------------------------------------------------------------- scrn +scrn: jsr zsave + ldx #15 +@cp: lda PB,x + sta bfr,x + dex + bpl @cp + + clc ; ln = top + r0 + lda top + adc r0 + sta ln + lda top+1 + adc #0 + sta ln+1 + + lda ln ; lp = lptr + 2*ln + asl a + sta lp + lda ln+1 + rol a + sta lp+1 + clc + lda lp + adc lptr + sta lp + lda lp+1 + adc lptr+1 + sta lp+1 + + lda #0 ; dst = $0400 + 40*r0 + sta dst + lda #4 + sta dst+1 + ldx r0 + beq @d1 +@dl: clc + lda dst + adc #40 + sta dst + bcc @dn + inc dst+1 +@dn: dex + bne @dl +@d1: lda r0 + sta rowc + +@row: lda #0 + sta rev + sta len + lda #$FF + sta cur + + lda ln ; past the last line: a blank row + cmp nl + lda ln+1 + sbc nl+1 + bcs @paint + + ldy #0 ; st = lptr[ln] + lda (lp),y + sta st + iny + lda (lp),y + sta st+1 + + clc ; is ln the last line? + lda ln + adc #1 + tax + lda ln+1 + adc #0 + cpx nl + bne @mid + cmp nl+1 + bne @mid + lda blen ; last line ends at blen + sta endp + lda blen+1 + sta endp+1 + jmp @gotend +@mid: ldy #2 ; else at lptr[ln+1] - 1 (before the $0d) + lda (lp),y + sec + sbc #1 + sta endp + iny + lda (lp),y + sbc #0 + sta endp+1 + +@gotend: + clc ; st += lcol + lda st + adc lcol + sta st + lda st+1 + adc lcol+1 + sta st+1 + + sec ; len = min(40, end - st), 0 if negative + lda endp + sbc st + tax + lda endp+1 + sbc st+1 + bcc @src ; scrolled past the end of this line + bne @full + cpx #41 + bcc @setn +@full: ldx #40 +@setn: stx len + +@src: clc ; src = bfr + st + lda bfr + adc st + sta src + lda bfr+1 + adc st+1 + sta src+1 + + lda rowc ; the cursor row? + cmp crow + bne @paint + lda ccol + sta cur + +@paint: jsr paint + + lda rowc ; next row, until r1 is done + cmp r1 + bcs @done + inc rowc + inc ln + bne @l1 + inc ln+1 +@l1: clc + lda lp + adc #2 + sta lp + bcc @l2 + inc lp+1 +@l2: clc + lda dst + adc #40 + sta dst + bcc @l3 + inc dst+1 +@l3: jmp @row +@done: jmp zrest + +; ---------------------------------------------------------------- row +; PB+0 src, PB+2 len, PB+3 dst, PB+5 cur, PB+6 rev +row: jsr zsave + ldx #6 +@cp: lda PB,x + sta src,x + dex + bpl @cp + jsr paint + jmp zrest + +; ---------------------------------------------------------------- paint +; clear the row to blanks, copy len bytes converted petscii -> screen code, +; then invert the cursor cell +paint: lda rev + ora #$20 + ldy #39 +@clr: sta (dst),y + dey + bpl @clr + + ldy #0 +@cp: cpy len + beq @cur + lda (src),y + cmp #$20 + bcs @ok + lda #$20 ; control codes show as a blank +@ok: cmp #$C0 + bcc @l1 + sbc #$80 + bcs @st +@l1: cmp #$A0 + bcc @l2 + sbc #$40 + bcs @st +@l2: cmp #$60 + bcc @l3 + sbc #$20 + bcs @st +@l3: cmp #$40 + bcc @st + sbc #$40 +@st: ora rev + sta (dst),y + iny + bne @cp + +@cur: lda cur + cmp #40 + bcs @fin ; $FF (none) or off screen + tay + lda (dst),y + eor #$80 + sta (dst),y +@fin: rts + +; ---------------------------------------------------------------- bmove +; PB+0 from, PB+2 to, PB+4 count. Overlap safe. +mfrom = $22 +mto = $25 +mcnt = $4B + +bmove: jsr zsave + lda PB + sta mfrom + lda PB+1 + sta mfrom+1 + lda PB+2 + sta mto + lda PB+3 + sta mto+1 + lda PB+4 + sta mcnt + lda PB+5 + sta mcnt+1 + + lda mto ; to < from: copy upwards from the start + cmp mfrom + lda mto+1 + sbc mfrom+1 + bcc @fwd + + ; to >= from: copy downwards from the end + clc ; point both at their last whole page + lda mfrom+1 + adc mcnt+1 + sta mfrom+1 + clc + lda mto+1 + adc mcnt+1 + sta mto+1 + ldy mcnt ; the odd bytes at the top first + beq @bpg +@bodd: dey + lda (mfrom),y + sta (mto),y + cpy #0 + bne @bodd +@bpg: ldx mcnt+1 ; then whole pages, highest first + beq @out +@bnext: dec mfrom+1 + dec mto+1 + ldy #0 +@bpl: dey + lda (mfrom),y + sta (mto),y + cpy #0 + bne @bpl + dex + bne @bnext +@out: jmp zrest + +@fwd: ldy #0 + ldx mcnt+1 ; whole pages + beq @fodd +@fpg: lda (mfrom),y + sta (mto),y + iny + bne @fpg + inc mfrom+1 + inc mto+1 + dex + bne @fpg +@fodd: ldx mcnt ; then the rest + beq @out +@fo: lda (mfrom),y + sta (mto),y + iny + dex + bne @fo + jmp zrest + +; ---------------------------------------------------------------- ladj +; PB+0 address of the first word, PB+2 how many, PB+4 delta +aptr = $22 +acnt = $4B +adel = $4D + +ladj: jsr zsave + ldx #5 +@cp: lda PB,x + sta $4B,x + dex + bpl @cp + lda $4B ; the address goes to $22 for (zp),y + sta aptr + lda $4C + sta aptr+1 + lda $4D ; count to acnt, delta to adel + sta acnt + lda $4E + sta acnt+1 + lda $4F + sta adel + lda $50 + sta adel+1 + +@lp: lda acnt + ora acnt+1 + beq @done + ldy #0 + clc + lda (aptr),y + adc adel + sta (aptr),y + iny + lda (aptr),y + adc adel+1 + sta (aptr),y + clc + lda aptr + adc #2 + sta aptr + bcc @nc + inc aptr+1 +@nc: lda acnt + bne @dc + dec acnt+1 +@dc: dec acnt + jmp @lp +@done: jmp zrest + +; ---------------------------------------------------------------- kpop +kpop: sei + lda $C6 + beq @none + ldx #0 + lda $0277 + pha +@sh: lda $0278,x + sta $0277,x + inx + cpx #9 + bne @sh + dec $C6 + pla + cli + rts +@none: cli + lda #0 + rts + +; ---------------------------------------------------------------- zsave / zrest +; $22-$2A -> SAVE+0..8, $4B-$60 -> SAVE+9..30, $FB-$FC -> SAVE+31..32 +zsave: ldx #8 +@a: lda $22,x + sta SAVE,x + dex + bpl @a + ldx #21 +@b: lda $4B,x + sta SAVE+9,x + dex + bpl @b + lda $FB + sta SAVE+31 + lda $FC + sta SAVE+32 + rts + +zrest: ldx #8 +@a: lda SAVE,x + sta $22,x + dex + bpl @a + ldx #21 +@b: lda SAVE+9,x + sta $4B,x + dex + bpl @b + lda SAVE+31 + sta $FB + lda SAVE+32 + sta $FC + rts + + .export scrn, row, bmove, ladj, kpop diff --git a/vim/vmlwrap.c b/vim/vmlwrap.c new file mode 100644 index 0000000..6d0d3db --- /dev/null +++ b/vim/vmlwrap.c @@ -0,0 +1,134 @@ +/* vmlwrap.c - the C side of the machine code helpers. build-ml.ps1 appends + * this to the generated byte array to make vml.c; edit it here. + * + * Parameters go through a block at $c400 (not $cf00: the C stack lives just below $d000), then sys() jumps to the entry. + * On the c64 sys() takes no bank argument: sys(addr, &a, &x, &y). + */ + +extern char *bfr; +extern unsigned blen; +extern unsigned lptr[]; +extern int nlines, top, leftcol; + +/* move the code to $c000 and keep power c's heap and stack below it. + must run before malloc() */ +mlinit() +{ + char *d; + int i; + + highmem(0xc000); + d = (char *) 0xc000; + for (i = 0; i < MLSIZE; i++) d[i] = mlcode[i]; + return 0; +} + +/* call an entry, return what it left in A */ +mlgo(addr) +unsigned addr; +{ + char a, x, y; + + a = 0; + x = 0; + y = 0; + sys(addr, &a, &x, &y); + return a & 255; +} + +/* The wrappers fill the parameter block through one pointer instead of + calling a helper per field: every power c function call costs a lot, and + mlscr runs on every key. Words are stored low byte first. */ + +/* paint one row at screen address dst: len bytes from src, cursor at column + cur (255 = none), rev = 128 for an all reverse row */ +mlrow(src, len, dst, cur, rev) +unsigned src, dst; +int len, cur, rev; +{ + char *p; + + p = (char *) 0xc400; + p[0] = src; + p[1] = src >> 8; + p[2] = len; + p[3] = dst; + p[4] = dst >> 8; + p[5] = cur; + p[6] = rev; + mlgo(MLROW); + return 0; +} + +/* paint screen rows r0..r1 from the buffer; the cursor sits at screen row + crow (255 = none), column ccol */ +mlscr(r0, r1, crow, ccol) +int r0, r1, crow, ccol; +{ + char *p; + unsigned v; + + p = (char *) 0xc400; + v = (unsigned) bfr; + p[0] = v; + p[1] = v >> 8; + v = (unsigned) lptr; + p[2] = v; + p[3] = v >> 8; + p[4] = nlines; + p[5] = nlines >> 8; + p[6] = top; + p[7] = top >> 8; + p[8] = blen; + p[9] = blen >> 8; + p[10] = leftcol; + p[11] = leftcol >> 8; + p[12] = crow; + p[13] = ccol; + p[14] = r0; + p[15] = r1; + mlgo(MLSCRN); + return 0; +} + +/* memmove: n bytes from 'from' to 'to', overlap safe */ +mlmove(from, to, n) +unsigned from, to, n; +{ + char *p; + + if (n == 0) return 0; + p = (char *) 0xc400; + p[0] = from; + p[1] = from >> 8; + p[2] = to; + p[3] = to >> 8; + p[4] = n; + p[5] = n >> 8; + mlgo(MLBMOVE); + return 0; +} + +/* add d to n words starting at address a (line index fix-ups) */ +mladj(a, n, d) +unsigned a, n, d; +{ + char *p; + + if (n == 0) return 0; + p = (char *) 0xc400; + p[0] = a; + p[1] = a >> 8; + p[2] = n; + p[3] = n >> 8; + p[4] = d; + p[5] = d >> 8; + mlgo(MLLADJ); + return 0; +} + +/* next key from the kernal buffer, or 0 */ +mlkey() +{ + return mlgo(MLKPOP); +} diff --git a/vim/vrow.bin b/vim/vrow.bin deleted file mode 100644 index 491ff514bf1332fa725387d169a39651f0fbe847..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 76 zcmZ3g!Ktu7eWGfI!0!bNM^rxOZd5v{uz_i%!pQ>@SY9@45Iea5NIPucJDC8a6*jP) gbeO>O(t%ZDqUwnY=a;IV{QrS{#YR=nhKZ^P00e^~g#Z8m diff --git a/vim/vrow.s b/vim/vrow.s deleted file mode 100644 index 810e49f..0000000 --- a/vim/vrow.s +++ /dev/null @@ -1,69 +0,0 @@ -; vrow.s - draw one screen row for the c64 vim clone (called from power c). -; -; Position independent: no absolute jumps and no absolute data, so the bytes can -; sit in a C array and be called at wherever the linker put that array. -; -; Parameters live in zero page scratch that Power C leaves to ML routines -; (decimal 34-42 = $22-$2A), poked by C before the call (see vml.c): -; $22/$23 src address of the first byte of text (petscii) -; $24 len bytes of text to show, 0..40 -; $25/$26 dst screen RAM address of the row's first cell -; $27 cur column to show reverse (the cursor), $FF = none -; $28 rev $00 normal text, $80 whole row reverse (status row) -; -; The row is cleared to blanks, the text is converted petscii -> screen code -; and copied, then the cursor cell is flipped to reverse video. Codes below -; $20 show as a blank. Colour RAM is set once at start-up, not here. -; -; Build: build-ml.ps1 (ca65 + ld65 -> vrow.bin -> vml.c). - -src = $22 -len = $24 -dst = $25 -cur = $27 -rev = $28 - - .segment "CODE" - -entry: lda rev ; clear the row - ora #$20 - ldy #39 -clr: sta (dst),y - dey - bpl clr - - ldy #0 -cp: cpy len - beq done - lda (src),y - cmp #$20 - bcs ok - lda #$20 -ok: cmp #$C0 ; petscii -> screen code - bcc l1 - sbc #$80 - bcs st ; carry is still set: an unconditional branch -l1: cmp #$A0 - bcc l2 - sbc #$40 - bcs st -l2: cmp #$60 - bcc l3 - sbc #$20 - bcs st -l3: cmp #$40 - bcc st - sbc #$40 -st: ora rev - sta (dst),y - iny - bne cp ; y never wraps (max 40): unconditional - -done: lda cur - cmp #$FF - beq fin - tay - lda (dst),y - eor #$80 - sta (dst),y -fin: rts diff --git a/vim/vscreen.c b/vim/vscreen.c index 6ce3c9d..bd85796 100644 --- a/vim/vscreen.c +++ b/vim/vscreen.c @@ -93,10 +93,12 @@ sinit() *p = 0; p = (char *) 0xd021; *p = 6; - p = (char *) 0x00cc; - *p = 0; + p = (char *) 0x00cc; /* kernal cursor blink: non-zero = off */ + *p = 1; p = (char *) 0x0091; /* run/stop flag: drop whatever boot left */ *p = 0; + p = (char *) 0x028a; /* key repeat: c64 default, cursor keys */ + *p = 0; /* only (the power c shell leaves all on) */ for (i = 0; i < 1000; i++) { scr[i] = 32; colr[i] = 1; @@ -140,22 +142,11 @@ sdone() * echoes through the screen editor and only hands characters over after RETURN. */ -/* next key, or 0 if none is waiting */ +/* next key, or 0 if none is waiting. the pop runs in machine code with irqs + masked, so the kernal cannot append a key half way through the shift */ skeyp() { - int n, k, i; - char *cnt, *buf; - - cnt = (char *) 0x00c6; - n = *cnt; - n = n & 255; - if (n == 0) return 0; - buf = (char *) 0x0277; - k = buf[0]; - k = k & 255; - for (i = 1; i < n; i++) buf[i - 1] = buf[i]; - *cnt = (char) (n - 1); - return k; + return mlkey(); } /* wait for a key */ diff --git a/vim/vscreen.pet b/vim/vscreen.pet index ac9d505..88977ed 100644 --- a/vim/vscreen.pet +++ b/vim/vscreen.pet @@ -1 +1 @@ -/* VSCREEN.C - SCREEN AND KEYBOARD FOR THE C64 VIM CLONE. * * ÔHE EDITOR WRITES STRAIGHT INTO THE SCREEN MATRIX AT $0400 AND COLOUR RAM AT * $D800 RATHER THAN GOING THROUGH THE KERNAL, AND IT SELECTS THE LOWER/UPPER * CASE CHARACTER SET AT $1800 SO THAT PETSCII $41-$5A DRAWS AS REAL LOWERCASE. * * ÅVERYTHING HERE RETURNS INT, SO THE OTHER COMPILATION UNIT CAN CALL THESE * WITHOUT DECLARING THEM (POWER C IS Ë&Ò Ã). */ CHAR *SCR; /* SCREEN MATRIX, $0400 */ CHAR *COLR; /* COLOUR RAM, $D800 */ /* PETSCII BYTE -> SCREEN CODE */ S2V(P) INT P; Û IF (P >= 0XC0) RETURN P - 0X80; IF (P >= 0XA0) RETURN P - 0X40; IF (P >= 0X60) RETURN P - 0X20; IF (P >= 0X40) RETURN P - 0X40; RETURN P; Ý /* RAW SCREEN CODE AT ROW R, COLUMN C */ SRAW(R, C, V) INT R, C, V; Û INT I; IF (R < 0) RETURN 0; IF (R > 24) RETURN 0; IF (C < 0) RETURN 0; IF (C > 39) RETURN 0; I = R * 40 + C; SCR[I] = V; COLR[I] = 1; RETURN 0; Ý /* ONE PETSCII CHARACTER */ SPC(R, C, P) INT R, C, P; Û SRAW(R, C, S2V(P)); RETURN 0; Ý /* ONE PETSCII CHARACTER, INVERTED: THE CURSOR AND THE STATUS ROW */ SRPC(R, C, P) INT R, C, P; Û SRAW(R, C, S2V(P) + 128); RETURN 0; Ý /* A REVERSED SPACE, FOR THE CURSOR SITTING PAST THE END OF A LINE */ SBLK(R, C) INT R, C; Û SRAW(R, C, 160); RETURN 0; Ý /* A ZERO TERMINATED PETSCII STRING */ SPS(R, C, S) INT R, C; CHAR *S; Û WHILE (*S) SPC(R, C++, *S++); RETURN 0; Ý /* N BLANKS FROM (R,C) */ SCLR(R, C, N) INT R, C, N; Û WHILE (N-- > 0) SRAW(R, C++, 32); RETURN 0; Ý /* TAKE OVER THE MACHINE: BLACK ON BLUE, LOWER CASE, NO BLINKING CURSOR */ SINIT() Û INT I; CHAR *P; SCR = (CHAR *) 0X0400; COLR = (CHAR *) 0XD800; P = (CHAR *) 0XD018; *P = 0X17; P = (CHAR *) 0XD020; *P = 0; P = (CHAR *) 0XD021; *P = 6; P = (CHAR *) 0X00CC; *P = 0; P = (CHAR *) 0X0091; /* RUN/STOP FLAG: DROP WHATEVER BOOT LEFT */ *P = 0; FOR (I = 0; I < 1000; I++) Û SCR[I] = 32; COLR[I] = 1; Ý RETURN 0; Ý /* GIVE THE MACHINE BACK THE WAY BASIC EXPECTS IT */ SDONE() Û CHAR *P; CHAR A, X, Y; P = (CHAR *) 0XD018; *P = 0X15; P = (CHAR *) 0XD020; *P = 14; P = (CHAR *) 0XD021; *P = 6; /* DO NOT RETURN TO BASIC: POWER C OWNS ZERO PAGE 43-74, WHICH IS WHERE BASIC KEEPS ITS PROGRAM AND VARIABLE POINTERS, SO BASIC CARRIES ON WITH GARBAGE AND PRINTS A MANGLED ERROR INSTEAD OF ÒÅÁÄÙ. RESTART THE MACHINE THROUGH THE KERNAL RESET INSTEAD - BANNER AND ÒÅÁÄÙ, LIKE SWITCHING ON. THE FILE HAS BEEN SAVED (OR ABANDONED WITH :Q!) BY NOW. */ A = 0; X = 0; Y = 0; SYS(0XFCE2, &A, &X, &Y); /* NEVER COMES BACK */ RETURN 0; Ý /* ---------------------------------------------------------------- KEYBOARD * * ËEYS COME STRAIGHT OUT OF THE KERNAL'S KEYBOARD BUFFER: $C6 SAYS HOW MANY * ARE WAITING AND THEY QUEUE FROM $0277. ÔHE KERNAL'S OWN IRQ HANDLER SCANS * THE KEYBOARD MATRIX AND FILLS THAT BUFFER, SO THIS GETS EACH PRESS AS IT * HAPPENS - AND IT ALSO CATCHES ANYTHING TYPED BEFORE THE PROGRAM STARTED. * * (ÇÅÔÉÎ THROUGH SYS() ONCE "CRASHED" - BUT THAT WAS SYS() CALLED WITH THE * C128 BANK ARGUMENT; ON THE C64 IT IS SYS(ADDR, &A, &X, &Y).) ÁND THE LIBRARY'S GETCHAR() IS LINE BUFFERED - IT * ECHOES THROUGH THE SCREEN EDITOR AND ONLY HANDS CHARACTERS OVER AFTER ÒÅÔÕÒÎ. */ /* NEXT KEY, OR 0 IF NONE IS WAITING */ SKEYP() Û INT N, K, I; CHAR *CNT, *BUF; CNT = (CHAR *) 0X00C6; N = *CNT; N = N & 255; IF (N == 0) RETURN 0; BUF = (CHAR *) 0X0277; K = BUF[0]; K = K & 255; FOR (I = 1; I < N; I++) BUF[I - 1] = BUF[I]; *CNT = (CHAR) (N - 1); RETURN K; Ý /* WAIT FOR A KEY */ SKEY() Û INT K; FOR (;;) Û K = SKEYP(); IF (K != 0) RETURN K; Ý Ý \ No newline at end of file +/* VSCREEN.C - SCREEN AND KEYBOARD FOR THE C64 VIM CLONE. * * ÔHE EDITOR WRITES STRAIGHT INTO THE SCREEN MATRIX AT $0400 AND COLOUR RAM AT * $D800 RATHER THAN GOING THROUGH THE KERNAL, AND IT SELECTS THE LOWER/UPPER * CASE CHARACTER SET AT $1800 SO THAT PETSCII $41-$5A DRAWS AS REAL LOWERCASE. * * ÅVERYTHING HERE RETURNS INT, SO THE OTHER COMPILATION UNIT CAN CALL THESE * WITHOUT DECLARING THEM (POWER C IS Ë&Ò Ã). */ CHAR *SCR; /* SCREEN MATRIX, $0400 */ CHAR *COLR; /* COLOUR RAM, $D800 */ /* PETSCII BYTE -> SCREEN CODE */ S2V(P) INT P; Û IF (P >= 0XC0) RETURN P - 0X80; IF (P >= 0XA0) RETURN P - 0X40; IF (P >= 0X60) RETURN P - 0X20; IF (P >= 0X40) RETURN P - 0X40; RETURN P; Ý /* RAW SCREEN CODE AT ROW R, COLUMN C */ SRAW(R, C, V) INT R, C, V; Û INT I; IF (R < 0) RETURN 0; IF (R > 24) RETURN 0; IF (C < 0) RETURN 0; IF (C > 39) RETURN 0; I = R * 40 + C; SCR[I] = V; COLR[I] = 1; RETURN 0; Ý /* ONE PETSCII CHARACTER */ SPC(R, C, P) INT R, C, P; Û SRAW(R, C, S2V(P)); RETURN 0; Ý /* ONE PETSCII CHARACTER, INVERTED: THE CURSOR AND THE STATUS ROW */ SRPC(R, C, P) INT R, C, P; Û SRAW(R, C, S2V(P) + 128); RETURN 0; Ý /* A REVERSED SPACE, FOR THE CURSOR SITTING PAST THE END OF A LINE */ SBLK(R, C) INT R, C; Û SRAW(R, C, 160); RETURN 0; Ý /* A ZERO TERMINATED PETSCII STRING */ SPS(R, C, S) INT R, C; CHAR *S; Û WHILE (*S) SPC(R, C++, *S++); RETURN 0; Ý /* N BLANKS FROM (R,C) */ SCLR(R, C, N) INT R, C, N; Û WHILE (N-- > 0) SRAW(R, C++, 32); RETURN 0; Ý /* TAKE OVER THE MACHINE: BLACK ON BLUE, LOWER CASE, NO BLINKING CURSOR */ SINIT() Û INT I; CHAR *P; SCR = (CHAR *) 0X0400; COLR = (CHAR *) 0XD800; P = (CHAR *) 0XD018; *P = 0X17; P = (CHAR *) 0XD020; *P = 0; P = (CHAR *) 0XD021; *P = 6; P = (CHAR *) 0X00CC; /* KERNAL CURSOR BLINK: NON-ZERO = OFF */ *P = 1; P = (CHAR *) 0X0091; /* RUN/STOP FLAG: DROP WHATEVER BOOT LEFT */ *P = 0; P = (CHAR *) 0X028A; /* KEY REPEAT: C64 DEFAULT, CURSOR KEYS */ *P = 0; /* ONLY (THE POWER C SHELL LEAVES ALL ON) */ FOR (I = 0; I < 1000; I++) Û SCR[I] = 32; COLR[I] = 1; Ý RETURN 0; Ý /* GIVE THE MACHINE BACK THE WAY BASIC EXPECTS IT */ SDONE() Û CHAR *P; CHAR A, X, Y; P = (CHAR *) 0XD018; *P = 0X15; P = (CHAR *) 0XD020; *P = 14; P = (CHAR *) 0XD021; *P = 6; /* DO NOT RETURN TO BASIC: POWER C OWNS ZERO PAGE 43-74, WHICH IS WHERE BASIC KEEPS ITS PROGRAM AND VARIABLE POINTERS, SO BASIC CARRIES ON WITH GARBAGE AND PRINTS A MANGLED ERROR INSTEAD OF ÒÅÁÄÙ. RESTART THE MACHINE THROUGH THE KERNAL RESET INSTEAD - BANNER AND ÒÅÁÄÙ, LIKE SWITCHING ON. THE FILE HAS BEEN SAVED (OR ABANDONED WITH :Q!) BY NOW. */ A = 0; X = 0; Y = 0; SYS(0XFCE2, &A, &X, &Y); /* NEVER COMES BACK */ RETURN 0; Ý /* ---------------------------------------------------------------- KEYBOARD * * ËEYS COME STRAIGHT OUT OF THE KERNAL'S KEYBOARD BUFFER: $C6 SAYS HOW MANY * ARE WAITING AND THEY QUEUE FROM $0277. ÔHE KERNAL'S OWN IRQ HANDLER SCANS * THE KEYBOARD MATRIX AND FILLS THAT BUFFER, SO THIS GETS EACH PRESS AS IT * HAPPENS - AND IT ALSO CATCHES ANYTHING TYPED BEFORE THE PROGRAM STARTED. * * (ÇÅÔÉÎ THROUGH SYS() ONCE "CRASHED" - BUT THAT WAS SYS() CALLED WITH THE * C128 BANK ARGUMENT; ON THE C64 IT IS SYS(ADDR, &A, &X, &Y).) ÁND THE LIBRARY'S GETCHAR() IS LINE BUFFERED - IT * ECHOES THROUGH THE SCREEN EDITOR AND ONLY HANDS CHARACTERS OVER AFTER ÒÅÔÕÒÎ. */ /* NEXT KEY, OR 0 IF NONE IS WAITING. THE POP RUNS IN MACHINE CODE WITH IRQS MASKED, SO THE KERNAL CANNOT APPEND A KEY HALF WAY THROUGH THE SHIFT */ SKEYP() Û RETURN MLKEY(); Ý /* WAIT FOR A KEY */ SKEY() Û INT K; FOR (;;) Û K = SKEYP(); IF (K != 0) RETURN K; Ý Ý \ No newline at end of file diff --git a/vim/vview.c b/vim/vview.c index 1d10f38..eb03d12 100644 --- a/vim/vview.c +++ b/vim/vview.c @@ -8,6 +8,7 @@ extern char *note; extern char fname[]; extern char ybuf[]; extern int ylen, yok; +extern int redraw; /* decimal of v into t at p, returns the position after it */ dnum(t, p, v) @@ -42,34 +43,6 @@ scroll() return 0; } -/* draw screen row sr from buffer line top+sr */ -drow(sr) -int sr; -{ - int ln, n, cc; - unsigned i, e, d; - - d = 1024 + sr * 40; /* screen RAM address of the row */ - ln = top + sr; - if (ln >= nlines) { - mlrow(0, 0, d, 255, 0); - return 0; - } - i = lptr[ln] + leftcol; - e = linend(ln); - n = 0; - if (e > i) n = e - i; - if (n > 40) n = 40; - cc = 255; /* cursor column, 255 = not here */ - if (ln == cy) { - cc = cx - leftcol; - if (cc < 0) cc = 255; - if (cc > 39) cc = 255; - } - mlrow((unsigned) bfr + i, n, d, cc, 0); /* machine code does the rest */ - return 0; -} - /* the status row: message, mode or name on the left; modified flag; line/total */ dstat() { @@ -96,14 +69,36 @@ dstat() return 0; } +/* what the screen showed last time, so a key that only moves the cursor + repaints two rows instead of 24 */ +int ltop, lleft, lcy; + +/* repaint what changed, then the status row. redraw is raised by the editing + code: 1 = the cursor line changed, 2 = lines moved, repaint everything. + (no double bar: power c cannot type it, see NOTES.md) */ dall() { - int r; + int crow, ccol, orow, full; - for (r = 0; r < 24; r++) drow(r); + crow = cy - top; + ccol = cx - leftcol; + full = 0; + if (redraw == 2) full = 1; + if (top != ltop) full = 1; + if (leftcol != lleft) full = 1; + if (full) { + mlscr(0, 23, crow, ccol); /* one call paints 24 rows */ + } else { + orow = lcy - top; /* take the cursor off its old row */ + if (orow != crow && orow >= 0 && orow < 24) mlscr(orow, orow, crow, ccol); + mlscr(crow, crow, crow, ccol); + } + ltop = top; + lleft = leftcol; + lcy = cy; + redraw = 0; dstat(); return 0; } - /* end of vview.c */ diff --git a/vim/vview.pet b/vim/vview.pet index 2df3f6f..83c4ff6 100644 --- a/vim/vview.pet +++ b/vim/vview.pet @@ -1 +1 @@ -/* VVIEW.C - DRAWING FOR THE C64 VIM CLONE: SCROLL, ROWS, STATUS ROW. */ EXTERN CHAR *BFR; EXTERN UNSIGNED BLEN; EXTERN UNSIGNED LPTR[]; EXTERN INT NLINES, CY, CX, TOP, LEFTCOL, MODE, DIRTY; EXTERN CHAR *NOTE; EXTERN CHAR FNAME[]; EXTERN CHAR YBUF[]; EXTERN INT YLEN, YOK; /* DECIMAL OF V INTO T AT P, RETURNS THE POSITION AFTER IT */ DNUM(T, P, V) CHAR *T; INT P, V; Û CHAR D[6]; INT N; N = 0; IF (V == 0) Û T[P++] = 48; RETURN P; Ý WHILE (V > 0 && N < 5) Û D[N++] = 48 + (V % 10); V = V / 10; Ý WHILE (N > 0) T[P++] = D[--N]; RETURN P; Ý /* BRING THE CURSOR BACK INTO VIEW */ SCROLL() Û IF (CY < TOP) TOP = CY; IF (CY > TOP + 23) TOP = CY - 23; IF (TOP < 0) TOP = 0; IF (CX < LEFTCOL) LEFTCOL = CX; IF (CX > LEFTCOL + 39) LEFTCOL = CX - 39; IF (LEFTCOL < 0) LEFTCOL = 0; RETURN 0; Ý /* DRAW SCREEN ROW SR FROM BUFFER LINE TOP+SR */ DROW(SR) INT SR; Û INT LN, N, CC; UNSIGNED I, E, D; D = 1024 + SR * 40; /* SCREEN ÒÁÍ ADDRESS OF THE ROW */ LN = TOP + SR; IF (LN >= NLINES) Û MLROW(0, 0, D, 255, 0); RETURN 0; Ý I = LPTR[LN] + LEFTCOL; E = LINEND(LN); N = 0; IF (E > I) N = E - I; IF (N > 40) N = 40; CC = 255; /* CURSOR COLUMN, 255 = NOT HERE */ IF (LN == CY) Û CC = CX - LEFTCOL; IF (CC < 0) CC = 255; IF (CC > 39) CC = 255; Ý MLROW((UNSIGNED) BFR + I, N, D, CC, 0); /* MACHINE CODE DOES THE REST */ RETURN 0; Ý /* THE STATUS ROW: MESSAGE, MODE OR NAME ON THE LEFT; MODIFIED FLAG; LINE/TOTAL */ DSTAT() Û CHAR T[40]; CHAR *S; INT I, N, P; IF (NOTE != 0) 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 < 26; I++) T[N++] = S[I]; WHILE (N < 28) T[N++] = 32; IF (DIRTY) T[N++] = 43; /* + */ ELSE T[N++] = 32; WHILE (N < 31) T[N++] = 32; P = DNUM(T, N, CY + 1); T[P++] = 47; /* / */ P = DNUM(T, P, NLINES); MLROW((UNSIGNED) T, P, 1984, 255, 128); /* ROW 24 = $0400 + 960, REVERSE */ RETURN 0; Ý DALL() Û INT R; FOR (R = 0; R < 24; R++) DROW(R); DSTAT(); RETURN 0; Ý /* END OF VVIEW.C */ \ No newline at end of file +/* VVIEW.C - DRAWING FOR THE C64 VIM CLONE: SCROLL, ROWS, STATUS ROW. */ EXTERN CHAR *BFR; EXTERN UNSIGNED BLEN; EXTERN UNSIGNED LPTR[]; EXTERN INT NLINES, CY, CX, TOP, LEFTCOL, MODE, DIRTY; EXTERN CHAR *NOTE; EXTERN CHAR FNAME[]; EXTERN CHAR YBUF[]; EXTERN INT YLEN, YOK; EXTERN INT REDRAW; /* DECIMAL OF V INTO T AT P, RETURNS THE POSITION AFTER IT */ DNUM(T, P, V) CHAR *T; INT P, V; Û CHAR D[6]; INT N; N = 0; IF (V == 0) Û T[P++] = 48; RETURN P; Ý WHILE (V > 0 && N < 5) Û D[N++] = 48 + (V % 10); V = V / 10; Ý WHILE (N > 0) T[P++] = D[--N]; RETURN P; Ý /* BRING THE CURSOR BACK INTO VIEW */ SCROLL() Û IF (CY < TOP) TOP = CY; IF (CY > TOP + 23) TOP = CY - 23; IF (TOP < 0) TOP = 0; IF (CX < LEFTCOL) LEFTCOL = CX; IF (CX > LEFTCOL + 39) LEFTCOL = CX - 39; IF (LEFTCOL < 0) LEFTCOL = 0; RETURN 0; Ý /* THE STATUS ROW: MESSAGE, MODE OR NAME ON THE LEFT; MODIFIED FLAG; LINE/TOTAL */ DSTAT() Û CHAR T[40]; CHAR *S; INT I, N, P; IF (NOTE != 0) 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 < 26; I++) T[N++] = S[I]; WHILE (N < 28) T[N++] = 32; IF (DIRTY) T[N++] = 43; /* + */ ELSE T[N++] = 32; WHILE (N < 31) T[N++] = 32; P = DNUM(T, N, CY + 1); T[P++] = 47; /* / */ P = DNUM(T, P, NLINES); MLROW((UNSIGNED) T, P, 1984, 255, 128); /* ROW 24 = $0400 + 960, REVERSE */ RETURN 0; Ý /* WHAT THE SCREEN SHOWED LAST TIME, SO A KEY THAT ONLY MOVES THE CURSOR REPAINTS TWO ROWS INSTEAD OF 24 */ INT LTOP, LLEFT, LCY; /* REPAINT WHAT CHANGED, THEN THE STATUS ROW. REDRAW IS RAISED BY THE EDITING CODE: 1 = THE CURSOR LINE CHANGED, 2 = LINES MOVED, REPAINT EVERYTHING. (NO DOUBLE BAR: POWER C CANNOT TYPE IT, SEE ÎÏÔÅÓ.MD) */ DALL() Û INT CROW, CCOL, OROW, FULL; CROW = CY - TOP; CCOL = CX - LEFTCOL; FULL = 0; IF (REDRAW == 2) FULL = 1; IF (TOP != LTOP) FULL = 1; IF (LEFTCOL != LLEFT) FULL = 1; IF (FULL) Û MLSCR(0, 23, CROW, CCOL); /* ONE CALL PAINTS 24 ROWS */ Ý ELSE Û OROW = LCY - TOP; /* TAKE THE CURSOR OFF ITS OLD ROW */ IF (OROW != CROW && OROW >= 0 && OROW < 24) MLSCR(OROW, OROW, CROW, CCOL); MLSCR(CROW, CROW, CROW, CCOL); Ý LTOP = TOP; LLEFT = LEFTCOL; LCY = CY; REDRAW = 0; DSTAT(); RETURN 0; Ý /* END OF VVIEW.C */ \ No newline at end of file diff --git a/vim/work.d81 b/vim/work.d81 index 54aa2f5c9a6c5d8af008c4320425e533d5b57653..e5d4403a8d0656a6d20833228cbc07b149536e49 100644 GIT binary patch delta 48105 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 BbrS#p delta 33553 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 Wp~M?>%XRP{|CmlVj<=X~IsXT1(1Kn7 diff --git a/vim/work.stamp b/vim/work.stamp index 314456f..ec2b301 100644 --- a/vim/work.stamp +++ b/vim/work.stamp @@ -1,6 +1,6 @@ -vim=8E13FCED4A0CAFD92855B9281D268368 -vscreen=7773151D0C575F00E919F19875CB498F -vview=F3C7D092B8080C956D9EBD7B95800CD5 +vim=D571CD44594114AF849B8C6C15366001 +vscreen=30C2D474C6B87917CD09BA23BBDF9A7C +vview=FDB755840D15E1F32FAFF1214F246BF8 mltest=E4BDE10600DE08AF766FFB32A6D9A3E5 -vedit=8BE6C95F2BDA01EC535A8C51CD666881 -vml=B9A7F164619D4675A2E78B48F02C179D +vedit=3BC34F6146A6C300555BDB65BC08E256 +vml=975E31324614BD5A546A523B34E02A64