fix: close remaining gaps found by the completeness audit

Addresses the partial ports listed in
docs/rewrite/notes/review-completeness-codex.md (details in
docs/rewrite/notes/gaps-X2.md):
- Flatpak: the exit watcher no longer consumes the host command; a
  shared FlatpakSignalHandle lets closing a surface still SIGHUP the
  host process group.
- Child environment is byte-preserving end to end (ghostty_os::EnvMap
  of OsStrings) and Snap paths are filtered like filterSnapPaths.
- argv handling no longer panics on non-UTF-8 arguments.
- URI percent-decoding keeps raw bytes; OSC 7 pwd is passed through
  without lossy conversion.
- Keybinding comparison/hashing uses full Unicode case folding from a
  table generated from CaseFolding.txt (Unicode 17.0.0).
- Link regexes set Oniguruma's whole-search retry limit through the new
  minimal ghostty-onig crate (one SAFETY-commented FFI call).
- OSC 9 NUL-capacity check ordering matches Zig.
- Wayland registry binds/clears globals advertised after startup.
- Custom shaders may pass sampler2D to helper functions (naga adapter).
- +list-themes preview renders OSC 8 links, styled underlines and
  follows color-scheme reports.

Also includes the inspector's integration edits in shared files
(renderer state hook, termio VT recording, core/apprt-gtk wiring), X1's
config/input/cli additions for completion generation, and a fix for a
race in the termio backpressure test (it wrote before `stty raw`
applied, so the cooked line discipline dropped bytes past MAX_CANON).

Test Plan:
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
This commit is contained in:
ddidderr committed 2026-10-12 04:39:24 +02:00
1 parent 90ad602969
commit f4c020101c
61 files changed
+4210 -298

No files matched your search

+11
View File
@@ -442,6 +442,17 @@ impl Parser {
Some(self.buffer.clone())
}
/// Zig `writer.writeByte(0)` capacity check: when the fixed buffer is
/// full there is no room for the NUL terminator, so the parser becomes
/// invalid and this returns false.
pub fn reserve_nul(&mut self) -> bool {
if self.writer == Writer::Fixed && self.buffer.len() >= Self::MAX_BUF {
self.state = State::Invalid;
return false;
}
true
}
/// Like [`Parser::data`] but without reserving room for the NUL
/// terminator (for parsers that, like Zig's color parsers, read
/// `writer.buffered()` directly).
+63 -6
View File
@@ -11,25 +11,49 @@ fn parse_unsigned<T: std::str::FromStr>(bytes: &[u8]) -> Option<T> {
/// Parse OSC 9, which could be an iTerm2 notification or a ConEmu extension.
pub fn parse(parser: &mut Parser, _terminator_ch: Option<u8>) -> Option<Command> {
// Zig prologue: writer must exist. (The NUL-terminator capacity check is
// folded into `data()`.)
let data = parser.data()?;
// Zig prologue: writer must exist. The NUL-terminator reservation is NOT
// done here: Zig only appends the NUL (`writer.writeByte(0)`) in the
// branches that return text, so full-buffer control commands (sleep,
// xterm emulation, progress, ...) still parse.
let data = parser.buffered()?;
// Check first to see if this is a ConEmu OSC
// https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC
if let Some(cmd) = conemu(&data) {
if let Some((cmd, needs_nul)) = conemu(&data) {
if needs_nul && !parser.reserve_nul() {
return None;
}
return Some(cmd);
}
// If it's not a ConEmu OSC, it's an iTerm2 notification
if !parser.reserve_nul() {
return None;
}
Some(Command::ShowDesktopNotification {
title: Vec::new(),
body: data,
})
}
/// Returns None where the Zig code does `break :conemu`.
fn conemu(data: &[u8]) -> Option<Command> {
/// Returns None where the Zig code does `break :conemu`. The flag is true
/// when the Zig branch appends a NUL terminator (needs buffer room).
fn conemu(data: &[u8]) -> Option<(Command, bool)> {
let r = conemu_inner(data)?;
let needs_nul = matches!(
r,
Command::ConemuComment(_)
| Command::ConemuShowMessageBox(_)
| Command::ConemuChangeTabTitle(_)
| Command::ConemuGuimacro(_)
| Command::ConemuRunProcess(_)
| Command::ConemuOutputEnvironmentVariable(_)
| Command::ReportPwd { .. }
);
Some((r, needs_nul))
}
fn conemu_inner(data: &[u8]) -> Option<Command> {
if data.is_empty() {
return None;
}
@@ -218,6 +242,39 @@ mod tests {
);
}
fn run_opt(input: &str) -> Option<Command> {
let mut p = Parser::init(false);
for ch in input.bytes() {
p.next(ch);
}
p.end(Some(0x1b))
}
#[test]
fn full_buffer_control_commands_do_not_need_nul() {
let fill = Parser::MAX_BUF;
// OSC 9;1 sleep with a buffer that has no room for a NUL: Zig never
// appends one for this command, so it still parses (default 100ms).
let input = format!("9;1;{}", "x".repeat(fill - 2));
assert_eq!(
run_opt(&input),
Some(Command::ConemuSleep { duration_ms: 100 })
);
// OSC 9;5 wait for input.
let input = format!("9;5{}", "x".repeat(fill - 1));
assert_eq!(run_opt(&input), Some(Command::ConemuWaitInput));
}
#[test]
fn full_buffer_text_commands_are_invalid() {
let fill = Parser::MAX_BUF;
// These append a NUL in Zig, which fails on a full buffer.
let input = format!("9;6;{}", "a".repeat(fill - 2));
assert_eq!(run_opt(&input), None);
let input = format!("9;{}", "a".repeat(fill));
assert_eq!(run_opt(&input), None);
}
#[test]
fn osc_9_show_desktop_notification() {
notif("9;Hello world", "Hello world");