docs: comment-toggle framing (Arc 2)
Q#CT1-6: pure Lua on the Arc 2 substrate (effective-edit mutators, command boundaries, after-edit delivery). M-; = comment-line-style toggle (named deviation from comment-dwim); pmacs.comment.strings public table; min-indent insertion, skip-blank, mixed-span comments; single buf:replace per toggle = one undo step / one CRDT op / one effective-edit verification. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
2dde4b8acd
commit
9d2af85380
|
|
@ -0,0 +1,149 @@
|
|||
# Comment/uncomment — framing (Arc 2, editing table stakes)
|
||||
|
||||
pmacs has no way to comment code out. This adds the table-stakes
|
||||
toggle: `M-;` comments or uncomments the current line or the selected
|
||||
lines, language-aware, as one undoable edit.
|
||||
|
||||
Roadmap: `docs/roadmap-2026-07.md` Arc 2 ("comment/uncomment").
|
||||
|
||||
## Ground truth (as of `2dde4b8`)
|
||||
|
||||
- **No comment-syntax knowledge exists anywhere** — not in the grammar
|
||||
registry, not in LSP config, not in Lua. A language → prefix table is
|
||||
new surface.
|
||||
- **Language detection**: `active_buffer_language()` in
|
||||
`builtin/runtime/lsp.lua` chains grammar detection
|
||||
(`pmacs.parse.language_for_path`) with the user-extensible
|
||||
`pmacs.lsp.filetypes` map — but it is a **local**. Known languages
|
||||
today: rust/lua/c/cpp + js/ts via grammars; python, c, go,
|
||||
tsx/jsx, lua, bash, toml, zig via filetypes.
|
||||
- **Bindings**: `M-;` (Emacs `comment-dwim`'s home) is free. `C-/` is
|
||||
taken by undo (terminal `Ctrl+/` ambiguity — three undo bindings
|
||||
exist), so the VSCode-style toggle chord is unavailable.
|
||||
- **Undo granularity**: every applied edit pushes one `UndoEntry`
|
||||
(`src/buffer.rs:130`) — there is no grouping/transaction. N per-line
|
||||
edits would need N undos.
|
||||
- From Arc 2 (merged): the buffer mutators return the **effective
|
||||
post-intercept edit** `(start, end, inserted_len)`; keybound commands
|
||||
rotate the command boundary and get `buffer.after-edit` from
|
||||
dispatch; `M-x` gets both via `invoke_interactive` +
|
||||
`with_after_edit_check`. Comment-toggle inherits all of it for free.
|
||||
|
||||
## Decisions
|
||||
|
||||
### Q#CT1 — Pure Lua, zero Rust changes
|
||||
|
||||
`builtin/runtime/comment.lua`. Everything needed exists: `buf:slice`,
|
||||
the effective-edit-returning `buf:replace`, `ed.region/cursor/
|
||||
goto_byte/clear_selection`, and the language chain. The one lsp.lua
|
||||
touch: **export** the existing local as
|
||||
`pmacs.lsp.active_buffer_language()` (one line) rather than replicating
|
||||
its grammar+filetypes chain and drifting.
|
||||
|
||||
### Q#CT2 — Command + binding
|
||||
|
||||
One command, `edit.toggle-comment`, bound **`M-;`**:
|
||||
|
||||
- **Region active** → toggle the whole lines the region touches, then
|
||||
clear the selection (CUA convention after a region op) and leave the
|
||||
cursor at the span start.
|
||||
- **No region** → toggle the current line, then **move to the next
|
||||
line** — Emacs's `comment-line` behavior, which makes repeated `M-;`
|
||||
walk down a block toggling as it goes.
|
||||
|
||||
Named deviation from Emacs: `M-;` in Emacs is `comment-dwim`, whose
|
||||
no-region case *appends* an empty comment at end of line. That mode is
|
||||
rarely what modern muscle memory wants from the toggle key; pmacs's
|
||||
`M-;` behaves like Emacs `C-x C-;` (`comment-line`). DWIM's
|
||||
append-comment can come later under its own name.
|
||||
|
||||
### Q#CT3 — The comment-string table
|
||||
|
||||
`pmacs.comment.strings` — a public, user-extensible map (the
|
||||
`pmacs.lsp.filetypes` pattern), language → line-comment prefix:
|
||||
|
||||
`//`: rust, c, cpp, go, zig, javascript, typescript,
|
||||
javascriptreact, typescriptreact · `--`: lua · `#`: python, bash,
|
||||
toml, yaml, sh.
|
||||
|
||||
Unknown language (or no language): status *"no comment syntax known
|
||||
for `<lang>`"*, no edit. Users add entries from init.lua:
|
||||
`pmacs.comment.strings.mylang = ";;"`. **Block comments are deferred**
|
||||
— line comments cover the table-stakes use, and block toggling has
|
||||
real edge cases (nesting, mid-line spans) that don't belong in v1.
|
||||
|
||||
### Q#CT4 — Toggle semantics
|
||||
|
||||
Over the span's lines:
|
||||
|
||||
- **Uncomment** when every non-blank line starts (after its
|
||||
indentation) with the prefix; removal strips the prefix plus one
|
||||
following space if present.
|
||||
- **Comment** otherwise: insert `prefix + " "` at the **minimum
|
||||
indentation column** of the span's non-blank lines (Emacs
|
||||
`comment-region` style — the comments line up instead of hugging
|
||||
each line's own indent). **Blank lines are skipped** in both
|
||||
directions and don't influence the min-indent computation.
|
||||
- A span that is entirely blank is a no-op with a status.
|
||||
|
||||
Mixed spans (some commented, some not) therefore **comment** — the
|
||||
double-prefix on already-commented lines round-trips back out, which
|
||||
is Emacs's behavior and preserves inner commented-out code.
|
||||
|
||||
### Q#CT5 — One edit, one undo step, one CRDT op
|
||||
|
||||
The whole toggle is a **single `buf:replace(span_start, span_end,
|
||||
new_text)`**: Lua builds the rewritten span, one edit applies it.
|
||||
Consequences, all deliberate:
|
||||
|
||||
- **One `C-/` undoes the whole toggle** (there is no undo grouping to
|
||||
lean on; N per-line edits would need N undos).
|
||||
- One CRDT op for replica frontends.
|
||||
- One effective-edit verification: the kill-ring discipline — if the
|
||||
returned `(start, end, inserted_len)` deviates from the request (a
|
||||
buffer intercept rewrote it), report *"comment toggle altered by
|
||||
buffer intercept"* and skip the cursor fix-up; the interceptor's
|
||||
result stands. `pcall`'d, so a rejecting intercept reports rather
|
||||
than throws.
|
||||
|
||||
### Q#CT6 — Chain/hook plumbing: nothing to build
|
||||
|
||||
Keybound `M-;` rotates the command boundary (breaking kill chains —
|
||||
correct) and fires `buffer.after-edit` from dispatch's revision check;
|
||||
`M-x edit.toggle-comment` gets the same via `invoke_interactive` and
|
||||
the accept-path hook wrapper. Both are the Arc 2 substrate working as
|
||||
designed — the acceptance suite asserts the hook fires once anyway.
|
||||
|
||||
## Bets
|
||||
|
||||
1. **Single-replace is the right granularity** — no complaint about
|
||||
whole-span replaces (vs. per-line edits) from CRDT replicas or LSP
|
||||
didChange (full-text sync makes this moot today).
|
||||
2. **Min-indent + skip-blank matches expectation** — no "why is my
|
||||
comment at column 0" or "why did my blank line get a `//`".
|
||||
|
||||
## Deferred (named)
|
||||
|
||||
- Block comments (`/* */`) and mid-line spans.
|
||||
- `comment-dwim`'s append-comment-at-EOL mode.
|
||||
- Doc-comment continuation on newline (belongs to auto-indent).
|
||||
- Per-language *padding* config (always one space in v1).
|
||||
|
||||
## Acceptance (`tests/comment_toggle_acceptance.rs`, dispatch-driven)
|
||||
|
||||
- Rust buffer: `M-;` comments the line (`// ` at indent), cursor moves
|
||||
to the next line; `M-;` on a commented line uncomments (round-trip,
|
||||
including the space).
|
||||
- Region across mixed-indent lines → prefixes at min indent, aligned;
|
||||
blank line inside the span untouched; selection cleared.
|
||||
- All-commented span → uncomments; mixed span → comments (inner
|
||||
prefix preserved, round-trips).
|
||||
- Lua buffer gets `--`, Python `#`; unknown/no language → status, no
|
||||
edit.
|
||||
- **One undo step**: multi-line toggle then a single `buffer.undo`
|
||||
restores the original text exactly.
|
||||
- Intercept discipline: rejecting intercept → status, no throw;
|
||||
transforming intercept → reported, no cursor fix-up.
|
||||
- `after-edit` probe fires exactly once per toggle (keybound and
|
||||
`M-x`).
|
||||
- Kill-chain break: `C-k`, `M-;`, `C-k` → two ring entries.
|
||||
Loading…
Reference in New Issue