From 0e735d3e4b189271b80189626699b283df477eef Mon Sep 17 00:00:00 2001 From: Daniel Covington Date: Fri, 6 Nov 2026 18:16:40 -0500 Subject: [PATCH] Update vim editor: split into modules, add build scripts and notes Co-Authored-By: Claude Sonnet 5.5 --- AGENTS.md | 5 +- vim/NOTES.md | 137 ++++++++ vim/build-ml.ps1 | 81 +++++ vim/build.bat | 74 +---- vim/build.ps1 | 241 ++++++++++++++ vim/mltest.c | 32 ++ vim/mltest.pet | 1 + vim/run.bat | 10 + vim/shots/run.png | Bin 0 -> 2080 bytes vim/shots/shell.png | Bin 0 -> 3230 bytes vim/to-petscii.ps1 | 31 -- vim/vedit.c | 224 +++++++++++++ vim/vedit.pet | 1 + vim/vim.c | 645 +++++++++++++++++--------------------- vim/vim.pet | 1 + vim/vml.c | 46 +++ vim/vml.pet | 1 + vim/vrow.bin | Bin 0 -> 76 bytes vim/vrow.s | 69 ++++ vim/vscreen.c | 327 +++++++------------ vim/vscreen.pet | 1 + vim/vview.c | 109 +++++++ vim/vview.pet | 1 + vim/{vim.d81 => work.d81} | Bin 819200 -> 819200 bytes vim/work.stamp | 6 + 25 files changed, 1375 insertions(+), 668 deletions(-) create mode 100644 vim/NOTES.md create mode 100644 vim/build-ml.ps1 create mode 100644 vim/build.ps1 create mode 100644 vim/mltest.c create mode 100644 vim/mltest.pet create mode 100644 vim/run.bat create mode 100644 vim/shots/run.png create mode 100644 vim/shots/shell.png delete mode 100644 vim/to-petscii.ps1 create mode 100644 vim/vedit.c create mode 100644 vim/vedit.pet create mode 100644 vim/vim.pet create mode 100644 vim/vml.c create mode 100644 vim/vml.pet create mode 100644 vim/vrow.bin create mode 100644 vim/vrow.s create mode 100644 vim/vscreen.pet create mode 100644 vim/vview.c create mode 100644 vim/vview.pet rename vim/{vim.d81 => work.d81} (88%) create mode 100644 vim/work.stamp diff --git a/AGENTS.md b/AGENTS.md index ebf4462..085a92a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,7 +25,7 @@ This repository contains structured skills and reference material for developing | `skills/interrupts/` | IRQ, NMI, CIA timers, raster interrupts | Interrupt-driven code, timing | | `skills/compiler-design/` | Compiler & assembler construction for C64/128 | Writing compilers, assemblers, parsers | | `skills/vice-emulator/` | VICE emulator, autostart, monitor, snapshots, automation | Running, testing, and debugging C64 software in VICE | -| `skills/powerc/` | Power C compiler on the C64 | Writing, compiling, linking, and running C programs (reference/POWERc.d81) | +| `skills/powerc/` | Power C compiler on the C64 | Writing, compiling, linking, and running C programs with Power C (reference/POWERc.d81); load this whenever Power C or on-C64 C development is requested | ### Reference Files @@ -225,7 +225,8 @@ When an AI agent receives a C64 development question: 3. **Cross-reference** `reference/` files for specific register values, addresses, or opcodes 4. **Check examples/** for working code patterns 5. **If the task involves running or debugging software in an emulator**, load `skills/vice-emulator/SKILL.md` and `reference/vice-usage.md` -6. **Apply the 150-expert consensus**: prefer documented Kernal routines over custom implementations; use zero-page for speed; always consider memory banking when accessing $D000–$DFFF +6. **If the task involves writing C for the C64** (user mentions C, Power C, `ced`, `cc`, `link`, or `reference/POWERc.d81`), load `skills/powerc/SKILL.md` first. Power C is K&R C (no prototypes/`void`, 2-byte `int`, 8-char identifiers), sources on disk are PETSCII, and `cc`/`link` need specific steps. Also load `skills/vice-emulator/SKILL.md` to automate the build in VICE (copy `reference/POWERc.d81` first; never write to the original). Note: `examples/moonlander.c` and `examples/linked-list-test.c` use **cc65**, not Power C; pick the toolchain the user asked for. +7. **Apply the 150-expert consensus**: prefer documented Kernal routines over custom implementations; use zero-page for speed; always consider memory banking when accessing $D000–$DFFF --- diff --git a/vim/NOTES.md b/vim/NOTES.md new file mode 100644 index 0000000..a32f0b8 --- /dev/null +++ b/vim/NOTES.md @@ -0,0 +1,137 @@ +# Building a vi/vim clone for the C64 with Power C — hard-won notes + +Everything below was verified on this machine (GTK3VICE 3.10, `reference/POWERc.d81`), +mostly by first getting it wrong. Read this before touching the build or the +keyboard code. + +## The build: one session, start to finish + +Compile every unit, link, then `bye` out of the Power C shell, then `load` and +`run` from BASIC — all typed into **one** emulator session from one `-keybuf` +string: + +``` +cc vim.c ... padding +cc vscreen.c ... padding +link -s +vim.o +vscreen.o +^ up-arrow: stdlib.l + syslib.l + empty RETURN: done +vim output name +bye +load"vim",8,1 +run +``` + +- **Never run two emulators against the same `.d81`.** Both hold the whole image + in memory and write it back on exit, so the second one's exit silently erases + what the first wrote. Symptoms: a compile that "produced nothing", a linker + that "found no object", a directory that changes between two `c1541 -list` + calls. +- Use `Start-Process -Wait` (or `.NET Process` + `WaitForExit`) so a step cannot + overlap the next. +- `-keybuf-delay` is **wall-clock** milliseconds per keystroke while the machine + runs at warp, so 900 ms per key is plenty for the machine. +- **`cc` clears the keyboard buffer when it starts**, so its three disk-prompt + answers have to arrive *after* the prompts appear, not before. That is why a + 300 ms burst of RETURNs fails: the answers are eaten and cc sits at "insert + object disk" forever. 900 ms plus ~20 spare RETURNs after each `cc` works. + Spare RETURNs at the shell prompt are harmless. +- **The linker's `>` prompts are line-based and it loads each object before + asking again**, so no padding RETURNs may sit between the object names — an + empty line means "end of object list". `^` is the up-arrow key and pulls in + the libraries; forget it and you get "unresolved external references" + (`Main`, `SYS`, `MALLOC`, ...). +- `-exitscreenshot` **will not overwrite** an existing file, and `-limitcycles` + is what ends the session (VICE exits with status 1 and prints "cycle limit + reached" — that is normal). +- Keep the disk image a **bare filename** and run from its folder: VICE splits + its `image:file` autostart argument on a colon, so a full path is ambiguous. + +## PowerShell traps + +- `Start-Process -ArgumentList` joins the array with spaces and **quotes + nothing**. `"-keybuf", "cc vim.c\n"` therefore arrived as two arguments and + VICE silently never started (no window, no screenshot). Build the command + line yourself and hand it to `System.Diagnostics.ProcessStartInfo.Arguments`, + escaping inner `"` as `\"` (see `build.ps1`). +- PowerShell does **not** interpret `\n` in a double-quoted string, so + `"cc $u.c\n"` really is backslash-n — which is what VICE wants. + +## Power C specifics + +- `cc name.c` → `name.o`; it prompts for source disk / compiler disk / object + disk. The Shell prompt is `$`; `bye` returns to BASIC. +- **The linker refuses to overwrite** — "63, file exists" — and then gives up + quietly. Build on a fresh copy of the system disk instead. +- `link -s` with no address links at the start of BASIC: the file begins + `01 08`, holds `10 SYS 2062`, and `$080E` is a `JMP` to the C runtime, so + `load"name",8,1` + `run` works. +- Source files on disk are **SEQ PETSCII with CR line endings**: `a-z → $41-$5A`, + `A-Z → $C1-$DA`, CRLF → CR, `{ | } ~ → $DB $DC $DD $DE` (ASCII `$7B-$7F` plus + `$60`). +- **Do not write `||`.** It needs `|` = LOGO+`*` = PETSCII `$DC`, and cc dies + with "*** illegal character" part-way through the file. Use nested `if`s. + (`&&` is fine.) +- `EOF` is not declared unless you `#include `; test + `if (ch < 0) break;` instead. +- K&R only: no prototypes, no `void`, no `//` comments, identifiers significant + to 8 characters, `int` is 16 bits, chars are unsigned and not sign-extended. + Functions returning `int` need no declaration; declare `char *malloc();`. +- `peek()`/`poke()` are C128-only — use pointer casts. +- `malloc()` (declared `char *malloc();`) happily gives 19000 bytes. Compiled + programs and the heap end at `$D000` by default. +- **Keep the readable `.c` authoritative and `.pet` derived.** In one bad + session the converted PETSCII copy ended up written over the host `.c` file + (every letter uppercased, CR line endings). It still compiled — the + conversion round-trips — but the source was gone. + +## Calling machine language: sys() and the keyboard + +- `sys(bank, address, &a, &x, &y)` is documented as + `int bank; unsigned address; char *aptr, *xptr, *yptr;` with `bank` from 0 to + 15 (BASIC 7 style). **Calling KERNAL GETIN through it crashed the program**; + the crash warm-starts the C64, which clears the screen, so the only evidence + was a blank blue screen and `READY.`. +- The library's **`getchar()` is line buffered**: it echoes through the KERNAL's + screen editor and only hands the characters over after RETURN. Useless for an + editor. +- **The answer: read the KERNAL keyboard buffer directly.** `$C6` holds how many + characters are queued and they sit from `$0277`. The KERNAL's IRQ handler + scans the matrix and fills that buffer, so this gets every real press as it + happens, with no line buffering and no echo. Pop by shifting the rest down and + decrementing `$C6`. +- **Clear `$91` at startup.** Bit 7 is the RUN/STOP flag and it can already be + set when the program starts; treating that as "Esc" made the editor's first + keypress a phantom and swallowed the filename. +- VICE's `-keybuf` feeds that same buffer, so a program reading `$C6` can be + 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. + +## C64 screen facts the editor relies on + +- Screen matrix `$0400`, colour RAM `$D800` (both directly writable). +- `$D018 = $17` → screen at `$0400`, character set at `$1800` (lower/upper case), + so PETSCII `$41-$5A` draws as real lowercase and `$C1-$DA` as uppercase. + Default is `$15` (uppercase/graphics at `$1000`) — restore it on exit. +- PETSCII → screen code: `$20-$3F` unchanged, `$40-$5F` minus `$40`, + `$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. +- Arrow keys are PETSCII `$11` down, `$91` up, `$1D` right, `$9D` left; + RETURN `$0D`, DEL `$14`. + +## Debugging technique that paid off + +- `-exitscreenshot` + `-limitcycles` gives one png per run; read it directly. +- **A crash warm-starts the machine: screen cleared, `READY.`, evidence gone.** + So probe in steps — write a marker to `$0400` (`p[0] = 2` shows as `B`) and + end with `for (;;) { }` instead of going on. Whatever is on screen when it + hangs is the last step that worked. +- Prove the toolchain before debugging your own code: a six-line program that + pokes the top row and spins. +- Keep a screenshot of every build step (`shots\cc-*.png`, `link.png`) and make + the script fail *per step* ("cc vim.c produced no object") instead of at the + end, or a compile failure looks like a linker failure. diff --git a/vim/build-ml.ps1 b/vim/build-ml.ps1 new file mode 100644 index 0000000..81d7c31 --- /dev/null +++ b/vim/build-ml.ps1 @@ -0,0 +1,81 @@ +# 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. +# +# 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. + +$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" +@" +MEMORY { M: start = `$C020, size = `$0100, 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 +if ($LASTEXITCODE) { throw "ca65 failed" } +& $ld65 -C $cfg -o $out $obj +if ($LASTEXITCODE) { throw "ld65 failed" } + +$b = [IO.File]::ReadAllBytes($out) +$rows = @() +for ($i = 0; $i -lt $b.Length; $i += 12) { + $n = [Math]::Min(12, $b.Length - $i) + $rows += " " + (($b[$i..($i + $n - 1)] | ForEach-Object { "$_" }) -join ", ") +} +$arr = $rows -join ",`n" + +@" +/* vml.c - GENERATED by build-ml.ps1 from vrow.s. Do not edit by hand. + * + * 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. + */ + +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(15, (unsigned) mlcode, &a, &x, &y); + return 0; +} +"@ | Set-Content (Join-Path $here "vml.c") -Encoding utf8 + +Write-Host ("vrow.bin {0} bytes -> vml.c" -f $b.Length) + diff --git a/vim/build.bat b/vim/build.bat index 99b5625..949f7b8 100644 --- a/vim/build.bat +++ b/vim/build.bat @@ -1,71 +1,5 @@ @echo off -setlocal -REM ------------------------------------------------------------------ -REM Build the vim editor with Spinnaker Power C on a private copy of -REM the system disk at ..\reference\POWERc.d81. -REM -REM The editor is two source files because Power C's compiler reports -REM "program too big" when the whole thing is one unit; both objects -REM are linked into vim. -REM -REM Power C is interactive: "cc" asks for the source, compiler and -REM object disks, and "link" prompts for object files, the "^" library -REM line and the output file name. -keybuf answers all of it; the runs -REM of \n are those prompts. -keybuf-delay spaces the answers out: fed -REM all at once they land before the later disk prompts appear and are -REM lost, which leaves cc waiting for the object disk. -REM ------------------------------------------------------------------ -set "VICE=C:\Program Files\GTK3VICE-3.10-win64\bin" -set "HERE=%~dp0" -set "BASE=%HERE%..\reference\POWERc.d81" -set "IMG=%HERE%vim.d81" - -if not exist "%VICE%\x64sc.exe" goto novice -if not exist "%BASE%" goto nobody - -echo === fresh work disk -copy /y "%BASE%" "%IMG%" >nul - -echo === ASCII source -^> PETSCII -powershell -NoProfile -ExecutionPolicy Bypass -File "%HERE%to-petscii.ps1" "%HERE%vim.c" "%HERE%vim.pet" -if errorlevel 1 goto bad -powershell -NoProfile -ExecutionPolicy Bypass -File "%HERE%to-petscii.ps1" "%HERE%vscreen.c" "%HERE%vscreen.pet" -if errorlevel 1 goto bad - -echo === write both sources to the disk -for %%f in (vim.c vim.o vscreen.c vscreen.o vim) do "%VICE%\c1541.exe" "%IMG%" -delete %%f >nul 2>&1 -"%VICE%\c1541.exe" "%IMG%" -write "%HERE%vim.pet" "vim.c,s" >nul -if errorlevel 1 goto bad -"%VICE%\c1541.exe" "%IMG%" -write "%HERE%vscreen.pet" "vscreen.c,s" >nul -if errorlevel 1 goto bad - -echo === cc vim.c (a minute or so in warp mode) -"%VICE%\x64sc.exe" -8 "%IMG%" -autostart "%IMG%:shell" -autostart-warp -warp -sounddev dummy -keybuf-delay 300 -keybuf "cc vim.c\n\n\n\n\n\n\n\n\n\n\n\n" -limitcycles 600000000 - -echo === cc vscreen.c -"%VICE%\x64sc.exe" -8 "%IMG%" -autostart "%IMG%:shell" -autostart-warp -warp -sounddev dummy -keybuf-delay 300 -keybuf "cc vscreen.c\n\n\n\n\n\n\n\n\n\n\n\n" -limitcycles 600000000 - -echo === link -s -^> vim -"%VICE%\x64sc.exe" -8 "%IMG%" -autostart "%IMG%:shell" -autostart-warp -warp -sounddev dummy -keybuf-delay 300 -keybuf "link -s\nvim.o\nvscreen.o\n^\n\nvim\n\n\n" -limitcycles 600000000 - -del "%HERE%vim.pet" >nul 2>&1 -del "%HERE%vscreen.pet" >nul 2>&1 - -echo === disk contents -"%VICE%\c1541.exe" "%IMG%" -list | findstr /i "vim vscreen" -echo. -echo Run it with: -echo x64sc -autostart "%IMG%:vim" -exit /b 0 - -:novice -echo x64sc.exe not found in "%VICE%" -exit /b 1 - -:nobody -echo %BASE% not found - is the repository complete? -exit /b 1 - -:bad -echo BUILD FAILED -exit /b 1 +REM build.bat [prog] [extra build.ps1 args] - build the editor on a fresh work disk. +REM build.bat build vim (the default program) +REM build.bat vim -Run -Keys "vim.c\njjj" build, then leave it running +powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0build.ps1" %* diff --git a/vim/build.ps1 b/vim/build.ps1 new file mode 100644 index 0000000..d7f0c80 --- /dev/null +++ b/vim/build.ps1 @@ -0,0 +1,241 @@ +# build.ps1 - build a Power C program for the C64 on a fresh work disk, then +# optionally leave it back in BASIC, loaded and running. +# +# powershell -NoProfile -File build.ps1 +# powershell -NoProfile -File build.ps1 -Run -Keys 'vim.c\njjj' +# powershell -NoProfile -File build.ps1 -Shell 'l\n' (poke at the disk) +# +# Everything happens in ONE emulator session: compile each unit, link, then +# "bye" out of the Power C shell, load the program from BASIC and run it. One +# session is not just faster, it is required: two emulators both attached to +# work.d81 write the image back over each other on exit and the files made by +# the other one disappear. +# +# Power C is interactive - cc asks which disk to insert three times, the linker +# wants one object file per ">" line, "^" for the libraries, an empty RETURN +# and then a name - so every prompt is answered from the -keybuf string. VICE +# paces those keystrokes in wall-clock milliseconds while the machine runs at +# warp, so the delay has to be long enough that a step's answers arrive after +# its prompt: cc, for one, clears the keyboard buffer when it starts. + +param( + [string]$Prog = "vim", + [string]$Keys = "", + [string]$Shell = "", + [int]$Delay = 900, # ms per keystroke; wall clock, machine is at warp + [int]$Pad = 20, # spare RETURNs while a step is working + [int]$Tail = 8, # spare RETURNs while the linker writes + [int]$Settle = 0, # extra seconds to run after the last key (screen redraw) + [switch]$Full, # ignore the stamps: fresh disk, recompile everything + [switch]$Run +) + +$ErrorActionPreference = "Stop" + +$vice = "C:\Program Files\GTK3VICE-3.10-win64\bin" +$x64 = Join-Path $vice "x64sc.exe" +$c1541 = Join-Path $vice "c1541.exe" +$here = $PSScriptRoot +$base = Join-Path $here "..\reference\POWERc.d81" +$shots = Join-Path $here "shots" + +# VICE splits "image:file" on a colon, so keep the image name bare and run from +# this folder. +Set-Location $here +$img = "work.d81" + +$units = @{ + vim = @("vim", "vscreen", "vview", "vedit", "vml") + mltest = @("mltest", "vml") +} +if (-not $units.ContainsKey($Prog)) { throw "unknown program '$Prog'" } +$unitList = $units[$Prog] + +if (-not (Test-Path $x64)) { throw "x64sc.exe not found in $vice" } +if (-not (Test-Path $base)) { throw "$base not found - is the repository complete?" } +New-Item -ItemType Directory -Force -Path $shots | Out-Null + +# ---------------------------------------------------------------- petscii + +# Power C sources live on the disk as PETSCII SEQ files with CR line endings: +# a-z -> $41-$5A, A-Z -> $C1-$DA, CRLF -> CR, and the four characters the manual +# says are typed with shift/logo keys: { | } ~ -> $DB $DC $DD $DE. +function ConvertTo-Petscii([string]$in, [string]$out) { + $bytes = [IO.File]::ReadAllBytes($in) + $start = 0 + if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and + $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) { $start = 3 } + $o = New-Object System.Collections.Generic.List[byte] + for ($i = $start; $i -lt $bytes.Length; $i++) { + $b = $bytes[$i] + if ($b -eq 13) { continue } + if ($b -eq 10) { $o.Add([byte]13); continue } + if ($b -ge 0x61 -and $b -le 0x7A) { $o.Add([byte]($b - 0x20)); continue } + if ($b -ge 0x41 -and $b -le 0x5A) { $o.Add([byte]($b + 0x80)); continue } + if ($b -ge 0x7B -and $b -le 0x7F) { $o.Add([byte]($b + 0x60)); continue } + $o.Add([byte]$b) + } + [IO.File]::WriteAllBytes($out, $o.ToArray()) + return $o.Count +} + +# ---------------------------------------------------------------- emulator + +# Quote one argument the way the Windows command line wants it. Start-Process's +# -ArgumentList joins with spaces and does not quote anything, which silently +# split "-keybuf cc vim.c\n" into two arguments, so the command line is built +# here and handed to .NET as a single string. +function Quote-Arg([string]$a) { + if ($a -ne "" -and $a -notmatch '[\s"]') { return $a } + $e = $a -replace '(\\*)"', '$1$1\"' + $e = $e -replace '(\\+)$', '$1$1' + return '"' + $e + '"' +} + +# Exactly one emulator, waited for, so nothing else touches the image. +function Invoke-C64 { + param( + [string]$KeyBuf, + [string]$Shot, + [string]$Target = "shell", + [int]$Cycles = 0, + [int]$KeyDelay = 900 + ) + if (Test-Path $Shot) { Remove-Item $Shot -Force } # VICE won't overwrite + if ($Cycles -le 0) { + $wall = ($KeyBuf.Length * $KeyDelay / 1000.0) + 15 + $Settle + $c = [long]($wall * 13000000) + if ($c -lt 150000000) { $c = 150000000 } + if ($c -gt 2000000000) { $c = 2000000000 } + $Cycles = [int]$c + } + $argv = @( + "-8", $img, + "-autostart", ($img + ":" + $Target), + "-autostart-warp", "-warp", "-sounddev", "dummy", + "-keybuf-delay", "$KeyDelay", + "-keybuf", $KeyBuf, + "-limitcycles", "$Cycles", + "-exitscreenshot", $Shot + ) + $cmdline = ($argv | ForEach-Object { Quote-Arg $_ }) -join ' ' + $psi = New-Object System.Diagnostics.ProcessStartInfo + $psi.FileName = $x64 + $psi.Arguments = $cmdline + $psi.UseShellExecute = $false + $pi = [System.Diagnostics.Process]::Start($psi) + $pi.WaitForExit() + Start-Sleep -Milliseconds 900 # let the image write settle +} + +function Get-DiskDir { + return (& $c1541 $img -list 2>$null) -join "`n" +} + +# ---------------------------------------------------------------- raw shell + +if ($Shell -ne "") { + if (-not (Test-Path $img)) { throw "$img not found - build something first" } + Invoke-C64 -KeyBuf $Shell -Shot (Join-Path $shots "shell.png") -KeyDelay $Delay + Write-Host "shots\shell.png" + exit 0 +} + +# ---------------------------------------------------------------- fresh disk + +# work.stamp remembers the hash of each .c as it was last compiled onto the work +# disk. Only units whose hash differs are converted, copied over and recompiled; +# the others keep the .o already on the disk. The linker then runs again. +$stampFile = Join-Path $here "work.stamp" +$stamps = @{} +if (Test-Path $stampFile) { + foreach ($ln in (Get-Content $stampFile)) { + $p = $ln -split '=' + if ($p.Count -eq 2) { $stamps[$p[0]] = $p[1] } + } +} +if ($Full -or -not (Test-Path $img) -or -not (Test-Path $stampFile)) { + Write-Host "== fresh work disk" + Copy-Item $base $img -Force + $stamps = @{} +} else { + Write-Host "== incremental build on $img" +} + +$changed = @() +foreach ($u in $unitList) { + $src = Join-Path $here "$u.c" + if (-not (Test-Path $src)) { throw "$src not found" } + $hash = (Get-FileHash $src -Algorithm MD5).Hash + if ($stamps[$u] -eq $hash) { continue } + $changed += $u + $pet = Join-Path $here "$u.pet" + $n = ConvertTo-Petscii $src $pet + Write-Host ("== {0}.c changed -> petscii ({1} bytes)" -f $u, $n) + # cc and the linker refuse to overwrite, so scratch the stale copies first + $null = & $c1541 $img -delete "$u.c" "$u.o" 2>$null + $null = & $c1541 $img -write $pet "$u.c,s" 2>$null + $stamps.Remove($u) # only re-stamped once its .o really exists +} +$haveProg = ((& $c1541 $img -list 2>$null) -join "`n") -match ('"' + [regex]::Escape($Prog) + '"\s+prg') +$needLink = ($changed.Count -gt 0) -or (-not $haveProg) +if ($needLink -and $haveProg) { $null = & $c1541 $img -delete $Prog 2>$null } +if ($changed.Count -eq 0) { Write-Host "== no source changed" } + +# ---------------------------------------------------------------- one session + +# cc and RETURNs for its disk prompts, over and over, then the linker: +# ">" object files, ">" up-arrow for the libraries, ">" empty to finish, the +# output name. With -Run the session goes on to leave the shell with bye, load +# the program from BASIC and type -Keys at whatever it puts on the screen. +$kb = "" +foreach ($u in $changed) { + $kb += "cc $u.c\n" + ("\n" * 3) + ("\n" * $Pad) +} +if ($needLink) { + $kb += "link -s\n" + foreach ($u in $unitList) { $kb += "$u.o\n" } + $kb += "^\n\n" + $Prog + "\n" + ("\n" * $Tail) +} +if ((-not $Run) -and ($kb -eq "")) { + Write-Host "build ok - nothing to do" + exit 0 +} +if ($Run) { + $kb += "bye\n" + ("\n" * 6) # let the shell hand back to BASIC + $kb += 'load"' + $Prog + '",8,1' + "\n" + ("\n" * 10) # let the load finish + $kb += "run\n" + $kb += $Keys +} + +$shot = if ($Run) { Join-Path $shots "run.png" } else { Join-Path $shots "build.png" } +Write-Host ("== one session: cc [{0}], {1}{2}" -f ($changed -join ' '), + $(if ($needLink) { 'link' } else { 'no link' }), + $(if ($Run) { ', bye, load, run' } else { '' })) +Invoke-C64 -KeyBuf $kb -Shot $shot -KeyDelay $Delay + +$dir = Get-DiskDir +$found = $false +foreach ($line in ($dir -split "`n")) { + if ($line -match ('"' + [regex]::Escape($Prog) + '"\s+prg')) { $found = $true } + if ($line -match ('"(' + ($unitList -join '|') + ')\.[co]"')) { + Write-Host " $line" + } +} + +# stamp a unit only once its object file is really on the disk +foreach ($u in $changed) { + if ($dir -match ('"' + [regex]::Escape($u) + '\.o"')) { + $stamps[$u] = (Get-FileHash (Join-Path $here "$u.c") -Algorithm MD5).Hash + } else { + Write-Host " $u.c produced no object - read $shot" + } +} +($stamps.GetEnumerator() | ForEach-Object { "{0}={1}" -f $_.Key, $_.Value }) | Set-Content $stampFile +if (-not $found) { throw "'$Prog' was not created - read $shot" } + +if ($Run) { + Write-Host (" {0}" -f (Split-Path $shot -Leaf)) +} else { + Write-Host "build ok - 'build.ps1 -Run -Keys ...' to try it" +} diff --git a/vim/mltest.c b/vim/mltest.c new file mode 100644 index 0000000..cad0807 --- /dev/null +++ b/vim/mltest.c @@ -0,0 +1,32 @@ +/* mltest.c - prove calling machine code from Power C, one marked step at a time. + * Whatever is left on screen when it hangs is the last step that worked. */ + +char txt[8]; +char rts[4]; + +main() +{ + char *p; + char a, x, y; + + p = (char *) 0x0400; + p[0] = 1; /* A: main started */ + + rts[0] = 96; /* RTS */ + a = 0; + x = 0; + y = 0; + p[1] = 2; /* B: about to sys() a bare RTS */ + for (;;) { } + sys(15, (unsigned) rts, &a, &x, &y); + p[2] = 3; /* C: sys + array address work */ + + txt[0] = 72; + txt[1] = 69; + txt[2] = 76; + txt[3] = 76; + txt[4] = 79; + mlrow((unsigned) txt, 5, 1104, 2, 0); + p[3] = 4; /* D: the real routine came back */ + for (;;) { } +} diff --git a/vim/mltest.pet b/vim/mltest.pet new file mode 100644 index 0000000..1e0abdd --- /dev/null +++ b/vim/mltest.pet @@ -0,0 +1 @@ +/* MLTEST.C - PROVE CALLING MACHINE CODE FROM ÐOWER Ã, ONE MARKED STEP AT A TIME. * ×HATEVER IS LEFT ON SCREEN WHEN IT HANGS IS THE LAST STEP THAT WORKED. */ CHAR TXT[8]; CHAR RTS[4]; MAIN() Û CHAR *P; CHAR A, X, Y; P = (CHAR *) 0X0400; P[0] = 1; /* Á: MAIN STARTED */ RTS[0] = 96; /* ÒÔÓ */ A = 0; X = 0; Y = 0; P[1] = 2; /* Â: ABOUT TO SYS() A BARE ÒÔÓ */ FOR (;;) Û Ý SYS(15, (UNSIGNED) RTS, &A, &X, &Y); P[2] = 3; /* Ã: SYS + ARRAY ADDRESS WORK */ TXT[0] = 72; TXT[1] = 69; TXT[2] = 76; TXT[3] = 76; TXT[4] = 79; MLROW((UNSIGNED) TXT, 5, 1104, 2, 0); P[3] = 4; /* Ä: THE REAL ROUTINE CAME BACK */ FOR (;;) Û Ý Ý \ No newline at end of file diff --git a/vim/run.bat b/vim/run.bat new file mode 100644 index 0000000..9132de1 --- /dev/null +++ b/vim/run.bat @@ -0,0 +1,10 @@ +@echo off +REM run.bat - start the editor from work.d81 in VICE. +setlocal +set "VICE=C:\Program Files\GTK3VICE-3.10-win64\bin" +cd /d "%~dp0" +if not exist work.d81 ( + echo work.d81 not found - run build.bat first + exit /b 1 +) +"%VICE%\x64sc.exe" -8 work.d81 -autostart "work.d81:vim" diff --git a/vim/shots/run.png b/vim/shots/run.png new file mode 100644 index 0000000000000000000000000000000000000000..f9165fcffa40060bc34b205fcaf6f72c480f09c6 GIT binary patch literal 2080 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 literal 0 HcmV?d00001 diff --git a/vim/to-petscii.ps1 b/vim/to-petscii.ps1 deleted file mode 100644 index 47a47a8..0000000 --- a/vim/to-petscii.ps1 +++ /dev/null @@ -1,31 +0,0 @@ -# to-petscii.ps1 - convert an ASCII C source file into the PETSCII form -# Power C expects on a C64 disk: uppercase letters shifted into $C1-$DA, -# lowercase into $41-$5A, { } into $DB/$DD, and CR ($0D) line endings. -# -# Usage: -# powershell -NoProfile -File to-petscii.ps1 vim.c vim.pet - -param( - [Parameter(Mandatory = $true)][string]$InFile, - [Parameter(Mandatory = $true)][string]$OutFile -) - -$bytes = [System.IO.File]::ReadAllBytes($InFile) -$start = 0 -if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) { - $start = 3 # skip a UTF-8 BOM if an editor added one -} - -$out = New-Object System.Collections.Generic.List[byte] -for ($i = $start; $i -lt $bytes.Length; $i++) { - $b = $bytes[$i] - if ($b -eq 13) { continue } # CRLF -> single CR - if ($b -eq 10) { $out.Add([byte]13); continue } - if ($b -ge 0x61 -and $b -le 0x7A) { $out.Add([byte]($b - 0x20)); continue } # a-z - if ($b -ge 0x41 -and $b -le 0x5A) { $out.Add([byte]($b + 0x80)); continue } # A-Z - if ($b -ge 0x7B -and $b -le 0x7F) { $out.Add([byte]($b + 0x60)); continue } # { | } ~ -> $DB-$DF - $out.Add([byte]$b) -} - -[System.IO.File]::WriteAllBytes($OutFile, $out.ToArray()) -Write-Host ("wrote {0} PETSCII bytes to {1}" -f $out.Count, $OutFile) diff --git a/vim/vedit.c b/vim/vedit.c new file mode 100644 index 0000000..1b3f457 --- /dev/null +++ b/vim/vedit.c @@ -0,0 +1,224 @@ +/* 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; /* S (petscii, not ascii) */ + on[i++] = 44; /* , */ + on[i++] = 87; /* W */ + 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; +} + diff --git a/vim/vedit.pet b/vim/vedit.pet new file mode 100644 index 0000000..8165488 --- /dev/null +++ b/vim/vedit.pet @@ -0,0 +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 diff --git a/vim/vim.c b/vim/vim.c index 3b92655..689f413 100644 --- a/vim/vim.c +++ b/vim/vim.c @@ -1,420 +1,345 @@ -/* vim.c - a small vi/vim-style screen editor for the Commodore 64 +/* vim.c - a small modal editor for the commodore 64, written for power c. * - * 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. + * Increment 2 edits: it reads a petscii SEQ 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 G + * insert i a A I o O (RUN/STOP leaves insert mode, DEL backspaces) + * change x dd p + * colon :w :w name :wq :x :q :q! * - * 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 ||. + * The buffer holds the file exactly as it sits on disk: petscii bytes with $0d + * between lines. Keys are compared as numbers, never as character constants, + * so nothing depends on how the compiler treats literals. (Unshifted letters + * arrive as $41-$5a, so h = 72; shifted ones as $c1-$da, so G = 199.) */ -#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 */ +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; - 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; + while (fname[i] && i < n) i++; + fname[i] = 0; + return 0; } -static intend(o) /* offset of the line end */ -int o; +/* a message that waits for a key */ +msg(s) +char *s; { - int e; - - e = o; - while (e < tlen && text[e] != 13) e = e + 1; - return e; + sclr(24, 0, 40); + sps(24, 0, s); + skey(); + return 0; } -static inschr(ch) -int ch; +/* 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 i; + int n, k; - if (tlen >= 1999) { - msgcode = 1; - return; + 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); } - for (i = tlen; i > pos; i--) - text[i] = text[i - 1]; - text[pos] = ch; - tlen = tlen + 1; - pos = pos + 1; + return n; } -static delat() +/* ask which file to open */ +askname() { - 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; + sclr(24, 0, 40); + sps(24, 0, "file: "); + if (getln(6, fname, 20) == 0) fname[0] = 0; + return 0; } -static linehome() +/* read fname into bfr. 1 on success, 0 if it could not be opened */ +loadf() { - pos = lstart(lnof()); -} - -static lineend() -{ - pos = intend(lstart(lnof())); -} - -/* ---------- saving ---------- */ - -static savfil() -{ - FILE *f; + int f, ch, prev, c; + char on[28]; int i; - f = fopen(fname, "w"); - if (!f) { - msgcode = 2; - return; + if (fname[0] == 0) return 1; + i = 0; + while (fname[i] && i < 20) { + on[i] = fname[i]; + i++; } - if (ferror(f)) { - msgcode = 2; - fclose(f); - return; + on[i++] = 44; /* , */ + on[i++] = 83; /* S (petscii, not ascii) */ + on[i++] = 44; /* , */ + on[i++] = 82; /* R */ + 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; } - for (i = 0; i < tlen; i++) fputc(text[i], f); - fclose(f); - msgcode = 3; + close(5); + return 1; } -/* ---------- : command line ---------- */ - -static docmd() +/* find the start of every line */ +buildidx() { - if (cmdlen == 0) return; - if (cmd[0] == 'w') { - if (cmdlen > 1 && cmd[1] == 'q') { - savfil(); - quit = 1; - return; + 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++; + } } - savfil(); - return; - } - if (cmd[0] == 'q') { - quit = 1; - return; } - msgcode = 4; + return 0; } -/* ---------- key handlers ---------- */ - -static insk(k) -int k; +/* the ":" command line. returns 1 when the editor should quit */ +doscmd() { - if (k == 3) { /* ESC: leave insert mode */ - mode = 0; - if (pos > 0) pos = pos - 1; - return; + 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; } - 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; + 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 (k == 'i') { mode = 1; return; } - if (k == 'a') { - if (pos < tlen && text[pos] != 13) pos = pos + 1; - mode = 1; - return; + if (ok == 0) { + note = "unknown command"; + return 0; } - if (k == 'o') { - pos = intend(lstart(lnof())); - inschr(13); - mode = 1; - return; + if (cmd[0] == 87 && cmd[1] == 81) { + qf = 1; + p = 2; } - if (k == 'O') { - pos = lstart(lnof()); - inschr(13); - pos = pos - 1; - mode = 1; - return; + while (cmd[p] == 32) p++; + if (cmd[p]) { + n = 0; + while (cmd[p] && n < 20) fname[n++] = cmd[p++]; + fname[n] = 0; } - 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; + if (fname[0] == 0) { + note = "no file name"; 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; + if (savef()) { + dirty = 0; + note = "written"; + return qf; } + note = "cannot write"; + return 0; } -/* ---------- main loop ---------- */ - main() { - int k; - int i; + int k, ok, p; + + sinit(); + mlinit(); /* machine code row painter */ + bfr = malloc(19000); + if (bfr == 0) { + sps(0, 0, "out of memory"); + skey(); + return; + } - tlen = 0; - pos = 0; + blen = 0; + nlines = 1; + lptr[0] = 0; + cy = 0; + cx = 0; + top = 0; + leftcol = 0; + fname[0] = 0; mode = 0; - cmdlen = 0; - quit = 0; - msgcode = 0; - - i = 0; - while (defnam[i]) { - fname[i] = defnam[i]; - i = i + 1; + 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"; } - fname[i] = 0; - while (!quit) { - draw(text, tlen, pos, mode, fname, cmd, cmdlen, msgcode); - msgcode = 0; - k = waitkey(); - if (mode == 2) { - if (k == 13) { + for (;;) { + scroll(); + dall(); + k = skey(); + note = 0; + + if (mode) { /* ---- insert mode */ + if (k == 3) { /* RUN/STOP = Esc */ 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; + 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 (mode == 1) { - insk(k); + + 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; } - cmdk(k); - } - clrscr(); - printf("bye\n"); + 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) { /* G */ + 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) { /* A */ + cx = linend(cy) - lptr[cy]; + mode = 1; + } else if (k == 201) { /* I */ + cx = 0; + mode = 1; + } else if (k == 79) { /* o */ + cx = linend(cy) - lptr[cy]; + newline(); + mode = 1; + } else if (k == 207) { /* O */ + cx = 0; + newline(); + cy--; + mode = 1; + } else if (k == 58) { /* : */ + if (doscmd() == 1) { + sdone(); + return; + } + } + } } diff --git a/vim/vim.pet b/vim/vim.pet new file mode 100644 index 0000000..c957569 --- /dev/null +++ b/vim/vim.pet @@ -0,0 +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 diff --git a/vim/vml.c b/vim/vml.c new file mode 100644 index 0000000..1e9d418 --- /dev/null +++ b/vim/vml.c @@ -0,0 +1,46 @@ +/* vml.c - GENERATED by build-ml.ps1 from vrow.s. Do not edit by hand. + * + * 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. + */ + +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(15, (unsigned) mlcode, &a, &x, &y); + return 0; +} diff --git a/vim/vml.pet b/vim/vml.pet new file mode 100644 index 0000000..3fe6377 --- /dev/null +++ b/vim/vml.pet @@ -0,0 +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(15, (UNSIGNED) MLCODE, &A, &X, &Y); RETURN 0; Ý \ No newline at end of file diff --git a/vim/vrow.bin b/vim/vrow.bin new file mode 100644 index 0000000000000000000000000000000000000000..491ff514bf1332fa725387d169a39651f0fbe847 GIT binary patch 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 literal 0 HcmV?d00001 diff --git a/vim/vrow.s b/vim/vrow.s new file mode 100644 index 0000000..810e49f --- /dev/null +++ b/vim/vrow.s @@ -0,0 +1,69 @@ +; 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 f2d9cfa..d1cd458 100644 --- a/vim/vscreen.c +++ b/vim/vscreen.c @@ -1,248 +1,165 @@ -/* vscreen.c - screen half of the vim editor for the C64 (Power C) +/* vscreen.c - screen and keyboard for the c64 vim clone. * - * Draws the text window, the status line and the cursor straight into - * the screen matrix at $0400 and colour RAM at $d800. Kept in its own - * file because Power C overflows its compiler when the whole editor is - * one source. + * The 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. * - * This module owns no program state: draw() receives everything it - * needs from vim.c as arguments. Sharing variables between Power C - * object files proved unreliable, so only functions cross the boundary. + * Everything here returns int, so the other compilation unit can call these + * without declaring them (power c is K&R C). */ -static char stat[40]; /* status line build buffer */ -static int slen; +char *scr; /* screen matrix, $0400 */ +char *colr; /* colour ram, $d800 */ -/* ---------- status line text ---------- */ - -static sclr() -{ - slen = 0; - stat[0] = 0; -} - -static sadd(c) -char c; -{ - if (slen < 39) { - stat[slen] = c; - slen = slen + 1; - stat[slen] = 0; - } -} - -static sstr(s) -char *s; +/* petscii byte -> screen code */ +s2v(p) +int p; { - while (*s) { - sadd(*s); - s = s + 1; - } + 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; } -static snum(n) -int n; +/* raw screen code at row r, column c */ +sraw(r, c, v) +int r, c, v; { - char b[6]; int i; - if (n < 0) { - sadd('-'); - n = -n; - } - if (n == 0) { - sadd('0'); - return; - } - i = 0; - while (n > 0) { - b[i] = '0' + (n % 10); - i = i + 1; - n = n / 10; - } - while (i > 0) { - i = i - 1; - sadd(b[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; } -/* ---------- PETSCII byte -> screen code ---------- */ - -static sccode(c) -int c; +/* one petscii character */ +spc(r, c, p) +int r, c, p; { - if (c >= 193 && c <= 218) return c - 192; /* shifted letters $C1-$DA */ - if (c >= 96 && c <= 127) return c - 32; - if (c >= 64 && c <= 95) return c - 64; - /* the C-only characters have no glyph of their own in the ROM */ - if (c == 219) return 91; /* { -> [ */ - if (c == 221) return 93; /* } -> ] */ - if (c == 223) return 94; /* | -> up arrow */ - if (c == 175) return 126; /* ~ -> its graphics glyph */ - if (c == 164) return 95; /* _ -> left arrow */ - return c; + sraw(r, c, s2v(p)); + return 0; } -/* ---------- screen output ---------- */ - -clrscr() +/* one petscii character, inverted: the cursor and the status row */ +srpc(r, c, p) +int r, c, p; { - char *s; - char *m; - int i; - - s = (char *) 1024; - m = (char *) 55296; - for (i = 0; i < 1000; i++) { - s[i] = 32; - m[i] = 1; - } + sraw(r, c, s2v(p) + 128); + return 0; } -static putchr(x, y, ch, c) -int x; -int y; -int ch; -int c; +/* a reversed space, for the cursor sitting past the end of a line */ +sblk(r, c) +int r, c; { - char *s; - char *m; - int o; - - if (x < 0) return; - if (x > 39) return; - if (y < 0) return; - if (y > 24) return; - o = y * 40 + x; - s = (char *) 1024; - m = (char *) 55296; - s[o] = ch; - m[o] = c; + sraw(r, c, 160); + return 0; } -static putstr(x, y, s, c) -int x; -int y; +/* a zero terminated petscii string */ +sps(r, c, s) +int r, c; char *s; -int c; { - while (*s && x < 40) { - putchr(x, y, sccode(*s), c); - x = x + 1; - s = s + 1; - } + while (*s) spc(r, c++, *s++); + return 0; } -static revchr(o) /* reverse the character in a cell */ -int o; +/* n blanks from (r,c) */ +sclr(r, c, n) +int r, c, n; { - char *s; - - s = (char *) 1024; - if (s[o] < 128) s[o] = s[o] + 128; + while (n-- > 0) sraw(r, c++, 32); + return 0; } -/* ---------- buffer scans over the caller's text ---------- */ - -static lstartx(tp, len, n) /* offset of the first byte of line n */ -char *tp; -int len; -int n; +/* take over the machine: black on blue, lower case, no blinking cursor */ +sinit() { int i; - int c; - - if (n <= 0) return 0; - c = 0; - i = 0; - while (i < len) { - if (tp[i] == 13) { - c = c + 1; - if (c == n) return i + 1; - } - i = i + 1; + 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 len; + return 0; } -static lnofx(tp, cur) /* line holding the cursor */ -char *tp; -int cur; +/* give the machine back the way basic expects it */ +sdone() { int i; - int c; + char *p; + + p = (char *) 0xd018; + *p = 0x15; + p = (char *) 0xd020; + *p = 14; + p = (char *) 0xd021; + *p = 6; + for (i = 0; i < 1000; i++) { + scr[i] = 32; + colr[i] = 1; + } + return 0; +} - c = 0; - for (i = 0; i < cur; i++) - if (tp[i] == 13) c = c + 1; - return c; +/* ---------------------------------------------------------------- keyboard + * + * Keys come straight out of the kernal's keyboard buffer: $c6 says how many + * are waiting and they queue from $0277. The 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. + * + * Two obvious alternatives do not work: calling the kernal's GETIN through + * sys() crashed the program, and the library's getchar() is line buffered - it + * echoes through the screen editor and only hands characters over after RETURN. + */ + +/* 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; } -/* ---------- full redraw ---------- */ - -draw(tp, len, cur, md, name, cp, cl, mc) -char *tp; -int len; -int cur; -int md; -char *name; -char *cp; -int cl; -int mc; +/* wait for a key */ +skey() { - int ln; - int top; - int r; - int i; - int to; - int c; - int cs; - int cr; - - ln = lnofx(tp, cur); - top = ln - 11; - if (top < 0) top = 0; - - clrscr(); - - to = lstartx(tp, len, top); - for (r = 0; r < 23; r++) { - i = 0; - while (to < len && i < 40) { - c = tp[to]; - if (c == 13) break; - putchr(i, r, sccode(c), 1); - i = i + 1; - to = to + 1; - } - while (to < len && tp[to] != 13) to = to + 1; - if (to < len) to = to + 1; - } + int k; - for (i = 0; i < 40; i++) putchr(i, 24, 32, 11); - - sclr(); - if (md == 1) sstr("INSERT"); - else if (md == 2) sstr(": "); - else sstr("COMMAND"); - sstr(" line "); - snum(ln + 1); - sstr(" "); - sstr(name); - if (mc == 1) sstr(" full"); - else if (mc == 2) sstr(" save err"); - else if (mc == 3) sstr(" saved"); - else if (mc == 4) sstr(" ?cmd"); - putstr(0, 24, stat, 11); - - if (md == 2) { - putchr(0, 23, sccode(':'), 1); - for (i = 0; i < cl; i++) putchr(i + 1, 23, sccode(cp[i]), 1); + for (;;) { + k = skeyp(); + if (k != 0) return k; } - - cs = cur - lstartx(tp, len, ln); - if (cs > 39) cs = 39; - if (cs < 0) cs = 0; - cr = ln - top; - if (cr >= 0 && cr < 23) revchr(cr * 40 + cs); } diff --git a/vim/vscreen.pet b/vim/vscreen.pet new file mode 100644 index 0000000..f2720b9 --- /dev/null +++ b/vim/vscreen.pet @@ -0,0 +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() Û INT I; CHAR *P; P = (CHAR *) 0XD018; *P = 0X15; P = (CHAR *) 0XD020; *P = 14; P = (CHAR *) 0XD021; *P = 6; FOR (I = 0; I < 1000; I++) Û SCR[I] = 32; COLR[I] = 1; Ý 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. * * ÔWO OBVIOUS ALTERNATIVES DO NOT WORK: CALLING THE KERNAL'S ÇÅÔÉÎ THROUGH * SYS() CRASHED THE PROGRAM, AND 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 diff --git a/vim/vview.c b/vim/vview.c new file mode 100644 index 0000000..1d10f38 --- /dev/null +++ b/vim/vview.c @@ -0,0 +1,109 @@ +/* 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 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() +{ + 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 */ diff --git a/vim/vview.pet b/vim/vview.pet new file mode 100644 index 0000000..2df3f6f --- /dev/null +++ b/vim/vview.pet @@ -0,0 +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 diff --git a/vim/vim.d81 b/vim/work.d81 similarity index 88% rename from vim/vim.d81 rename to vim/work.d81 index cc474783ee3c85a944d7b0e7a6f0792a9dd85b99..62109cd208741e37327c96b8ef11a6db2964746a 100644 GIT binary patch delta 78041 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�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 diff --git a/vim/work.stamp b/vim/work.stamp new file mode 100644 index 0000000..5eb49e8 --- /dev/null +++ b/vim/work.stamp @@ -0,0 +1,6 @@ +vim=8E13FCED4A0CAFD92855B9281D268368 +vscreen=FFCDAD177DF3AD43CE2809EA7ACD6029 +vview=F3C7D092B8080C956D9EBD7B95800CD5 +mltest=E4BDE10600DE08AF766FFB32A6D9A3E5 +vedit=8BE6C95F2BDA01EC535A8C51CD666881 +vml=D600B54B6821800D24B2986D920E9B12