Fullscreen the pane while an editor holds it

ctrl-g opens the prompt in $EDITOR, which takes the child's pty over via
the alternate screen — something Claude Code never does itself. The
compact frame looks for the input box's two rules, so an editor could
only ever be cropped by it. Follow the alternate screen instead: give
the pane the whole screen for as long as the editor lasts, then put it
back. Edge-triggered, so ctrl-f still wins.
This commit is contained in:
Jonas H
2026-09-07 11:52:43 +02:00
parent 043c99cdeb
commit ae454a3d2d
4 changed files with 152 additions and 4 deletions

View File

@@ -160,7 +160,10 @@ src/term.rs embedded claude pane: spawns `claude --session-id <uuid>` in a
see the pane-scroll invariant. `shows_error` reports whether
the compact frame is currently holding an error Claude Code
printed, which is what stops the ctrl-l wipe from deleting it —
see the pane-error invariant
see the pane-error invariant. `alt_screen` reports the
*alternate* screen — an `$EDITOR` (nvim, `git commit`, a pager)
Claude Code launched into the same pty — which auto-fullscreens
the pane; see the alt-screen invariant
src/reload.rs hot reload: ctrl-r `execve`s the binary now on disk *into this
process* — same pid, so the listener socket, the `claude` child
and (via a JSON snapshot) the live feed all cross over. Builds
@@ -571,6 +574,33 @@ agentId: <hex>`), and the real completion is injected into the parent's next
that already ran *before* you went fullscreen is gone for good though —
Ink redraws only the live frame, so fullscreen shows history from that
point on.
- **An editor on the child's alternate screen owns the whole pane.** ctrl-g
opens the prompt in `$EDITOR` (so do `/memory` and a `git commit` a tool
runs), and that program takes the pty over via the alternate buffer — which
Claude Code itself never does (the pane-scroll invariant leans on the same
fact). So `EmbeddedTerm::alt_screen` means exactly one thing: what is on
screen is not a prompt to frame, and framing it can only crop it
(`compact_frame` looks for the input box's two rules, which nvim never
draws). `ui::sync_alt_screen` therefore fullscreens the pane for as long as
the editor lasts and puts it back after. Four rules:
1. **Edge-triggered, never re-asserted per frame**, which is what leaves
ctrl-f in charge: a manual toggle mid-edit sticks instead of being undone
on the next draw, and it clears the restore flag
(`EmbedUi::alt_fullscreen`) so quitting the editor doesn't reverse it. A
pane that was *already* fullscreen stays fullscreen afterwards — only a
fullscreen we entered ourselves is undone.
2. **Gated on pane focus**, keeping fullscreen ⇔ focused: ctrl-↑ hands the
screen back to the feed mid-edit, ctrl-↓ returns it to the editor.
3. **The ctrl-l wipe is cancelled** while the alternate screen is up, for a
different reason than fullscreen's: that keystroke is meant for Claude
Code's input box, and sending it into nvim is not ours to do. The scroll
view also resets on both edges — the two screens index stable rows
differently, and the alternate one holds no scrollback at all.
4. **A hot reload reads the flag off the adopted child** before the first
draw, so an editor still open at ctrl-r raises no edge and the
snapshotted `PaneState::alt_fullscreen` stays meaningful.
- Tool input streams as raw JSON fragments; pretty-printed only on
`content_block_stop`. Streaming text re-renders markdown on every change
(FeedCache fingerprints by content length + done + result), so partial