Skip to content

feat(terminal): record shell integration (OSC 7 cwd + OSC 133 marks) - #956

Merged
nwparker merged 1 commit into
mainfrom
nwparker/shell-integration
Apr 22, 2026
Merged

nwparker merged 1 commit into
mainfrom
nwparker/shell-integration

Conversation

@nwparker

Copy link
Copy Markdown
Contributor

Summary

Wire the two shell-integration escape sequences that xterm.js does not handle natively, continuing the pattern established by #896 (DEC 2031) and #952 (OSC 52):

  • OSC 7 — the shell's current working directory, emitted on every cd by a shell whose rc includes a precmd hook. Recorded per-pane in paneCwdRef.
  • OSC 133 — FinalTerm / iTerm semantic prompt marks (A = prompt start, B = prompt end, C = command end, D[;code] = done). Recorded per-pane in a capped ring buffer with buffer-row coordinates.

Shipped consumer

The terminal context menu grows a platform-aware Reveal in Finder / Show in Explorer / Open Folder item that opens the shell's current directory in the native file manager. The item is hidden when no OSC 7 has been observed, consistent with how iTerm/tmux surface shell-integration-dependent features.

This gives users an immediate reason to enable OSC 7 in their rc and proves the plumbing works end-to-end — the anti-pattern of shipping infrastructure without consumers was explicitly discussed in the design pass.

Recorded-only (no consumer yet)

OSC 133 marks are recorded but no UI surfaces them. Recording is cheap (a push onto a ring buffer), and the consumer UX (block-nav keybinds, exit-code gutters, per-command timing) deserves its own design debate. Landing the recording now means future consumer PRs won't also have to add the parsing.

Design

See docs/shell-integration-design.md in the local worktree (gitignored per project convention for *-design.md).

Module layout

  • src/renderer/src/components/terminal-pane/shell-integration.ts — pure parsers (parseOsc7Path, parseOsc133Payload, pushSemanticMark).
  • src/renderer/src/components/terminal-pane/shell-integration.test.ts — 21 unit tests.
  • use-terminal-pane-lifecycle.ts — register/dispose handlers per-pane, populate the two refs.
  • use-terminal-pane-context-menu.ts — expose menuPaneCwd + onRevealCwd on the menu state.
  • TerminalContextMenu.tsx — render the platform-labeled Reveal item.

Memory safety

  • Cwd: one string per pane, dropped in onPaneClosed.
  • Marks: ring buffer capped at 10 000 entries per pane (~400 KB upper bound).

Security / Safety

  • OSC 7: file:// URI only; non-file schemes return null. Passed unchanged to shell.showItemInFolder via the existing shell:openPath IPC (which is read-only — it just selects the directory in the OS file manager).
  • OSC 133: inert recording — no code path reads exitCode or row in this PR.

Known non-goals (explicit)

  • Split inherits cwd. Requires extending PaneManager.splitPane to accept a per-pane cwd override and threading it through connectPanePty. Separate design, ~3× this PR's footprint.
  • OSC 133 UI. Deferred pending UX design.
  • Shell rc auto-injection. Users install the precmd snippet manually today. Auto-injection needs rc-file ownership / uninstall semantics / Fish + PowerShell variants — separate design.

Testing

  • pnpm lint
  • pnpm typecheck
  • 21 new parser unit tests, 134/134 terminal-pane tests passing
  • Manual: add printf '\e]7;file://%s%s\a' "$HOSTNAME" "$PWD" to ~/.zshrc precmd, confirm Reveal in Finder opens the current directory

Wire the two escape sequences that TUIs and modern shells use to tell
the terminal about state that xterm.js doesn't handle natively:

- OSC 7  — shell emits its current working directory on every `cd`.
- OSC 133 — FinalTerm / iTerm semantic prompt marks (A/B/C/D with
  optional exit code on D).

Parsing lives in a new pure `shell-integration.ts` helper with 21 unit
tests covering URI decoding, Windows UNC / drive-letter paths, all four
OSC 133 letters, the `key=value` hint suffix some emitters append, and
the ring-buffer cap on recorded marks.

Wiring follows the DEC 2031 / OSC 52 pattern from #896 and #952:

- Handlers registered per-pane in onPaneCreated, disposed symmetrically
  in onPaneClosed.
- Two new refs (`paneCwdRef`, `paneSemanticMarksRef`) live on
  TerminalPane and are passed through UseTerminalPaneLifecycleDeps.
- Semantic marks record the absolute buffer row (`baseY + cursorY`) so
  future block-navigation consumers can scroll to them.
- A 10 000-entry ring buffer caps per-pane memory.

Shipped consumer: the terminal context menu grows a platform-aware
"Reveal in Finder / Show in Explorer / Open Folder" item that opens
the shell's current directory in the native file manager. The item
is hidden when no OSC 7 has been received, consistent with how other
terminal emulators surface shell-integration-dependent features.

No consumer yet for OSC 133 — recording is cheap and the consumer UX
(block-nav keybinds, exit-code gutters) deserves its own design pass.
@nwparker
nwparker merged commit 953a6cd into main Apr 22, 2026
2 checks passed
enrick-dev pushed a commit to enrick-dev/orca that referenced this pull request Oct 15, 2026
…tablyai#956)

Wire the two escape sequences that TUIs and modern shells use to tell
the terminal about state that xterm.js doesn't handle natively:

- OSC 7  — shell emits its current working directory on every `cd`.
- OSC 133 — FinalTerm / iTerm semantic prompt marks (A/B/C/D with
  optional exit code on D).

Parsing lives in a new pure `shell-integration.ts` helper with 21 unit
tests covering URI decoding, Windows UNC / drive-letter paths, all four
OSC 133 letters, the `key=value` hint suffix some emitters append, and
the ring-buffer cap on recorded marks.

Wiring follows the DEC 2031 / OSC 52 pattern from stablyai#896 and stablyai#952:

- Handlers registered per-pane in onPaneCreated, disposed symmetrically
  in onPaneClosed.
- Two new refs (`paneCwdRef`, `paneSemanticMarksRef`) live on
  TerminalPane and are passed through UseTerminalPaneLifecycleDeps.
- Semantic marks record the absolute buffer row (`baseY + cursorY`) so
  future block-navigation consumers can scroll to them.
- A 10 000-entry ring buffer caps per-pane memory.

Shipped consumer: the terminal context menu grows a platform-aware
"Reveal in Finder / Show in Explorer / Open Folder" item that opens
the shell's current directory in the native file manager. The item
is hidden when no OSC 7 has been received, consistent with how other
terminal emulators surface shell-integration-dependent features.

No consumer yet for OSC 133 — recording is cheap and the consumer UX
(block-nav keybinds, exit-code gutters) deserves its own design pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant