fix: preserve non-UTF-8 bytes in OS, config and CLI layers
Zig treats paths, arguments and environment values as byte slices; the port converted them lossily in many places. Now (final-Z1.md): - argv, env (incl. present non-UTF-8 values), HOME, PATH lookup, passwd, resources dir, TMPDIR, cgroup, config path canonicalization, +validate-config, +list-themes and Zig-string escapes (\xFF) keep raw bytes. String-based config values carry bytes via ghostty_os::osbytes, a bijective escape (noncharacter U+FDD0 + byte) that leaves all valid UTF-8 - including Nerd Font private-use icons - unchanged. - os/file rlimit fallbacks exist on all targets. - i18n locale canonicalization is an exact port of libintl's tables (e.g. pa-Guru -> pa_IN). - New ghostty-build-info crate is the single source of version, channel, commit and build mode for all consumers. Test Plan: - cargo test -p ghostty-os -p ghostty-config -p ghostty-cli (incl. randomized osbytes round-trip property tests)
This commit is contained in:
1 parent
e4cdd8e007
commit
395cff8981
32 files changed
+1464
-333
No files matched your search
@@ -79,26 +79,49 @@ pub fn tr(msgid: &str) -> String {
|
||||
/// a POSIX-compliant value.
|
||||
///
|
||||
/// The Zig version is a thin layer over gnulib's
|
||||
/// `_libintl_locale_name_canonicalize` (only linked when building libintl
|
||||
/// from source, i.e. macOS). That function isn't available here, so this is
|
||||
/// a pure-Rust approximation of the cases that matter: BCP-47 style tags
|
||||
/// have `-` replaced with `_` and the Chinese script tags are mapped to
|
||||
/// their POSIX region equivalents. Encodings are NOT parsed out (like the
|
||||
/// original, `en_US.UTF-8` becomes `en_US.UTF_8`).
|
||||
/// `_libintl_locale_name_canonicalize` (the Mac OS X flavour of
|
||||
/// `gl_locale_name_canonicalize`), after fixing up some `zh` locales. This
|
||||
/// ports that function exactly, including its legacy-name, language-tag and
|
||||
/// script tables ([`crate::i18n_canon_table`]). Encodings are NOT parsed out
|
||||
/// (like the original, `en_US.UTF-8` becomes `en_US.UTF_8`).
|
||||
pub fn canonicalize_locale(locale: &str) -> String {
|
||||
// Fix zh locales
|
||||
if let Some(fixed) = fix_zh_locale(locale) {
|
||||
return fixed.to_owned();
|
||||
}
|
||||
|
||||
// Emulate gnulib's mapping of bare zh script tags.
|
||||
match locale {
|
||||
"zh-Hans" => return "zh_CN".to_owned(),
|
||||
"zh-Hant" => return "zh_TW".to_owned(),
|
||||
_ => {}
|
||||
libintl_locale_name_canonicalize(locale)
|
||||
}
|
||||
|
||||
/// `gl_locale_name_canonicalize` (gnulib `localename-unsafe.c`).
|
||||
fn libintl_locale_name_canonicalize(name: &str) -> String {
|
||||
use crate::i18n_canon_table::{LANGTAG_TABLE, LEGACY_TABLE, SCRIPT_TABLE};
|
||||
|
||||
// Step 1: Convert using legacy_table.
|
||||
if name.as_bytes().first().is_some_and(u8::is_ascii_uppercase)
|
||||
&& let Some((_, unixy)) = LEGACY_TABLE.iter().find(|(l, _)| *l == name)
|
||||
{
|
||||
return (*unixy).to_owned();
|
||||
}
|
||||
|
||||
locale.replace('-', "_")
|
||||
// Step 2: Convert using langtag_table and script_table.
|
||||
if name.len() == 7 && name.as_bytes()[2] == b'-' {
|
||||
if let Some((_, unixy)) = LANGTAG_TABLE.iter().find(|(l, _)| *l == name) {
|
||||
return (*unixy).to_owned();
|
||||
}
|
||||
|
||||
// `name.get(3..)` is None only when byte 3 is not a char boundary,
|
||||
// in which case no script entry can match.
|
||||
if let Some(script) = name.get(3..)
|
||||
&& let Some((_, unixy)) = SCRIPT_TABLE.iter().find(|(s, _)| *s == script)
|
||||
{
|
||||
// name[2] = '@'; strcpy (name + 3, unixy)
|
||||
return format!("{}@{unixy}", &name[..2]);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Convert new-style dash to Unix underscore.
|
||||
name.replace('-', "_")
|
||||
}
|
||||
|
||||
/// Handles some zh locales canonicalization because internal libintl
|
||||
@@ -183,6 +206,37 @@ mod tests {
|
||||
assert_eq!("en_US.UTF_8", canonicalize_locale("en_US.UTF-8"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canonicalize_locale_libintl_tables() {
|
||||
// langtag_table
|
||||
assert_eq!("pa_IN", canonicalize_locale("pa-Guru"));
|
||||
assert_eq!("pa_PK", canonicalize_locale("pa-Arab"));
|
||||
assert_eq!("az", canonicalize_locale("az-Latn"));
|
||||
assert_eq!("sr", canonicalize_locale("sr-Cyrl"));
|
||||
assert_eq!("kk", canonicalize_locale("kk-Cyrl"));
|
||||
assert_eq!("ga", canonicalize_locale("ga-dots"));
|
||||
// 8 bytes long: the `strlen == 7` guard skips the tables (upstream quirk).
|
||||
assert_eq!("yue_Hans", canonicalize_locale("yue-Hans"));
|
||||
// script_table (name[2] = '@')
|
||||
assert_eq!("sr@latin", canonicalize_locale("sr-Latn"));
|
||||
assert_eq!("uz@arabic", canonicalize_locale("uz-Arab"));
|
||||
assert_eq!("mn@mongolian", canonicalize_locale("mn-Mong"));
|
||||
// legacy_table (only for names starting uppercase)
|
||||
assert_eq!("pt_BR", canonicalize_locale("Brazilian Portuguese"));
|
||||
assert_eq!("zh_TW", canonicalize_locale("Traditional Chinese"));
|
||||
assert_eq!("de", canonicalize_locale("German"));
|
||||
assert_eq!("yi", canonicalize_locale("Yiddish"));
|
||||
assert_eq!("english", canonicalize_locale("english"));
|
||||
// dash fallback
|
||||
assert_eq!("pt_BR", canonicalize_locale("pt-BR"));
|
||||
assert_eq!("en_US", canonicalize_locale("en-US"));
|
||||
// non-ASCII input of length 7 must not panic
|
||||
assert_eq!(
|
||||
"\u{e9}\u{e9}\u{e9}x",
|
||||
canonicalize_locale("\u{e9}\u{e9}\u{e9}x")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn locales_map_and_static_locale() {
|
||||
assert!(is_supported_locale("zh_CN"));
|
||||
|
||||
Reference in new issue
Block a user