Skip to content

Make OSC 7 directory tracking robust to #, %, and escaped paths - #649

Merged
dakra merged 1 commit into
mainfrom
fix/osc7-path-escaping
Aug 24, 2026
Merged

dakra merged 1 commit into
mainfrom
fix/osc7-path-escaping

Conversation

@dakra

@dakra dakra commented Aug 23, 2026

Copy link
Copy Markdown
Owner

Problem

The shell integrations emit the raw $PWD inside a file:// URI and the Emacs side takes the parsed path verbatim. That round trip is self-consistent but fragile:

  • A # in any directory component is split off as a URI fragment by url-generic-parse-url. Locally, tracking silently stalls (the file-directory-p guard rejects the truncated path); remotely, a wrong TRAMP default-directory is committed without a check.
  • Percent-encoded reports from foreign emitters — vte.sh, WezTerm, fish's builtin OSC 7, nushell's native reporting (which escapes control characters) — leave literal %20 etc. in default-directory.

Fix

Adopts the kitty/ghostty two-scheme model on both sides (ghostty's shell integrations are the reference):

  • bash/zsh emit kitty's kitty-shell-cwd:// scheme, which carries the path verbatim — no shell-side encoder needed, and ghostty/kitty parse it too when the remote script runs under them (previously ghostty rejected our raw-space file:// URIs outright).
  • fish percent-encodes its file:// report via the string escape --style=url builtin, exactly like ghostty's fish integration.
  • ghostel--update-directory takes kitty-shell-cwd:// paths as-is (substring split so newlines survive; host downcased to keep the TRAMP connection identity consistent with file:// reports) and percent-decodes file:// paths UTF-8-aware, with %0A/%0D preserved and %00 rejected. When only the raw %XX spelling names a local directory (nushell, stale integration scripts), the raw spelling wins. A URI with an unrecognized scheme is reported instead of being mistaken for a plain OSC 9;9 path.

Compatibility

  • Updated ghostel still understands old raw file:// scripts (unescaped ASCII round-trips unchanged; literal-%XX names resolve locally via the raw-spelling fallback).
  • Remote (TRAMP) reports of literal-%XX names need the updated scripts; an old ghostel receiving the new scheme ignores it until both sides are current.

Verification

  • make -j8 all green (elisp + native + zig tests, checkdoc, package-lint).
  • New tests: kitty-scheme verbatim handling (#/?/literal %20), kitty-scheme remote TRAMP construction, file:// decoding (space, UTF-8, escaped %, raw fallback, decoded-wins-when-both-exist), a bash emitter→parser end-to-end round trip for a #/%/space path, and a fish wire-format probe. Windows-incompatible fixtures are tagged posix.
  • Live-verified in a sandboxed Emacs: bash, zsh, fish, and nushell each tracked a#b?c, literal 50%20off, and päth directories correctly, with raw OSC 7 wire captures confirming the emitted forms.

The shell integrations emitted the raw $PWD inside a file:// URI and
the Emacs side took the parsed path verbatim.  That round trip was
self-consistent but fragile: a `#' in any directory component was
split off as a URI fragment (silently stalling tracking locally,
committing a wrong TRAMP directory remotely), and percent-encoded
reports from foreign emitters (vte.sh, WezTerm, fish's builtin,
nushell's control-char encoding) left literal %XX in default-directory.

Adopt the kitty/ghostty two-scheme model on both sides:

- bash/zsh emit kitty's kitty-shell-cwd:// scheme, which carries the
  path verbatim, so no shell-side encoder is needed.
- fish percent-encodes its file:// report via the string escape
  builtin, exactly like ghostty's fish integration.
- ghostel--update-directory takes kitty-shell-cwd:// paths as-is
  (substring split, so newlines survive; host downcased to keep the
  TRAMP connection identity consistent with file:// reports) and
  percent-decodes file:// paths UTF-8-aware, with %0A/%0D preserved
  and %00 rejected.  When only the raw %XX spelling names a local
  directory (nushell, stale integration scripts), the raw spelling
  wins.  A URI with an unrecognized scheme is reported instead of
  being mistaken for a plain OSC 9;9 path.

Remote (TRAMP) reports of literal-%XX names still need the updated
scripts; an old ghostel receiving the new scheme ignores it until
both sides are current.
@dakra
dakra force-pushed the fix/osc7-path-escaping branch 3 times, most recently from 8e071a3 to 245438b Compare August 24, 2026 16:06
@dakra
dakra merged commit 245438b into main Aug 24, 2026
23 checks passed
@dakra
dakra deleted the fix/osc7-path-escaping branch August 24, 2026 16:06

This branch was previously deployed

1 inactive deployment
github-pages — 245438b0 Deployed Aug 24, 2026 by dakra via deploy #170
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