First Phase A session. pmacs-gpu now sends FrontendEvent::Viewport
back to the daemon after BufferSnapshot lands, receives the resulting
InstanceMessage::StyleSpans frames, and renders the rope with
per-span colors via cosmic-text's set_rich_text.
What's wired:
- AttachClient gains a write-side Arc<Mutex<UnixStream>> and the
assigned FrontendId from Hello; new send_viewport method emits
FrontendEvent::Viewport. The mutex is over-cautious for our
single-threaded event loop but future-proofs against multi-window
emission.
- State adds current_buffer_id and current_spans (sorted by
range.start). BufferSnapshot bootstraps both, then the App's
user_event handler emits a follow-up Viewport via
AttachClient::send_viewport.
- StyleSpans handling distinguishes full vs incremental:
* full=true: replace_style_spans drops prior, takes segments as
authoritative for the declared viewport.
* full=false: merge_style_spans applies the M11.4 dirty-segment
rule — spans fully inside a dirty range drop; spans straddling
a dirty edge get clipped to outside the range (with the
straddles-both-edges case splitting into two); new spans append;
re-sort by start.
- reshape() walks current_text + current_spans, emits (substr,
Attrs) chunks at every span boundary (with .min(text_len) clamps
for safety against stale spans past EOF), calls set_rich_text.
- cell_color_to_glyphon converts cell::Color to glyphon::Color via
the standard xterm-style 256-color palette (16 ANSI + 6x6x6 cube +
24-step grayscale). Default → None so the renderer's default
Attrs color stays.
Adversarial verification scope (Phase A probes):
- #1 non-ASCII source: exercised through the UTF-16 col/byte
conversion already in pmacs's producer side; pmacs-gpu just renders
what the wire delivers. Non-ASCII files should show correct
styling at the right byte positions.
- #3 viewport-boundary tokens: the merge_style_spans path is exactly
bet #1 from the framing pass ('StyleSpans/Decorations dirty-segment
edges at viewport boundaries — headless-test-blind-spot probe').
Edits near a span edge exercise the clip-and-merge logic.
- #6 CRLF line endings: implicit — pmacs's rope uses byte offsets so
styling spans naturally include or exclude the \r as the producer
decided. pmacs-gpu doesn't special-case line endings.
Known limitation (session 4 acceptable artifact, documented in
set_text): CrdtOp + StyleSpans arrive separately. CrdtOp updates text;
StyleSpans for the new generation comes one tick later. Between the
two, current_spans points at pre-edit byte positions while the text
is post-edit — visually stale for one frame. The .min(text_len) clamp
in reshape() keeps it safe; the artifact is brief.
Headless test gap: there's no Rust-level test of merge_style_spans
or the rich-text segmentation. Phase A's framing intentionally
chose manual validation over headless tests for these paths (the
adversarial probes are visual). A Phase A audit doc lands at session
close with the predicted-vs-actual scoring; per-method unit tests
for the merge logic could land then if findings argue for them.
Gates: fmt; clippy --all-targets -D warnings clean across the whole
workspace; lib 1303 + pmacs-protocol 11 = 1314; m4_acceptance 83;
m11_5_semantic_acceptance --features crdt 2.
Manual validation pending — same daemon+TUI+pmacs-gpu setup as
session 3, now showing colored text in the GPU window.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .github/workflows | ||
| audit | ||
| builtin | ||
| docs | ||
| pmacs-gpu | ||
| pmacs-protocol | ||
| proptest-regressions | ||
| src | ||
| tests | ||
| .gitignore | ||
| CHANGELOG.md | ||
| Cargo.lock | ||
| Cargo.toml | ||
| LICENSE-APACHE | ||
| LICENSE-MIT | ||
| README.md | ||
| build.rs | ||
| rust-toolchain.toml | ||
| rustfmt.toml | ||
README.md
Pmacs
Parallel Emacs --- a Rust-cored, Lua-scripted editor in the Emacs tradition.
Pmacs runs the editor's hot path (rope, buffers, views, async runtime, process supervision) in Rust, and exposes the rest --- commands, keymaps, hooks, packages --- through an embedded Lua VM. The design follows Emacs in shape (configurable, introspectable, programmable from inside) but discards the single-threaded substrate; workers, message bus, and a coroutine-based async surface are core primitives, not bolt-ons.
The editor is partitioned into a long-lived instance (the daemon
that owns buffers, processes, and language services) and a thin
frontend that attaches over a typed protocol. Frontends can run
locally over a Unix socket or remotely over SSH; reconnect-on-drop
modeled on mosh keeps remote sessions alive across laptop suspends.
The first-class package is a REPL package written entirely against the public Lua API: PTY-spawned shells (bash, zsh, fish, lua), an ECMA-48 ANSI parser, multi-REPL coexistence, and scrollback management with line/byte retention. Successful completion of an audit verifying the package uses zero direct Rust core access was the v0.1 ship gate.
Status
v1.0.0 --- stable. The design described above is implemented and working. Solo development carried the project to 1.0; public contributions are open from this release. Use, evaluate, file issues, and send pull requests.
Build
Builds on the toolchain pinned in rust-toolchain.toml (Rust
1.95.0, edition 2024); rustup selects it automatically. Lua flavor selectable
between luajit (default) and lua54; both pass the full test suite.
cargo build --release # produce target/release/pmacs
cargo run --release -- <file> # build and run on a file
cargo test # unit + integration tests
cargo fmt --check
cargo clippy --all-targets -- -D warnings
Release-only perf gates (M5 keystroke-to-render, M6 ingest/RSS/cancel
and scrollback navigation/search) are #[ignore]'d during normal
test runs and exercised in CI under dedicated jobs.
Runtime requirements
The pmacs binary depends on a small set of POSIX command-line tools
at runtime. The dependency exists because the project enforces
#![forbid(unsafe_code)] everywhere, including in tests; calls that
would otherwise need unsafe (PTY raw-mode setup, signal name
translation) are routed through trampolines that exec these tools.
/bin/sh(POSIX shell). Used for the PTY raw-mode trampoline:/bin/sh -c 'stty raw -echo </dev/tty 2>/dev/null; exec "$@"' --configures the controlling TTY's line discipline before exec'ing the actual subprocess. Required by the REPL package and any other caller that spawns a process in raw PTY mode.stty(coreutils). The line-discipline configurator invoked by the trampoline above.coreutilsmore broadly. The M6 process-supervisor tests spawncat,yes, andwhich; absent these the test suite (not the editor itself) degrades.whichis also used by the M6.5 shell-locator helper to findbash/zsh/fishfor per-shell integration tests. The M7.2 fetcher's timeout test usessleep.git(added in M7.2). Required for any package operation: the package fetcher shells out togitto clone, fetch, and resolve refs, with a deterministic environment (GIT_TERMINAL_PROMPT=0,GIT_CONFIG_NOSYSTEM=1,LC_ALL=C, inheritedGIT_*variables stripped). Authentication for private repositories rides the user's existing git configuration (credential helpers, SSH agent), so packagers do not need a separate auth story. Pre-M7 builds without package operations do not need git.tar(added in M7.3). Required forpmacs.packages.install: the installer materializes a snapshot viagit archive --format=tarpiped intotar -x -C <dest>, which keeps the on-disk install directory self-contained (no.gitlinkage back to the bare cache, no working-tree state). GNU tar and bsdtar both work. Pre-M7 builds and any path that doesn't callpmacs.packages.install{...}do not need tar.
Distribution packagers should ensure these are runtime dependencies
of the pmacs package. On a typical Linux distribution, busybox or
GNU coreutils plus a shell of any kind satisfies the requirement; on
macOS the system shell and /usr/bin/stty are both standard.
The Lua VM (LuaJIT or Lua 5.4) is statically vendored via mlua's
vendored feature, so there is no external Lua dependency at
runtime.
What v0.1 ships with
- Editor core. Persistent rope with O(log N) edits and snapshots; buffers with chained intercept-views; undo/redo; atomic file I/O; crossterm-driven TUI.
- Lua surface. Embedded LuaJIT (or Lua 5.4) with
pmacs.command,pmacs.keymap(global / mode / buffer scopes),pmacs.hook(typed kinds: all-must-succeed, first-non-nil, last-write-wins),pmacs.buffer,pmacs.window,pmacs.editor. Minibuffer is itself a buffer.describe-keyanddescribe-commandfor self-introspection. - Async runtime. Worker pool + message bus + coroutine-based Lua
async surface (
pmacs.async). Cancellation is provably correct under load. - Language services. Tree-sitter highlighting and LSP integration ride the worker/message infrastructure. Project indexing as a third service. Symbol search across 1M+ symbols completes under a second.
- Frontend partition. Daemon mode with local Unix-socket transport; cell-delta diffing on the instance side; SSH transport variant for remote attach; reconnect-on-drop preserves session state across laptop suspend / network drop.
- REPL package. A 691-line Lua package that wires the M6 ANSI parser to PTY-spawned shells with raw-mode line discipline. Three- region buffer (history / prompt / input) with read-only enforcement; RET / C-c / C-d bindings; multi-REPL coexistence; scrollback retention with line- and byte-bounded truncation. Published alongside an audit verifying zero direct Rust core access.
Layout
src/ Rust core
rope.rs persistent byte-sequence backing every buffer
buffer.rs buffer + view chain + undo/redo
editor_core.rs cursor + commands + edit dispatch
async_runtime.rs worker pool + message bus
process.rs PTY-aware process supervisor
ansi.rs ECMA-48 parser
daemon.rs instance side of the frontend partition
attach.rs frontend side; protocol + reconnect
lsp.rs language-server client
syntax.rs tree-sitter integration
project_index.rs symbol / file indexing
text_view.rs cell-grid renderer
frontend.rs crossterm TUI
lua_bindings.rs pmacs.* Lua surface installers
main.rs entry point (TUI + daemon modes)
builtin/ Lua runtime shipped with the binary
commands/default.lua named commands for every editor primitive
keymaps/default.lua default key bindings
hooks/default.lua built-in hook definitions
runtime/ packages (async, lsp, repl, syntax)
tests/ integration tests (acceptance gates per milestone)
License
Dual-licensed under either of:
- MIT License (LICENSE-MIT)
- Apache License, Version 2.0 (LICENSE-APACHE)
at your option.