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

Update vim editor: split into modules, add build scripts and notes

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
main
Daniel Covington 1 неделю назад
Родитель
Сommit
0e735d3e4b
25 измененных файлов: 1375 добавлений и 668 удалений
  1. +3
    -2
      AGENTS.md
  2. +137
    -0
      vim/NOTES.md
  3. +81
    -0
      vim/build-ml.ps1
  4. +4
    -70
      vim/build.bat
  5. +241
    -0
      vim/build.ps1
  6. +32
    -0
      vim/mltest.c
  7. +1
    -0
      vim/mltest.pet
  8. +10
    -0
      vim/run.bat
  9. Двоичные данные
      vim/shots/run.png
  10. Двоичные данные
      vim/shots/shell.png
  11. +0
    -31
      vim/to-petscii.ps1
  12. +224
    -0
      vim/vedit.c
  13. +1
    -0
      vim/vedit.pet
  14. +285
    -360
      vim/vim.c
  15. +1
    -0
      vim/vim.pet
  16. +46
    -0
      vim/vml.c
  17. +1
    -0
      vim/vml.pet
  18. Двоичные данные
      vim/vrow.bin
  19. +69
    -0
      vim/vrow.s
  20. +122
    -205
      vim/vscreen.c
  21. +1
    -0
      vim/vscreen.pet
  22. +109
    -0
      vim/vview.c
  23. +1
    -0
      vim/vview.pet
  24. Двоичные данные
      vim/work.d81
  25. +6
    -0
      vim/work.stamp

+ 3
- 2
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

---



+ 137
- 0
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 <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.

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


+ 4
- 70
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" %*

+ 241
- 0
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 <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"
}

+ 32
- 0
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 (;;) { }
}

+ 1
- 0
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 (;;) Û Ý Ý

+ 10
- 0
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"

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

До После
Ширина: 384  |  Высота: 272  |  Размер: 2.0KB

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

До После
Ширина: 384  |  Высота: 272  |  Размер: 3.2KB

+ 0
- 31
vim/to-petscii.ps1 Просмотреть файл

@@ -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)

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


+ 1
- 0
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; Ý

+ 285
- 360
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 <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;
}
}
}
}

+ 1
- 0
vim/vim.pet
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


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

+ 1
- 0
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; Ý

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


+ 69
- 0
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

+ 122
- 205
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);
}

+ 1
- 0
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; Ý Ý

+ 109
- 0
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 */

+ 1
- 0
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 */

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


+ 6
- 0
vim/work.stamp Просмотреть файл

@@ -0,0 +1,6 @@
vim=8E13FCED4A0CAFD92855B9281D268368
vscreen=FFCDAD177DF3AD43CE2809EA7ACD6029
vview=F3C7D092B8080C956D9EBD7B95800CD5
mltest=E4BDE10600DE08AF766FFB32A6D9A3E5
vedit=8BE6C95F2BDA01EC535A8C51CD666881
vml=D600B54B6821800D24B2986D920E9B12

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

Powered by TurnKey Linux.