terminal/tmux: decode %output octal escapes#12076
terminal/tmux: decode %output octal escapes#12076h3nock wants to merge 2 commits intoghostty-org:mainfrom
Conversation
I think I brought this up in the discussion you opened (and maybe you responded but I can't find it): why do we need to decode it during parsing vs. during use? My original thinking was to minimize parsing time because there may be users who listen to tmux control mode but don't actually want to use the output, and decoding is just wasted CPU cycles in that case. If we can decode within the same buffer, I feel like perhaps we can add a Thoughts on that kind of approach? |
my thinking was that
makes sense. esp for whatever reason some non rendering consumers are using this
agreed on this. it solves both of our concerns (wasted cpu cycle & abstracting the decoding logic from the user). and just tracking whether the payload was already decoded or not will make it idempotent one implementation detail, if |
Sorry for the delay. I think this is fine. |
|
Updated this PR with lazy |
TmuxCC port of ghostty's tmux control-mode parser (src/terminal/tmux/control.zig), covering the M1 subset per #20: - Token enum (blockEnd, blockError, output, sessionChanged, sessionRenamed, sessionsChanged, exit, unknown). - Line-oriented Parser state machine (idle / notification / block / broken) with buffer cap and broken-state drop-through. - decodeTmuxOutput() for %output octal (\ooo) escape decoding, ported from ghostty PR ghostty-org/ghostty#12076. Malformed sequences emit '?' rather than silently dropping. - %output payloads are kept as Data to preserve busted UTF-8. - 27 tests via Swift Testing (swift test), mirroring ghostty's control.zig test cases plus octal decoder coverage. Also lands the devenv/Nix scaffolding for the dev environment: - devenv.nix / devenv.yaml / devenv.lock (replaces .mise.toml). - .envrc for direnv auto-activation. - Makefile (fmt, fmt-check, lint, lint-fix, qa, secrets). - .swift-format, .swiftlint.yml, .gitleaks.toml configs. - .pre-commit-config.yaml wired through devenv git-hooks. - scripts/setup.sh updated for the devenv-first workflow. - CLAUDE.md expanded with toolchain / formatting / privacy rules.
TmuxCC port of ghostty's tmux control-mode parser (src/terminal/tmux/control.zig), covering the M1 subset per #20: - Token enum (blockEnd, blockError, output, sessionChanged, sessionRenamed, sessionsChanged, exit, unknown). - Line-oriented Parser state machine (idle / notification / block / broken) with buffer cap and broken-state drop-through. - decodeTmuxOutput() for %output octal (\ooo) escape decoding, ported from ghostty PR ghostty-org/ghostty#12076. Malformed sequences emit '?' rather than silently dropping. - %output payloads are kept as Data to preserve busted UTF-8. - 27 tests via Swift Testing (swift test), mirroring ghostty's control.zig test cases plus octal decoder coverage. Also lands the devenv/Nix scaffolding for the dev environment: - devenv.nix / devenv.yaml / devenv.lock (replaces .mise.toml). - .envrc for direnv auto-activation. - Makefile (fmt, fmt-check, lint, lint-fix, qa, secrets). - .swift-format, .swiftlint.yml, .gitleaks.toml configs. - .pre-commit-config.yaml wired through devenv git-hooks. - scripts/setup.sh updated for the devenv-first workflow. - CLAUDE.md expanded with toolchain / formatting / privacy rules.
What
Decode octal-escaped bytes in tmux control mode
%outputnotifications.Why
tmux control mode escapes control bytes and '\' as
\ooo(backslash + three octal digits), so the parser was forwarding encoded bytes without decoding them.From the tmux wiki:
Without decoding, pane output like
hello\r\nworldis emitted ashello\015\012worldin control mode and forwarded by the parser as literal characters\015\012instead of real CR LF bytes, so the terminal never sees the line break.Changes
decodeEscapedOutputtocontrol.zig: decodes escaped bytes in-place since the output is always ≤ the input length.%outputnotification path inparseNotification.Parser.put.Notes
u8to accumulate three octal digits because tmux only encodes bytes below 32 and \ (92 = \134) so 92 (\134) is the max we'd ever get. no overflow guard needed.used
?for malformed escaped sequences (fewer than 3 octal digits after ). realistically this shouldn't fire but worst case showing up ? is better than dropping the whole%outputpayload and also signals something went wrongRelates to Feature: Support for tmux's Control Mode #1935.
AI disclosure: Claude Code Opus 4.6 and Codex 5.4 xhigh were used for research, understanding Zig and helping me convert my ideas to Zig.