Skip to content

Route OSC actions through a custom stream handler#299

Merged
dakra merged 1 commit into
mainfrom
feat/osc-zig-dispatch
May 21, 2026
Merged

Route OSC actions through a custom stream handler#299
dakra merged 1 commit into
mainfrom
feat/osc-zig-dispatch

Conversation

@dakra
Copy link
Copy Markdown
Owner

@dakra dakra commented May 20, 2026

Summary

  • Replace ~395 lines of pre-write / post-write byte scanning in module.zig with a custom gt.Stream handler (src/GhostelHandler.zig) that wraps libghostty's standard terminal handler and overrides the OSC arms.
  • Single source of truth for OSC parsing — ghostty's parser (chunk-boundary safe, BEL/ST terminator handling, ConEmu/iTerm disambiguation, color-request lists) instead of a parallel scanner.
  • OSC 51 is the only OSC still scanned by hand because it is ghostel's elisp-eval extension, not a standard OSC.

Behavioural notes (carried in test fixtures)

  • OSC 7 with an embedded ESC truncates at the second \e] (ghostty parser behavior); test expects PWD="PARTIAL".
  • Empty notifications (\e]9;\e\\ / \e]777;notify;;\e\\) drop at the FFI boundary.
  • OSC 9;4;1 → progress=0 (parser pre-fill); OSC 9;4;1;<garbage>progress=nil (parser writes null over the pre-fill). Documented quirk, no defensive default.
  • ConEmu OSC 9 sub-codes (9;5, 9;12<anything>) are swallowed by ghostty's parser before they can re-route to iTerm notifications.
  • OSC 133 N (new_command) maps to the same A marker ghostel already uses for fresh-line prompts.

Test plan

  • make -j8 all test-evil — 78 ert + native + evil tests, lint, byte-compile clean
  • Smoke-test in emacsclient: prompt navigation (OSC 133), shell pwd tracking (OSC 7 / 9;9), color-scheme query reply (OSC 10 / 11 / 4), clipboard set (OSC 52), notification (OSC 9 / 777), progress (OSC 9;4).

Wrap libghostty's standard terminal handler in `GhostelHandler` and
override the OSC arms (semantic_prompt, color_operation, report_pwd,
clipboard_contents, show_desktop_notification, progress_report) instead
of byte-scanning the same OSC payloads in module.zig before and after
every PTY write.  Deletes ~395 lines of parallel scanner code (OscIterator,
extractOscColorQueries, dispatchPostWriteOscs, OSC 9 / 777 dispatch,
dynamic / palette color reply formatters) in favor of reading
ghostty's parsed Command values directly.

Behavioural notes carried in test fixtures:
- OSC 7 with an embedded ESC truncates at the second `\\e]` (ghostty
  parser's behavior); test expects PWD="PARTIAL".
- Empty notifications (`\\e]9;\\e\\\\` / `\\e]777;notify;;\\e\\\\`)
  are dropped at the FFI boundary.
- OSC 9;4;1 → progress=0 (parser pre-fill); OSC 9;4;1;<garbage> →
  progress=nil (parser writes null over the pre-fill).  Documented
  upstream quirk; no defensive default in the handler.
- ConEmu OSC 9 sub-codes (9;5, 9;12<anything>) are swallowed by the
  parser before they can be re-interpreted as iTerm notifications.

Only OSC 51 still uses a bespoke byte scan in module.zig because it
is ghostel's elisp-eval extension and not a standard OSC.
@emil-e
Copy link
Copy Markdown
Collaborator

emil-e commented May 20, 2026

Huge improvement!

Comment thread src/GhostelHandler.zig Outdated
Comment thread src/GhostelHandler.zig Outdated
Comment thread src/module.zig
/// scanner only sees one `data` slice at a time. Adding carry-over
/// would require per-`GhostelTerm` state; not worth it until an
/// OSC 51 chunk-spanning case actually shows up.
fn dispatchOsc51(env: emacs.Env, data: []const u8) void {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't libghostty have a parser for general OSCs? Might not be able to use the handler but I think we can at least us a parser?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a TODO, to replace it.
We can't use Ghostty's parser (see TODO comment).
But either they accept a change, OSC 51 is acually defined in xterm and reservered for "Emacs shell", so that would be best, but we could also use a unused clipboard sub-protocol.
I don't think someone cares if they emit 51;E or 52:E. but since 51 is the "Emacs shell" OSC, would be nice to use it.

@dakra dakra force-pushed the feat/osc-zig-dispatch branch from 4965270 to df7ea9b Compare May 21, 2026 05:20
@dakra dakra merged commit df7ea9b into main May 21, 2026
22 checks passed
@dakra dakra deleted the feat/osc-zig-dispatch branch May 21, 2026 05:36
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.

2 participants