docs: add cross-machine active-work handoff
Add synchronized agent bootstraps and a volatile active-work ledger so another machine can distinguish durable project state from open branches, local checkpoints, machine-only providers, and incomplete verification. Record githubsucks/main as the canonical development line, refresh the #124/protocol-v17 handoff, correct the current keybinding reference for compile mode, and mark the July roadmap as a historical snapshot.
This commit is contained in:
parent
f8096ff826
commit
968e01ae6a
|
|
@ -0,0 +1,33 @@
|
|||
# pmacs agent instructions
|
||||
|
||||
**Start here: read `docs/agent-handoff.md`, then
|
||||
`docs/active-work.md`, before taking on any work.** The handoff carries
|
||||
durable project state, working method, substrate invariants, and the
|
||||
standing backlog. The active-work ledger carries volatile branches,
|
||||
checkpoints, verification, and exact cross-machine recovery commands.
|
||||
Keep both updated according to their own update protocols.
|
||||
|
||||
Always true, independent of the handoff:
|
||||
|
||||
- Rust core + Lua runtime (`builtin/runtime/*.lua`), TUI + GPU
|
||||
(`pmacs-gpu`) frontends over a versioned semantic protocol
|
||||
(`pmacs-protocol`). `#![forbid(unsafe_code)]`.
|
||||
- Workflow: framing doc in `docs/` -> user approval -> branch -> implement
|
||||
-> full gate suite -> PR -> user review rounds -> user says when to
|
||||
merge. Never merge unprompted. One feature, one branch, one PR.
|
||||
- Gates before any PR: `cargo fmt --check`; `cargo clippy --workspace
|
||||
--all-targets -- -D warnings` (as its own step); `cargo test --lib`;
|
||||
`cargo test --lib --features crdt`; the touched acceptance suites;
|
||||
`cargo test --test m4_acceptance -- --skip basedpyright`;
|
||||
`PMACS_REQUIRE_GPU=1 cargo test -p pmacs-gpu`; `git diff --check`.
|
||||
- The checkout may be shared with the user: check `git status` for
|
||||
foreign uncommitted work before stash, checkout, or branch operations,
|
||||
and never delete untracked files you did not create.
|
||||
- `githubsucks/main` is the canonical development line. `origin/main`
|
||||
is a release mirror and may lag substantially; never base new work on
|
||||
it without comparing both refs.
|
||||
- Work is portable only after it is committed and pushed. Uncommitted
|
||||
worktree changes, untracked files, and `/tmp` dependencies do not
|
||||
travel to another machine.
|
||||
- Write commit messages with `git commit -F <file>`. Never use
|
||||
`git add .`.
|
||||
31
CLAUDE.md
31
CLAUDE.md
|
|
@ -1,18 +1,19 @@
|
|||
# pmacs — agent instructions
|
||||
# pmacs agent instructions
|
||||
|
||||
**Start here: read `docs/agent-handoff.md` before taking on any work.**
|
||||
It carries current project state, the working method, substrate
|
||||
invariants, and the standing backlog — it is the continuity bridge
|
||||
between development machines. Keep it updated as part of your work
|
||||
(update protocol is in the file itself).
|
||||
**Start here: read `docs/agent-handoff.md`, then
|
||||
`docs/active-work.md`, before taking on any work.** The handoff carries
|
||||
durable project state, working method, substrate invariants, and the
|
||||
standing backlog. The active-work ledger carries volatile branches,
|
||||
checkpoints, verification, and exact cross-machine recovery commands.
|
||||
Keep both updated according to their own update protocols.
|
||||
|
||||
Always true, independent of the handoff:
|
||||
|
||||
- Rust core + Lua runtime (`builtin/runtime/*.lua`), TUI + GPU
|
||||
(`pmacs-gpu`) frontends over a versioned semantic protocol
|
||||
(`pmacs-protocol`). `#![forbid(unsafe_code)]`.
|
||||
- Workflow: framing doc in `docs/` → user approval → branch → implement
|
||||
→ full gate suite → PR → user's review rounds → user says when to
|
||||
- Workflow: framing doc in `docs/` -> user approval -> branch -> implement
|
||||
-> full gate suite -> PR -> user review rounds -> user says when to
|
||||
merge. Never merge unprompted. One feature, one branch, one PR.
|
||||
- Gates before any PR: `cargo fmt --check`; `cargo clippy --workspace
|
||||
--all-targets -- -D warnings` (as its own step); `cargo test --lib`;
|
||||
|
|
@ -20,7 +21,13 @@ Always true, independent of the handoff:
|
|||
`cargo test --test m4_acceptance -- --skip basedpyright`;
|
||||
`PMACS_REQUIRE_GPU=1 cargo test -p pmacs-gpu`; `git diff --check`.
|
||||
- The checkout may be shared with the user: check `git status` for
|
||||
foreign uncommitted work before stash/checkout/branch operations, and
|
||||
never delete untracked files you didn't create.
|
||||
- Commit messages via `git commit -F <file>`, ending with the Claude
|
||||
co-author line; PR bodies end with the Claude Code attribution.
|
||||
foreign uncommitted work before stash, checkout, or branch operations,
|
||||
and never delete untracked files you did not create.
|
||||
- `githubsucks/main` is the canonical development line. `origin/main`
|
||||
is a release mirror and may lag substantially; never base new work on
|
||||
it without comparing both refs.
|
||||
- Work is portable only after it is committed and pushed. Uncommitted
|
||||
worktree changes, untracked files, and `/tmp` dependencies do not
|
||||
travel to another machine.
|
||||
- Write commit messages with `git commit -F <file>`. Never use
|
||||
`git add .`.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,169 @@
|
|||
# Active work — cross-machine resume ledger
|
||||
|
||||
**Snapshot: 2026-07-20.** This file records volatile work that has not
|
||||
landed on `main`. Read it after `docs/agent-handoff.md`. Remove completed
|
||||
entries when their PR merges; do not let this become a second permanent
|
||||
backlog.
|
||||
|
||||
## Repository authority
|
||||
|
||||
- Canonical development remote: `githubsucks`
|
||||
(`https://github.com/levineuwirth/pmacs.git`).
|
||||
- Canonical base at this snapshot:
|
||||
`githubsucks/main` @ `f8096ff` (#124 merged, protocol v17).
|
||||
- `origin/main` @ `d3fa632` is the release mirror and was 400 commits
|
||||
behind `githubsucks/main` at the snapshot. Do not base new work on it.
|
||||
- The shared desktop checkout contained unrelated uncommitted work. The
|
||||
branches below were prepared in isolated worktrees; never clean or
|
||||
overwrite the shared checkout to recover them.
|
||||
|
||||
Start on another machine with:
|
||||
|
||||
```sh
|
||||
git fetch githubsucks --prune
|
||||
git log -1 --oneline githubsucks/main
|
||||
git worktree list
|
||||
git status --short --branch
|
||||
```
|
||||
|
||||
The first command must expose `f8096ff` or a newer intentional main.
|
||||
If it does not, stop and repair the remote/fetch configuration.
|
||||
|
||||
## Active lane: PR #123 — JSON + YAML
|
||||
|
||||
- PR: <https://github.com/levineuwirth/pmacs/pull/123>
|
||||
- Public PR branch: `githubsucks/json-yaml-grammar`
|
||||
- Public PR head before the transfer checkpoint: `4be2a65`
|
||||
- Original merge base: `56eb67e`; current main is nine commits ahead.
|
||||
- Portable checkpoint branch:
|
||||
`githubsucks/json-yaml-handoff-2026-07-20`
|
||||
- The checkpoint is a continuation branch for recovery, not a second
|
||||
feature and not a merge target. Finish there, then push its completed
|
||||
head to `githubsucks/json-yaml-grammar`.
|
||||
|
||||
The checkpoint carries the unpushed review-fix set that previously lived
|
||||
only in `../pmacs-jsonyaml`:
|
||||
|
||||
- push configured settings via
|
||||
`workspace/didChangeConfiguration` immediately after `initialized`;
|
||||
- fake-LSP configuration sink and a deterministic delivery test;
|
||||
- explicit `json.validate.enable = true`;
|
||||
- JSON provider pin
|
||||
`@t1ckbase/vscode-langservers-extracted@2.0.2`;
|
||||
- corrected YAML configuration sections:
|
||||
`yaml`, `http`, `[yaml]`, `editor`, `files`;
|
||||
- corrected telemetry, schema-network, and schema-association claims;
|
||||
- PATH-gated real JSON provider integration test.
|
||||
|
||||
Verification already completed before transfer:
|
||||
|
||||
- JSON provider standalone protocol smoke passed.
|
||||
- JSON provider through pmacs passed
|
||||
`m4_real_json_provider_receives_config_and_reports_diagnostics`.
|
||||
- `m4_json_yaml_lsp_configs_pin_command_and_sections` passed.
|
||||
- `m4_5_initial_config_pushed_via_did_change_configuration` passed.
|
||||
- `cargo fmt --check` and `git diff --check` passed.
|
||||
- `yaml-language-server@1.24.0` standalone protocol smoke passed:
|
||||
initialization reported version 1.24.0; the initial
|
||||
`workspace/configuration` request was exactly
|
||||
`yaml`, `http`, `[yaml]`, `editor`, `files`; opening the document
|
||||
caused a second scoped `[yaml]` request; invalid YAML produced a real
|
||||
parser diagnostic; shutdown exited 0 with empty stderr.
|
||||
|
||||
Still required on the destination machine:
|
||||
|
||||
1. Add the PATH-gated real-YAML pmacs acceptance test, mirroring the
|
||||
JSON test. Disable SchemaStore and Kubernetes CRD catalog access in
|
||||
the test for deterministic, network-free operation.
|
||||
2. Run it against the exact Red Hat provider and require:
|
||||
auto-attach, initialized state, a non-empty diagnostic, and no crash.
|
||||
3. Update `docs/json-yaml-framing.md` if the live pmacs path reveals any
|
||||
difference from the standalone evidence.
|
||||
4. Rebase the checkpoint onto current `githubsucks/main`.
|
||||
5. Run the full repository gate suite from `AGENTS.md`.
|
||||
6. Push the completed head to `githubsucks/json-yaml-grammar`; confirm
|
||||
new PR checks belong to that head. Never merge without the user's
|
||||
instruction.
|
||||
|
||||
Provider setup is intentionally machine-local:
|
||||
|
||||
```sh
|
||||
npm install --prefix /tmp/pmacs-jsonls \
|
||||
@t1ckbase/vscode-langservers-extracted@2.0.2
|
||||
npm install --prefix /tmp/pmacs-yamlls \
|
||||
yaml-language-server@1.24.0
|
||||
```
|
||||
|
||||
The Node language servers had to run outside the prior machine's
|
||||
restrictive execution sandbox. The `/tmp` prefixes and smoke harnesses
|
||||
do not travel.
|
||||
|
||||
Recovery worktree:
|
||||
|
||||
```sh
|
||||
git worktree add --track \
|
||||
-b json-yaml-handoff-2026-07-20 \
|
||||
../pmacs-jsonyaml-home \
|
||||
githubsucks/json-yaml-handoff-2026-07-20
|
||||
```
|
||||
|
||||
## Active lane: Arc 4 stage 3 — statusline segments
|
||||
|
||||
- Portable branch: `githubsucks/statusline-segments`
|
||||
- State: framing only, revision 1, based on `f8096ff` / protocol v17.
|
||||
- Status: awaiting user review. No implementation and no PR.
|
||||
- Scope: composable per-window Lua modeline providers, dynamic
|
||||
modeline-face inventory, and protocol v18
|
||||
`StatuslineSegments`; the built-in LSP segment is the first consumer.
|
||||
- Do not implement until the user approves the framing. When approved,
|
||||
continue on this branch so the framing remains the first commit.
|
||||
|
||||
Recovery worktree:
|
||||
|
||||
```sh
|
||||
git worktree add --track \
|
||||
-b statusline-segments \
|
||||
../pmacs-statusline \
|
||||
githubsucks/statusline-segments
|
||||
```
|
||||
|
||||
## Parked lane: kill-ring browser + persistence
|
||||
|
||||
- Portable branch: `githubsucks/kill-ring-browser`
|
||||
- State: framing only, revision 2; no implementation and no PR.
|
||||
- Status: explicitly parked by the user on 2026-07-20.
|
||||
- Its original scout was based on `0efb5cd`. The preserved framing marks
|
||||
this ground truth stale and requires a complete re-scout against the
|
||||
then-current `githubsucks/main` before implementation.
|
||||
- Compile-mode has merged since the original scout, so old
|
||||
“compile-mode in flight” keybinding/touch-set assumptions are not
|
||||
authoritative.
|
||||
|
||||
Recovery worktree, only when the user un-parks it:
|
||||
|
||||
```sh
|
||||
git worktree add --track \
|
||||
-b kill-ring-browser \
|
||||
../pmacs-kill-ring-browser \
|
||||
githubsucks/kill-ring-browser
|
||||
```
|
||||
|
||||
## Documentation lane
|
||||
|
||||
- Portable branch: `githubsucks/handoff-2026-07-20`
|
||||
- Carries synchronized `AGENTS.md` / `CLAUDE.md`, this ledger, the
|
||||
durable handoff refresh, and the keybinding reference correction.
|
||||
- It changes no runtime code.
|
||||
- Review and merge this documentation branch separately; it must not be
|
||||
folded into PR #123 or either feature framing branch.
|
||||
|
||||
## Update protocol
|
||||
|
||||
Whenever a listed lane changes materially:
|
||||
|
||||
1. update its public branch and head/state here;
|
||||
2. record new verification and remove superseded caveats;
|
||||
3. keep durable architecture in `docs/agent-handoff.md`, not here;
|
||||
4. remove the lane after merge or abandonment;
|
||||
5. verify every recovery command from a clean worktree before calling
|
||||
the transfer complete.
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
# Agent handoff — cross-machine continuity
|
||||
|
||||
**Last updated: 2026-07-15, after multi-language injections (#122)
|
||||
merged; also carries the #120 themes-stage-1 snapshot.** This file is the
|
||||
**Last updated: 2026-07-20, after the cross-machine continuity audit;
|
||||
main still reflects GPU font preferences (#124), themes stage 1 (#120),
|
||||
and injections (#122).** This file is the
|
||||
bridge between development machines. If you are an agent reading
|
||||
this on a fresh clone: this document plus the `docs/*-framing.md`
|
||||
files ARE your memory. Read this fully before taking on work, seed
|
||||
|
|
@ -9,11 +10,14 @@ your persistent memory from it, and **update this file (and commit
|
|||
it) whenever project state changes materially** — the next machine
|
||||
reads it the way you just did.
|
||||
|
||||
## 1. Where the project stands (2026-07-15)
|
||||
For volatile branches, checkpoints, verification, and recovery
|
||||
commands, read `docs/active-work.md` immediately after this file.
|
||||
|
||||
- `main` @ `5e73966` (multi-language injections #122 merged; #120
|
||||
themes stage 1 below it), protocol **v16** (`SUPPORTED=[6..16]`;
|
||||
v15→16 shipped the `ThemeFacts` channel — injections added no wire).
|
||||
## 1. Where the project stands (2026-07-20)
|
||||
|
||||
- `main` @ `f8096ff` (GPU font preferences #124 merged), protocol
|
||||
**v17** (`SUPPORTED=[6..17]`; v15→16 shipped `ThemeFacts`, v16→17
|
||||
shipped `FontFacts`).
|
||||
- **Syntax-highlight / language-detection side-quest (#114–#118)
|
||||
LANDED** — a one-shot arc built in sibling worktrees off main while
|
||||
the user's themes lane (`theme-faces`) ran concurrently in the shared
|
||||
|
|
@ -114,12 +118,38 @@ reads it the way you just did.
|
|||
survive both sides) and the **store-sourced diag-count freeze**
|
||||
(per-URI severity totals in `DiagnosticStore`, O(1), survive
|
||||
`mark_stale`).
|
||||
- **NEXT: themes stage 2 — `pmacs.gpu.set_font` at protocol v17**
|
||||
(shipped versions are never reused; the `pmacs-gpu-design.md:299`
|
||||
no-wire-change claim is superseded and must be corrected in the
|
||||
stage-2 framing). Glyphon font reload was flagged HARD. Stage 3
|
||||
after: Lua statusline-segment API (segments carry face names).
|
||||
Workflow as always: framing → user approval → branch → gates → PR.
|
||||
- **Themes (Arc 4) stage 2 LANDED — #124 merged after the complete
|
||||
behavioral review** (`docs/gpu-set-font-framing.md` rev 5):
|
||||
`pmacs.gpu.set_font { family?, size? }` is a live global preference;
|
||||
authoritative bufferless `FontFacts` is gated at protocol v17.
|
||||
GPU family resolution is frontend-local and fail-closed to a
|
||||
sanitized monospace default across normal/bold/italic queries;
|
||||
metrics for all seven glyphon buffers derive atomically from one
|
||||
logical-pixel size. The visual-run caret substrate normalizes
|
||||
source bytes through adornments and shaped clusters (including
|
||||
combining sequences/ligatures), preserves deliberately scrolled-away
|
||||
viewports, and reflows on font, gutter, minimap, text, CRDT, resize,
|
||||
and snapshot geometry changes. Test fixtures enter fontdb before
|
||||
`FontSystem` construction; alternate advances are measured across
|
||||
complete shaped runs, not sampled glyphs.
|
||||
- **NEXT: themes stage 3 — Lua statusline-segment API.**
|
||||
Revision 1 is preserved on branch `statusline-segments` and awaits
|
||||
review. It scopes additive per-window modeline providers, face-name
|
||||
segments, dynamic `ThemeFacts` inventory, and a new v18
|
||||
`StatuslineSegments` channel; the existing LSP status tracker is the
|
||||
first built-in provider. Approval → implementation → gates → PR.
|
||||
Completing stage 3 completes Arc 4.
|
||||
- **OPEN: JSON + YAML PR #123.** The public PR branch still points to
|
||||
the original two-commit implementation (`4be2a65`). Review fixes and
|
||||
live-provider evidence are preserved on checkpoint branch
|
||||
`json-yaml-handoff-2026-07-20`; the real YAML-through-pmacs smoke,
|
||||
rebase, and full gates remain. Do not mistake the PR's old green CI
|
||||
for validation of the checkpoint. Exact recovery is in
|
||||
`docs/active-work.md`.
|
||||
- **PARKED: kill-ring browser + persistence.** Revision 2 framing is
|
||||
preserved on branch `kill-ring-browser`, but its `0efb5cd` scout is
|
||||
stale and must be repeated before implementation. No PR or
|
||||
implementation is active.
|
||||
- Roadmap: `docs/roadmap-2026-07.md` (ranked arcs). Position:
|
||||
- **Arc 1 (LSP utility surface) COMPLETE** — completion popup
|
||||
(#92/#93), panels/references/outline/hover (#94–#96), plus
|
||||
|
|
@ -238,8 +268,9 @@ buffer owns a path's recovery slot; only recover/discard release
|
|||
unclaimed crash data; adopt clears the old owner's skip cache.
|
||||
|
||||
**Protocol** — encoding-breaking bumps are deliberate and versioned
|
||||
(`SUPPORTED=[6..15]`). v15 = `CompletionPopup` + `StatusFacts.message`.
|
||||
New wire surface ⇒ bump + both-frontends support + acceptance.
|
||||
(`SUPPORTED=[6..17]`). v15 = `CompletionPopup` +
|
||||
`StatusFacts.message`; v16 = `ThemeFacts`; v17 = `FontFacts`. New wire
|
||||
surface ⇒ bump + both-frontends support + acceptance.
|
||||
|
||||
**Fake LSP** (`src/bin/pmacs_fake_lsp.rs`) modes: `fullonly`,
|
||||
`rangeonly`, `rangeonly16` (UTF-16 + fail-closed bounds validation),
|
||||
|
|
@ -323,7 +354,9 @@ runtime/Lua-registered languages (v1 resolves only against
|
|||
`BUILTIN_LANGUAGES`), and the next injection *consumers* gated on new
|
||||
grammars — HTML/CSS/GraphQL/SQL (`<script>`/`<style>`, JS/TS template
|
||||
literals, doc-comment code); modeline detection as a 5th layer
|
||||
(`-*- mode: … -*-` / `# vim: ft=…`); JSON/YAML grammars+LSP;
|
||||
(`-*- mode: … -*-` / `# vim: ft=…`); JSON/YAML grammars+LSP
|
||||
(PR #123 open; checkpoint and remaining verification are in
|
||||
`docs/active-work.md`);
|
||||
byte-accurate multibyte cursor placement in `move_active_cursor_to`
|
||||
(still steps one codepoint per LSP byte column). A full Jupyter `.ipynb`
|
||||
setup (reader → editable → kernel execution) is a real arc now gated on
|
||||
|
|
@ -358,5 +391,7 @@ Don't expect them in a clone; on the desktop, never delete them.
|
|||
When a PR merges, an arc opens/closes, or a decision lands: edit the
|
||||
snapshot (§1), append lessons (§5) and deferrals (§6) as they arise,
|
||||
bump the date line at the top, and commit — usually riding the same PR
|
||||
as the work. Keep it under ~250 lines: this is a briefing, not a log;
|
||||
prune sections that stop being true.
|
||||
as the work. Keep durable architecture here; put branch hashes,
|
||||
machine-local tools, incomplete verification, and recovery commands in
|
||||
`docs/active-work.md`. This is a briefing, not a log: prune sections
|
||||
that stop being true.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,379 @@
|
|||
# pmacs keybindings — reference
|
||||
|
||||
**Last verified against `main` @ `f8096ff` (2026-07-20).** This is a
|
||||
snapshot, not generated output — when a PR adds, removes, or rebinds a
|
||||
key, update this file in the same PR (see §6). If you're an agent and
|
||||
this file looks stale against the code it cites, trust the code.
|
||||
|
||||
pmacs keys come from two independent places:
|
||||
|
||||
- **The Lua keymap** (§1–2) — `pmacs.keymap.bind{...}` calls, resolved
|
||||
by the Rust dispatcher against whatever `init.lua` has bound at
|
||||
runtime. Fully user-rebindable: unbind or rebind any of these from
|
||||
init.lua (§5).
|
||||
- **Rust-hardcoded modal shadows** (§3) — isearch, query-replace,
|
||||
the minibuffer/prompt, the completion popup, and the context menu
|
||||
each shadow the Lua keymap while active: `EditorInstance::dispatch_key`
|
||||
(`src/editor.rs:658-733`) checks these modes, highest-priority first,
|
||||
before a key ever reaches the Lua dispatcher. **Not user-configurable**
|
||||
— there is no `pmacs.keymap` surface for them; changing one means
|
||||
editing the mode's `from_chord` decoder in Rust.
|
||||
|
||||
Notation matches what `pmacs.keymap.bind` accepts: `C-` = Ctrl, `M-` =
|
||||
Alt/Meta, `S-` = Shift, bare letters/punctuation self-insert when
|
||||
unmodified. Named keys are angle-bracketed (`<left>`, `<up>`, `<home>`)
|
||||
or all-caps (`RET`, `BS`/Backspace, `DEL`/Delete, `TAB`, `SPC`).
|
||||
Sequences separated by spaces (`C-x C-s`) are chords typed in order.
|
||||
|
||||
## 1. Global keymap
|
||||
|
||||
Source: `builtin/keymaps/default.lua` unless noted. All bound at
|
||||
`scope = "global"`.
|
||||
|
||||
### Cursor motion
|
||||
|
||||
| Key | Command |
|
||||
|---|---|
|
||||
| `C-a` / `<home>` | `cursor.line-start` |
|
||||
| `C-e` / `<end>` | `cursor.line-end` |
|
||||
| `C-f` / `<right>` | `cursor.right` |
|
||||
| `C-b` / `<left>` | `cursor.left` |
|
||||
| `C-n` / `<down>` | `cursor.down` |
|
||||
| `C-p` / `<up>` | `cursor.up` |
|
||||
| `C-<left>` / `M-b` | `cursor.word-left` |
|
||||
| `C-<right>` / `M-f` | `cursor.word-right` |
|
||||
| `C-<up>` / `M-{` | `cursor.paragraph-up` |
|
||||
| `C-<down>` / `M-}` | `cursor.paragraph-down` |
|
||||
| `<pageup>` / `M-v` | `cursor.page-up` |
|
||||
| `<pagedown>` / `C-v` | `cursor.page-down` |
|
||||
| `M-g g` / `M-g M-g` | `cursor.goto-line` (`builtin/runtime/editops.lua`) |
|
||||
|
||||
### Selection (CUA shift-select)
|
||||
|
||||
Plain motion preserves an existing selection instead of dropping it
|
||||
(Emacs-flavored default, not strict CUA).
|
||||
|
||||
| Key | Command |
|
||||
|---|---|
|
||||
| `S-<left>` / `S-<right>` | `cursor.select-left` / `cursor.select-right` |
|
||||
| `S-<up>` / `S-<down>` | `cursor.select-up` / `cursor.select-down` |
|
||||
| `S-<home>` / `S-<end>` | `cursor.select-line-start` / `cursor.select-line-end` |
|
||||
| `C-S-<left>` / `C-S-<right>` | `cursor.select-word-left` / `cursor.select-word-right` |
|
||||
| `C-S-<up>` / `C-S-<down>` | `cursor.select-paragraph-up` / `cursor.select-paragraph-down` |
|
||||
|
||||
### Editing
|
||||
|
||||
| Key | Command |
|
||||
|---|---|
|
||||
| `BS` | `buffer.delete-backward` |
|
||||
| `DEL` / `C-d` | `buffer.delete-forward` |
|
||||
| `RET` | `edit.newline-and-indent` |
|
||||
| `TAB` | `buffer.tab` |
|
||||
| `C-BS` / `C-h` | `buffer.delete-word-backward` (see §4 for the `C-h` rationale) |
|
||||
| `M-BS` | `buffer.delete-word-backward` |
|
||||
| `C-DEL` | `buffer.delete-word-forward` |
|
||||
| `M-d` | `buffer.delete-word-forward` |
|
||||
| `M-u` | `edit.upcase` (`editops.lua`) |
|
||||
| `M-l` | `edit.downcase` (`editops.lua`) |
|
||||
| `M-c` | `edit.capitalize` (`editops.lua`) |
|
||||
| `C-t` | `edit.transpose-chars` (`editops.lua`) |
|
||||
| `M-t` | `edit.transpose-words` (`editops.lua`) |
|
||||
| `M-z` | `edit.zap-to-char` (`editops.lua`) |
|
||||
| `M-<up>` / `M-<down>` | `edit.move-line-up` / `edit.move-line-down` (`editops.lua`) |
|
||||
| `M-^` | `edit.join-line` (`editops.lua`) |
|
||||
| `M-;` | `edit.toggle-comment` (`builtin/runtime/comment.lua`) |
|
||||
|
||||
> `M-d` / `M-BS` currently plain-delete the word — they are **not**
|
||||
> kill-ring members yet (a named deferral; see `docs/agent-handoff.md`
|
||||
> §6, "word kills"). `edit.kill-line` (below) is the only word/line
|
||||
> kill wired into the ring so far.
|
||||
|
||||
### Clipboard & kill ring
|
||||
|
||||
| Key | Command |
|
||||
|---|---|
|
||||
| `M-w` | `edit.copy` |
|
||||
| `C-w` | `edit.cut` |
|
||||
| `C-y` | `edit.paste` |
|
||||
| `C-x h` | `edit.select-all` (Emacs `mark-whole-buffer`) |
|
||||
| `C-k` | `edit.kill-line` (`builtin/runtime/killring.lua`) |
|
||||
| `M-y` | `edit.yank-pop` — replace the just-yanked text with the previous kill, immediately after `C-y` (`killring.lua`) |
|
||||
|
||||
### Undo / redo
|
||||
|
||||
Multiple bindings exist because terminals disagree on how `Ctrl+/`
|
||||
encodes; see §4.
|
||||
|
||||
| Key | Command |
|
||||
|---|---|
|
||||
| `C-/` / `C-_` / `C-4` / `C-x u` | `buffer.undo` |
|
||||
| `C-?` / `C-S-_` / `C-x r` | `buffer.redo` |
|
||||
|
||||
### Search & replace
|
||||
|
||||
Once a search is running, `C-s`/`C-r` step to the next/previous match
|
||||
and `M-r` toggles literal↔regex — those are Rust-hardcoded isearch
|
||||
keys, not Lua bindings (§3).
|
||||
|
||||
| Key | Command |
|
||||
|---|---|
|
||||
| `C-s` | `search.forward` (starts isearch) |
|
||||
| `C-r` | `search.backward` (starts isearch) |
|
||||
| `C-M-s` | `search.forward-regex` |
|
||||
| `C-M-r` | `search.backward-regex` |
|
||||
| `M-%` | `query-replace` (starts an interactive replace session, §3) |
|
||||
| `C-M-%` | `query-replace-regexp` |
|
||||
|
||||
### Multi-key (`C-x`) chords
|
||||
|
||||
| Key | Command |
|
||||
|---|---|
|
||||
| `C-x C-s` | `buffer.save` |
|
||||
| `C-x C-c` | `editor.quit` |
|
||||
| `C-x 2` | `window.split-horizontal` |
|
||||
| `C-x 3` | `window.split-vertical` |
|
||||
| `C-x o` / `C-x O` | `window.focus-next` / `window.focus-prev` |
|
||||
| `C-x 0` | `window.close` |
|
||||
| `C-x 1` | `window.close-others` |
|
||||
| `C-x b` | `editor.switch-buffer` |
|
||||
| `C-x C-b` | `editor.list-buffers` (opens the `*buffer-list*` panel, §2) |
|
||||
| `C-x <right>` / `C-x <left>` | `editor.next-buffer` / `editor.previous-buffer` |
|
||||
| `C-x C-r` | `recent-files` (`builtin/runtime/recentf.lua`) |
|
||||
|
||||
### Command palette & cancellation
|
||||
|
||||
| Key | Command |
|
||||
|---|---|
|
||||
| `M-x` | `editor.execute-command` — prompts (via the minibuffer, §3) for any command by name |
|
||||
| `C-g` | `editor.cancel` — resets the dispatcher / clears an unfinished prefix |
|
||||
|
||||
### Completion
|
||||
|
||||
| Key | Command |
|
||||
|---|---|
|
||||
| `C-M-i` | `completion.at-point` (`builtin/runtime/completion.lua`) — opens the popup; popup navigation is Rust-hardcoded (§3) |
|
||||
|
||||
### LSP
|
||||
|
||||
Source: `builtin/runtime/lsp.lua`. `M-.` follows the cross-editor
|
||||
go-to-definition convention; the rest sit on the `C-c` prefix to keep
|
||||
printable letters free for self-insert.
|
||||
|
||||
| Key | Command |
|
||||
|---|---|
|
||||
| `M-.` | `lsp.go-to-definition` |
|
||||
| `M-?` | `lsp.find-references` (opens `*references*` panel, §2) |
|
||||
| `M-,` | `lsp.jump-back` (unwind the cross-file jump ring) |
|
||||
| `C-c o` | `lsp.document-symbols` (opens `*outline*` panel, §2) |
|
||||
| `C-c r` | `lsp.rename` |
|
||||
| `C-c a` | `lsp.code-actions` |
|
||||
| `C-c i` | `lsp.inlay-hints` |
|
||||
| `C-c y` | `lsp.semantic-tokens` |
|
||||
| `C-c h` | `lsp.hover` |
|
||||
| `C-c H` | `lsp.hover-doc` (opens `*lsp-help*` panel, §2) |
|
||||
| `C-c s` | `lsp.signature-help` |
|
||||
| `C-c f` | `lsp.format-buffer` |
|
||||
|
||||
`builtin/runtime/lsp.lua` initially binds `M-g n` / `M-g p` to
|
||||
diagnostic navigation. `compile.lua` loads afterward and deliberately
|
||||
replaces them with the unified error dispatcher below.
|
||||
|
||||
### Compile, shell command, and unified errors
|
||||
|
||||
Source: `builtin/runtime/compile.lua`.
|
||||
|
||||
| Key | Command |
|
||||
|---|---|
|
||||
| `M-g n` / `M-g p` | `error.next` / `error.previous` — compile/grep errors when that source has claimed navigation, otherwise LSP diagnostics |
|
||||
| `` C-x ` `` | `error.next` |
|
||||
| `M-!` | `shell.command` — asynchronous output in `*shell-command*` |
|
||||
|
||||
`compile.run` and `compile.recompile` are available through `M-x`; no
|
||||
global key is assigned to them.
|
||||
|
||||
## 2. Buffer-local panel keymaps
|
||||
|
||||
Read-only panel buffers built on `pmacs.listview.open` (buffer scope
|
||||
`{ scope = "buffer", buffer = <id> }`; see `builtin/runtime/listview.lua`)
|
||||
all share one keymap:
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `RET` / `SPC` | `listview.visit` — act on the item under the cursor |
|
||||
| `n` / `<down>` | `cursor.down` |
|
||||
| `p` / `<up>` | `cursor.up` |
|
||||
| `g` | `listview.refresh` — re-run the data source and re-render |
|
||||
| `q` | `listview.quit` — restore the buffer that was active before the panel opened |
|
||||
|
||||
Panels currently built on this: `*references*`, `*outline*`,
|
||||
`*lsp-help*` (hover docs). Header text always spells out the same
|
||||
`RET`/`n`/`p`/`g`/`q` legend inline.
|
||||
|
||||
`*buffer-list*` (`editor.list-buffers`, `C-x C-b`) uses its own
|
||||
keymap, layered on the same idiom, in `builtin/commands/default.lua`:
|
||||
|
||||
| Key | Command |
|
||||
|---|---|
|
||||
| `RET` / `SPC` | `editor.buffer-list-visit` |
|
||||
| `n` / `<down>` | `cursor.down` |
|
||||
| `p` / `<up>` | `cursor.up` |
|
||||
| `d` | `editor.buffer-list-mark-delete` |
|
||||
| `u` | `editor.buffer-list-unmark` |
|
||||
| `x` | `editor.buffer-list-execute` — kill every marked buffer |
|
||||
| `k` | `editor.buffer-list-kill-now` |
|
||||
| `g` | `editor.buffer-list-refresh` |
|
||||
| `q` | `editor.buffer-list-quit` |
|
||||
|
||||
One-off buffer-local bindings, each scoped to a single generated
|
||||
buffer:
|
||||
|
||||
| Buffer | Key | Command |
|
||||
|---|---|---|
|
||||
| `*workers*` (`editor.list-workers`) | `C-c C-k` | `workers.cancel-at-point` (`builtin/runtime/async.lua`) |
|
||||
| `*pmacs-instance*` (`editor.describe-instance-buffer`) | `q` | `buffer.kill-this` (`commands/default.lua`) |
|
||||
| `*help*` (`editor.describe-command`) | `q` | `buffer.kill-this` |
|
||||
| REPL buffers (`builtin/packages/repl/init.lua`) | `RET` | `pmacs.repl.submit-current` |
|
||||
| REPL buffers | `C-c` | `pmacs.repl.send-sigint-current` |
|
||||
| REPL buffers | `C-d` | `pmacs.repl.send-eof-current` — closes stdin on an empty line, else deletes forward |
|
||||
|
||||
Compile-mode generated buffers (`*compilation*` and
|
||||
`*shell-command*`) have their own buffer-local map:
|
||||
|
||||
| Key | Command |
|
||||
|---|---|
|
||||
| `RET` | `compile.visit-error` |
|
||||
| `n` / `p` | `compile.next-error-line` / `compile.previous-error-line` |
|
||||
| `q` | `compile.quit` |
|
||||
| `C-c C-k` | `compile.kill` |
|
||||
| `g` | `compile.recompile` (`*compilation*` only) |
|
||||
| every shipped undo/redo chord | `compile.undo-noop` — generated output is intercept-read-only |
|
||||
|
||||
The REPL package (`builtin/packages/repl/`) is shipped but opt-in —
|
||||
loaded via `require`, not part of the always-on `builtin/runtime`
|
||||
lane. Its bindings only exist in a buffer created by a REPL session.
|
||||
|
||||
## 3. Rust-hardcoded modal keys
|
||||
|
||||
These live in `src/editor.rs` (and `src/minibuffer.rs` for the
|
||||
prompt) as small `from_chord(chord) -> Action` decoders, one per mode,
|
||||
checked in priority order by `EditorInstance::dispatch_key`
|
||||
(`src/editor.rs:658-733`, highest first): **context menu → isearch →
|
||||
query-replace → minibuffer → completion popup → normal Lua dispatch.**
|
||||
Each decoder's rustdoc names its own key list; this table mirrors
|
||||
those. They are not reachable through `pmacs.keymap` — there is
|
||||
deliberately no Lua surface for them (keeps the set curated; see the
|
||||
`R51` rationale cited in `lib.rs`/`lua_bindings/mod.rs`).
|
||||
|
||||
**Isearch** (`SearchKey`, `editor.rs:1858-1919`) — active after
|
||||
`C-s`/`C-r`/`C-M-s`/`C-M-r`:
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `C-s` / `<down>` | next match |
|
||||
| `C-r` / `<up>` | previous match |
|
||||
| `RET` / `C-m` | accept — keep cursor + highlights |
|
||||
| `C-g` / `Esc` | cancel — restore the origin cursor |
|
||||
| `BS` / `C-h` | shorten the query by one character |
|
||||
| `M-r` | toggle literal ↔ regex |
|
||||
| any printable char | extend the query |
|
||||
|
||||
**Query-replace** (`QueryReplaceKey`, `editor.rs:1926-1961`) — active
|
||||
after `M-%`/`C-M-%`:
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `y` / `SPC` | replace this match, advance |
|
||||
| `n` / `BS` / `Delete` | skip this match, advance |
|
||||
| `!` | replace this and every remaining match, no more prompts |
|
||||
| `.` | replace this match, then quit |
|
||||
| `q` / `RET` / `Esc` / `C-g` | quit (replacements already made are kept) |
|
||||
|
||||
**Minibuffer / prompt** (`MinibufferAction`, `minibuffer.rs:418-527`)
|
||||
— backs every `pmacs.minibuffer.read` call: `M-x`, query-replace's
|
||||
from/to prompts, `find-file`, etc.:
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `RET` / `C-m` | accept |
|
||||
| `C-g` | cancel |
|
||||
| `TAB` / `C-i` | complete to the selected candidate |
|
||||
| `<up>` / `<down>` | prev/next candidate if a dropdown is showing, else history navigation |
|
||||
| `C-p` / `C-n` | history prev/next, unconditionally |
|
||||
| `<left>` / `C-b`, `<right>` / `C-f` | cursor move |
|
||||
| `<home>` / `C-a`, `<end>` / `C-e` | line start/end |
|
||||
| `BS` | delete backward |
|
||||
| `Delete` / `C-d` | delete forward |
|
||||
| `M-n` / `M-p` | scroll the selected candidate forward/back |
|
||||
| any other printable char | self-insert |
|
||||
|
||||
**In-buffer completion popup** (`CompletionPopupKey`,
|
||||
`editor.rs:2019-2059`) — active after `C-M-i` or an LSP-triggered
|
||||
popup. Unlike the others this is a **partial** shadow: only the keys
|
||||
below are intercepted; everything else (typing, motion) falls through
|
||||
to normal dispatch, so typing keeps self-inserting while the popup is
|
||||
open.
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `<down>` / `C-n` | next candidate |
|
||||
| `<up>` / `C-p` | previous candidate |
|
||||
| `TAB` / `RET` | accept the highlighted candidate |
|
||||
| `Esc` / `C-g` | dismiss |
|
||||
|
||||
**Context menu** (`MenuKey`, `editor.rs:1966-2009`) — opened by
|
||||
right-click, not a keybinding itself, but shadows the keymap while
|
||||
open:
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `<down>` / `C-n` | next item |
|
||||
| `<up>` / `C-p` | previous item |
|
||||
| `RET` | invoke the highlighted item |
|
||||
| `Esc` / `C-g` | cancel |
|
||||
| any other key | dismiss (click-away semantics) |
|
||||
|
||||
**Frontend detach** — `F12` (any modifiers) detaches an attached
|
||||
frontend from the daemon (`src/attach.rs:997-1006`, checked at
|
||||
`attach.rs:818`). Not a UI mode inside the editor core, but another
|
||||
literal-`KeyCode` interception outside the Lua keymap; tentative for
|
||||
v0.1 per the comment there (chosen because F12 is rarely bound to
|
||||
anything else).
|
||||
|
||||
## 4. Terminal-compatibility caveats
|
||||
|
||||
- **`C-h` doubles as `C-BS`.** Most terminals without the kitty
|
||||
keyboard protocol can't disambiguate `Ctrl+Backspace` from
|
||||
`Ctrl+H` — both legacy paths send byte `0x08`. `C-h` is bound to
|
||||
`buffer.delete-word-backward` alongside `C-BS` so the shortcut works
|
||||
on legacy terminals. pmacs does not use `C-h` as an Emacs-style help
|
||||
prefix; a user who wants that can rebind it.
|
||||
- **Undo/redo have redundant bindings** (`C-/`, `C-_`, `C-4` for undo;
|
||||
`C-?`, `C-S-_` for redo) because terminals encode `Ctrl+/` several
|
||||
different ways. Kitty's keyboard protocol routes most cleanly
|
||||
through `C-/`; the alternates keep legacy/remote terminals working.
|
||||
- Kitty-protocol-only chords (e.g. distinguishing `C-i` from `TAB`)
|
||||
degrade gracefully where noted above — check the frontend's terminal
|
||||
capability negotiation if a chord seems to not fire.
|
||||
|
||||
## 5. Changing bindings
|
||||
|
||||
`pmacs.keymap.bind` / `pmacs.keymap.unbind` are ordinary Lua API,
|
||||
callable from `init.lua`:
|
||||
|
||||
```lua
|
||||
pmacs.keymap.bind { scope = "global", sequence = "C-c g", command = "cursor.goto-line" }
|
||||
pmacs.keymap.unbind { scope = "global", sequence = "M-z" }
|
||||
```
|
||||
|
||||
`scope = "buffer"` additionally takes `buffer = <id>`; buffer-local
|
||||
bindings are pruned automatically when that buffer is removed. This
|
||||
covers §1 and §2 only — §3's Rust-hardcoded modal keys have no Lua
|
||||
surface (see §3's intro).
|
||||
|
||||
## 6. Keeping this file honest
|
||||
|
||||
Update this file in the same PR whenever a binding is added, removed,
|
||||
or moved — same discipline as `docs/agent-handoff.md`. To re-derive it
|
||||
from scratch instead of trusting the table: grep `builtin/` for
|
||||
`pmacs.keymap.bind`/`.bind(` and `pmacs.listview.open`, and grep
|
||||
`src/editor.rs` / `src/minibuffer.rs` for `from_chord`.
|
||||
|
|
@ -4,6 +4,10 @@ Date: 2026-07-07. Produced from a five-way codebase/docs/memory sweep
|
|||
(core editing + persistence, LSP surface, GPU parity, extensibility +
|
||||
terminal, deferred-work inventory across all framing docs).
|
||||
|
||||
> **Historical planning snapshot.** Several arcs below have since
|
||||
> landed. Use `docs/agent-handoff.md` for durable current state and
|
||||
> `docs/active-work.md` for open branches and recovery instructions.
|
||||
|
||||
**Decision (2026-07-07): push Arc 1 (LSP utility surface), with Arc 2
|
||||
(editing table stakes) items interleaved between sub-arcs.**
|
||||
|
||||
|
|
|
|||
|
|
@ -42,9 +42,11 @@ The direct continuation of the #114–#118 grammar/detection stack.
|
|||
(`<script>`/`<style>`, template literals, doc-comment code).
|
||||
- **Modeline detection** — a 5th detection layer (`-*- mode: … -*-`,
|
||||
`# vim: ft=…`) after extension → filetype → filename → shebang.
|
||||
- **JSON + YAML** grammars + LSP (`tree-sitter-json` +
|
||||
vscode-json-languageserver; yaml-language-server). JSON is also the
|
||||
prerequisite for the notebook path.
|
||||
- **JSON + YAML — PR #123 open.** Grammars and LSP configs exist on the
|
||||
feature line; review fixes are preserved on
|
||||
`json-yaml-handoff-2026-07-20`. A real YAML-through-pmacs smoke,
|
||||
rebase, and full gates remain before review resumes. JSON is also the
|
||||
prerequisite for the notebook path; see `docs/active-work.md`.
|
||||
- **More grammars for languages with neither grammar nor LSP** — ruby,
|
||||
php, html, css, sql, etc.
|
||||
- **Grapheme / combining-mark awareness** in the text view
|
||||
|
|
@ -211,8 +213,8 @@ domain excluded — see below), `editor.rs` split (7 k lines),
|
|||
|
||||
## Excluded — themes / faces main quest (seen, routed elsewhere)
|
||||
|
||||
`pmacs.gpu.set_font` + the statusline-segment API; background/selection
|
||||
theming; per-peer stable presence colors; exact quad colors per
|
||||
The statusline-segment API (Arc 4 stage 3; framing awaiting review);
|
||||
background/selection theming; per-peer stable presence colors; exact quad colors per
|
||||
decoration kind; GPU gutter background layer / wash recolor / chrome
|
||||
bold-italic-underline; current-line highlight refinements; multi-server
|
||||
semantic-token *style* blending; the compile-mode **severity→color**
|
||||
|
|
@ -229,8 +231,8 @@ guides (visual, not color).
|
|||
north-star items is done, so the highest-leverage board is now:
|
||||
|
||||
1. **Config registry** — frees ~5 editing/indent/comment features at once.
|
||||
2. **JSON (+ YAML) grammar** — the one remaining gate on the Jupyter
|
||||
`.ipynb` path now that injections exist, and a clean highlight one-shot.
|
||||
2. **Finish JSON + YAML PR #123** — the one remaining gate on the
|
||||
Jupyter `.ipynb` path now that injections exist.
|
||||
3. **Locals-query processing** — restores `.builtin` styling for
|
||||
non-shadowed builtins, the last rough edge of the highlight stack.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue