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 d1a5e55198
commit 2aa2a12de0
61 files changed
+4210 -298

No files matched your search

+35 -4
View File
@@ -13,7 +13,6 @@
//! async-signal-safe, like their Zig counterparts are expected to be).
use std::{
collections::HashMap,
ffi::{CString, NulError, c_char},
os::fd::OwnedFd,
panic::{AssertUnwindSafe, catch_unwind},
@@ -25,6 +24,7 @@ use rustix::{
process::{self, WaitOptions},
stdio,
};
use std::os::unix::ffi::OsStrExt;
/// Function executed /in the child process/ after the fork, but before
/// exec'ing the command. If the function returns `Some(code)`, the child
@@ -173,7 +173,7 @@ pub struct Command {
/// Environment variables for the child process. If this is `None`,
/// inherits the environment variables from this process. These are the
/// exact environment variables to set; these are /not/ merged.
pub env: Option<HashMap<String, String>>,
pub env: Option<ghostty_os::EnvMap>,
/// Working directory to change to in the child process. If not set, the
/// working directory of the calling process is preserved. Failure to
@@ -249,7 +249,12 @@ impl Command {
let envp = match &self.env {
Some(map) => map
.iter()
.map(|(k, v)| CString::new(format!("{k}={v}")))
.map(|(k, v)| {
let mut b = k.as_bytes().to_vec();
b.push(b'=');
b.extend_from_slice(v.as_bytes());
CString::new(b)
})
.collect::<Result<_, _>>()?,
// Inherit: snapshot our environment before the fork (nothing may
// allocate in the child).
@@ -738,13 +743,39 @@ mod tests {
let mut cmd = sh("echo $VALUE");
cmd.stdout = Some(stdout.try_clone().unwrap().into());
cmd.env = Some(HashMap::from([("VALUE".to_owned(), "hello".to_owned())]));
cmd.env = Some(ghostty_os::EnvMap::from_iter([("VALUE", "hello")]));
cmd.start().unwrap();
assert_eq!(cmd.wait(true).unwrap(), Exit::Exited(0));
assert_eq!("hello\n", read_all(stdout));
}
#[test]
fn non_utf8_env_value_is_preserved() {
use std::os::unix::ffi::OsStringExt;
let td = TempDir::init().unwrap();
let stdout = test_file(&td, "stdout.txt");
let mut cmd = sh("printf %s \"$VALUE\"");
cmd.stdout = Some(stdout.try_clone().unwrap().into());
let mut env = ghostty_os::EnvMap::new();
env.insert(
"VALUE",
std::ffi::OsString::from_vec(vec![b'a', 0xff, b'b']),
);
cmd.env = Some(env);
cmd.start().unwrap();
assert_eq!(cmd.wait(true).unwrap(), Exit::Exited(0));
let mut bytes = Vec::new();
std::io::Read::read_to_end(
&mut std::fs::File::open(td.path().join("stdout.txt")).unwrap(),
&mut bytes,
)
.unwrap();
assert_eq!(bytes, vec![b'a', 0xff, b'b']);
}
#[test]
fn custom_working_directory() {
let td = TempDir::init().unwrap();