Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>main
| @@ -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 | |||
| --- | |||
| @@ -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 <RETURN><RETURN><RETURN> ... padding | |||
| cc vscreen.c <RETURN><RETURN><RETURN> ... 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 <stdio.h>`; 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. | |||
| @@ -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) | |||
| @@ -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" %* | |||
| @@ -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 <unit> 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" | |||
| } | |||
| @@ -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 (;;) { } | |||
| } | |||
| @@ -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 (;;) Û Ý Ý | |||
| @@ -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" | |||
| @@ -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) | |||
| @@ -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; | |||
| } | |||
| @@ -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; Ý | |||
| @@ -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 <stdio.h> | |||
| 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; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| @@ -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; Ý | |||
| @@ -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 | |||
| @@ -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); | |||
| } | |||
| @@ -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; Ý Ý | |||
| @@ -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 */ | |||
| @@ -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 */ | |||
| @@ -0,0 +1,6 @@ | |||
| vim=8E13FCED4A0CAFD92855B9281D268368 | |||
| vscreen=FFCDAD177DF3AD43CE2809EA7ACD6029 | |||
| vview=F3C7D092B8080C956D9EBD7B95800CD5 | |||
| mltest=E4BDE10600DE08AF766FFB32A6D9A3E5 | |||
| vedit=8BE6C95F2BDA01EC535A8C51CD666881 | |||
| vml=D600B54B6821800D24B2986D920E9B12 | |||
Powered by TurnKey Linux.