refactor: type escaped OS strings and fix the final audit findings
Escaped config strings (ghostty_os::osbytes) kept reaching OS boundaries undecoded, and decoding was occasionally applied to plain text. The root cause was that both were plain `String`s. Now: - `EncodedOs` is a newtype (no Display/AsRef<Path>) that decodes only via to_os/to_path/to_bytes/to_wide; plain text enters via from_plain_text. OS helpers (env, home, xdg, passwd, resources dir, path expansion), config::Path, WorkingDirectory, theme/file_load/edit paths, terminal pwd, OpenUrl/Pwd actions and termio cwd use it, so the compiler forces decoding at every boundary. os::open takes &OsStr. Config output is unchanged. Fixes all rows of docs/rewrite/notes/review-rv5-codex.md (final-Z7.md): screen-file copy; regex/OSC 8 URLs no longer decoded (regression); lossless Windows WTF-8/WTF-16 for args, env and CreateProcessW; Windows resources dir/TERMINFO; color OSC request lists and X11 color lookup no longer allocate globally (C zero-allocation test extended); macOS Mach handler resumes all threads before KERN_FAILURE (hang regression) and SIGABRT minidumps go through the exception thread; plus open-config, background images, crash dir, ssh cache, exe dir/PATH, fontconfig filenames, file links, kitty TMPDIR, .hushlogin and benchmark paths. The typing also surfaced and fixed gtk-custom-css, bell audio, bash HISTFILE, OSC 52 writes and the C config getters. Test Plan: - cargo test --workspace (3376 passed) - just check-macos (both), just check-windows, just check-wasm - diff <(ghostty-rs +show-config --default --docs) \ <(ghostty +show-config --default --docs)
This commit is contained in:
1 parent
0f898d3e41
commit
8f3e206d9f
92 files changed
+2967
-992
No files matched your search
@@ -28,3 +28,4 @@ workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.27.0"
|
||||
temp-env = "0.3.6"
|
||||
@@ -85,7 +85,8 @@ fn run_inner(
|
||||
};
|
||||
format!("{} ago", d.round(1_000_000_000))
|
||||
};
|
||||
writeln!(stdout, "{} ({since})", report.name)?;
|
||||
// Display only: the file name is shown as text.
|
||||
writeln!(stdout, "{} ({since})", report.name.to_string_lossy())?;
|
||||
}
|
||||
|
||||
Ok(0)
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::io::Write;
|
||||
use std::os::unix::process::CommandExt;
|
||||
|
||||
use ghostty_config::config::Config;
|
||||
use ghostty_os::osbytes::EncodedOs;
|
||||
|
||||
use crate::error::Error;
|
||||
pub use crate::options::NoOptions as Options;
|
||||
@@ -43,7 +44,7 @@ pub fn run(args: &[String]) -> Result<u8, Error> {
|
||||
|
||||
/// Pick the editor from `$VISUAL` then `$EDITOR` (empty values are skipped).
|
||||
#[must_use]
|
||||
pub fn find_editor() -> Option<String> {
|
||||
pub fn find_editor() -> Option<EncodedOs> {
|
||||
// VISUAL vs. EDITOR: https://unix.stackexchange.com/questions/4859/visual-vs-editor-what-s-the-difference
|
||||
for var in ["VISUAL", "EDITOR"] {
|
||||
if let Some(v) = ghostty_os::getenv(var) {
|
||||
@@ -76,19 +77,20 @@ fn run_inner(stderr: &mut dyn Write) -> Result<u8, Error> {
|
||||
|
||||
/// Windows branch of `run`: print the path and fail.
|
||||
#[cfg(windows)]
|
||||
fn write_windows_unsupported(stderr: &mut dyn Write, path: &str) -> Result<u8, Error> {
|
||||
fn write_windows_unsupported(stderr: &mut dyn Write, path: &EncodedOs) -> Result<u8, Error> {
|
||||
write!(
|
||||
stderr,
|
||||
"The `ghostty +edit-config` command is not supported on Windows.\n\
|
||||
Please edit the configuration file manually at the following path:\n\
|
||||
\n\
|
||||
{path}\n"
|
||||
{}\n",
|
||||
path.as_encoded()
|
||||
)?;
|
||||
Ok(1)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn exec_editor(stderr: &mut dyn Write, path: &str) -> Result<u8, Error> {
|
||||
fn exec_editor(stderr: &mut dyn Write, path: &EncodedOs) -> Result<u8, Error> {
|
||||
// Get our editor
|
||||
let Some(editor) = find_editor() else {
|
||||
// If we don't have `$EDITOR` set then we can't do anything
|
||||
@@ -107,15 +109,16 @@ fn exec_editor(stderr: &mut dyn Write, path: &str) -> Result<u8, Error> {
|
||||
);
|
||||
// Output the path using the OSC8 sequence so that it is linked.
|
||||
// (Paths are raw bytes: decode the reversible escape when writing.)
|
||||
let msg = format!("{msg}\x1b]8;;file://{path}\x1b\\{path}\x1b]8;;\x1b\\\n");
|
||||
let p = path.as_encoded();
|
||||
let msg = format!("{msg}\x1b]8;;file://{p}\x1b\\{p}\x1b]8;;\x1b\\\n");
|
||||
stderr.write_all(&ghostty_os::osbytes::config_str_to_bytes(&msg))?;
|
||||
return Ok(1);
|
||||
};
|
||||
|
||||
// Flush before exec so no output is lost.
|
||||
let _ = stderr.flush();
|
||||
let err = std::process::Command::new(ghostty_os::osbytes::config_str_to_os(&editor))
|
||||
.arg(ghostty_os::osbytes::config_str_to_os(path))
|
||||
let err = std::process::Command::new(editor.to_os())
|
||||
.arg(path.to_os())
|
||||
.exec();
|
||||
|
||||
// If we reached this point then exec failed.
|
||||
@@ -131,8 +134,10 @@ fn exec_editor(stderr: &mut dyn Write, path: &str) -> Result<u8, Error> {
|
||||
permissions, or the shell environment not being set up\n\
|
||||
correctly.\n\
|
||||
\n\
|
||||
Editor: {editor}\n\
|
||||
Path: {path}\n"
|
||||
Editor: {}\n\
|
||||
Path: {}\n",
|
||||
editor.as_encoded(),
|
||||
path.as_encoded()
|
||||
);
|
||||
stderr.write_all(&ghostty_os::osbytes::config_str_to_bytes(&msg))?;
|
||||
Ok(1)
|
||||
|
||||
@@ -15,12 +15,7 @@ use ghostty_config::{
|
||||
config_struct,
|
||||
theme::{Location, LocationIterator},
|
||||
};
|
||||
use ghostty_os::osbytes::{
|
||||
config_str_to_bytes,
|
||||
config_str_to_path,
|
||||
os_to_config_string,
|
||||
path_to_config_string,
|
||||
};
|
||||
use ghostty_os::osbytes::{EncodedOs, config_str_to_bytes, os_to_config_string};
|
||||
|
||||
use crate::{error::Error, tui};
|
||||
|
||||
@@ -58,7 +53,7 @@ cli_options!(Options, help);
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ThemeListElement {
|
||||
pub location: Location,
|
||||
pub path: String,
|
||||
pub path: EncodedOs,
|
||||
pub theme: String,
|
||||
pub rank: Option<f64>,
|
||||
}
|
||||
@@ -76,7 +71,7 @@ impl ThemeListElement {
|
||||
#[must_use]
|
||||
pub fn to_uri(&self) -> String {
|
||||
let mut out = String::from("file://");
|
||||
for b in ghostty_os::osbytes::config_str_to_bytes(&self.path) {
|
||||
for b in self.path.to_bytes() {
|
||||
// std.Uri path component escaping: unreserved + sub-delims + ":@/".
|
||||
if b.is_ascii_alphanumeric()
|
||||
|| matches!(
|
||||
@@ -123,15 +118,15 @@ pub fn location_name(loc: Location) -> &'static str {
|
||||
/// Returns the unsorted list (in directory order). Errors opening a
|
||||
/// directory other than "not found" are printed to stderr and skipped.
|
||||
#[must_use]
|
||||
pub fn collect_themes(resources_dir: Option<&str>) -> Vec<ThemeListElement> {
|
||||
pub fn collect_themes(resources_dir: Option<&EncodedOs>) -> Vec<ThemeListElement> {
|
||||
let mut themes = vec![];
|
||||
let mut it = LocationIterator::new(resources_dir);
|
||||
while let Some((location, dir)) = it.next_location() {
|
||||
let rd = match std::fs::read_dir(config_str_to_path(&dir)) {
|
||||
let rd = match std::fs::read_dir(dir.to_path()) {
|
||||
Ok(rd) => rd,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
|
||||
Err(e) => {
|
||||
eprintln!("error trying to open {dir}: {e}");
|
||||
eprintln!("error trying to open {}: {e}", dir.as_encoded());
|
||||
continue;
|
||||
}
|
||||
};
|
||||
@@ -150,7 +145,7 @@ pub fn collect_themes(resources_dir: Option<&str>) -> Vec<ThemeListElement> {
|
||||
continue;
|
||||
}
|
||||
themes.push(ThemeListElement {
|
||||
path: path_to_config_string(&config_str_to_path(&dir).join(&raw_name)),
|
||||
path: EncodedOs::from_path(&dir.to_path().join(&raw_name)),
|
||||
location,
|
||||
theme: name,
|
||||
rank: None,
|
||||
@@ -174,15 +169,14 @@ pub fn should_include_theme(theme_filter: ColorScheme, theme_config: &Config) ->
|
||||
}
|
||||
|
||||
/// Make `path` absolute (`Config::load_file` requires absolute paths).
|
||||
pub(crate) fn absolute_path(path: &str) -> String {
|
||||
std::path::absolute(config_str_to_path(path))
|
||||
.map_or_else(|_| path.to_string(), |p| path_to_config_string(&p))
|
||||
pub(crate) fn absolute_path(path: &EncodedOs) -> EncodedOs {
|
||||
std::path::absolute(path.to_path()).map_or_else(|_| path.clone(), |p| EncodedOs::from_path(&p))
|
||||
}
|
||||
|
||||
fn resources_dir() -> Option<String> {
|
||||
fn resources_dir() -> Option<EncodedOs> {
|
||||
ghostty_os::resources_dir()
|
||||
.ok()
|
||||
.and_then(|r| r.app().map(str::to_string))
|
||||
.and_then(|r| r.app().cloned())
|
||||
}
|
||||
|
||||
/// The `list-themes` command is used to preview or list all the available
|
||||
@@ -238,7 +232,7 @@ pub fn run(args: &[String]) -> Result<u8, Error> {
|
||||
)?;
|
||||
}
|
||||
|
||||
let mut themes = collect_themes(resources_dir.as_deref());
|
||||
let mut themes = collect_themes(resources_dir.as_ref());
|
||||
|
||||
if themes.is_empty() {
|
||||
write!(
|
||||
@@ -281,7 +275,7 @@ pub fn write_plain(
|
||||
// Names/paths are raw bytes in Zig: decode the reversible escape so
|
||||
// non-UTF-8 bytes are written back unchanged.
|
||||
let line = if opts.path {
|
||||
format!("{} ({loc}) {}\n", theme.theme, theme.path)
|
||||
format!("{} ({loc}) {}\n", theme.theme, theme.path.as_encoded())
|
||||
} else {
|
||||
format!("{} ({loc})\n", theme.theme)
|
||||
};
|
||||
@@ -314,7 +308,7 @@ mod tests {
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let raw = std::ffi::OsStr::from_bytes(b"th\xffeme");
|
||||
std::fs::write(dir.join(raw), "background = #000000\n").unwrap();
|
||||
let res = tmp.path().to_string_lossy().into_owned();
|
||||
let res = EncodedOs::from_path(tmp.path());
|
||||
let themes: Vec<_> = collect_themes(Some(&res))
|
||||
.into_iter()
|
||||
.filter(|t| t.location == Location::Resources)
|
||||
@@ -322,7 +316,7 @@ mod tests {
|
||||
assert_eq!(1, themes.len());
|
||||
assert_eq!(config_str_to_bytes(&themes[0].theme), b"th\xffeme".to_vec());
|
||||
// The path points at the real file and the listing loads it.
|
||||
assert_eq!(config_str_to_path(&themes[0].path), dir.join(raw));
|
||||
assert_eq!(themes[0].path.to_path(), dir.join(raw));
|
||||
let mut out = Vec::new();
|
||||
write_plain(&mut out, &themes, &Options::default()).unwrap();
|
||||
assert_eq!(out, b"th\xffeme (resources)\n");
|
||||
@@ -336,7 +330,7 @@ mod tests {
|
||||
("Beta", "background = 202020\n"),
|
||||
(".DS_Store", "junk"),
|
||||
]);
|
||||
let res = tmp.path().to_string_lossy().into_owned();
|
||||
let res = EncodedOs::from_path(tmp.path());
|
||||
let mut themes: Vec<_> = collect_themes(Some(&res))
|
||||
.into_iter()
|
||||
.filter(|t| t.location == Location::Resources)
|
||||
@@ -365,7 +359,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!("Zeta (resources)\n", list(ColorScheme::Light, false));
|
||||
assert_eq!(
|
||||
format!("Zeta (resources) {res}/themes/Zeta\n"),
|
||||
format!("Zeta (resources) {}/themes/Zeta\n", res.as_encoded()),
|
||||
list(ColorScheme::Light, true)
|
||||
);
|
||||
}
|
||||
@@ -392,7 +386,7 @@ mod tests {
|
||||
fn uri_escaping() {
|
||||
let t = ThemeListElement {
|
||||
location: Location::User,
|
||||
path: "/a b/c%d".into(),
|
||||
path: EncodedOs::from_encoded("/a b/c%d"),
|
||||
theme: "x".into(),
|
||||
rank: None,
|
||||
};
|
||||
@@ -403,7 +397,7 @@ mod tests {
|
||||
fn uri_escaping_percent_encodes_original_bytes() {
|
||||
let t = ThemeListElement {
|
||||
location: Location::User,
|
||||
path: ghostty_os::osbytes::bytes_to_config_string(b"/a/\xff\xc3x"),
|
||||
path: EncodedOs::from_bytes(b"/a/\xff\xc3x"),
|
||||
theme: "x".into(),
|
||||
rank: None,
|
||||
};
|
||||
|
||||
@@ -353,8 +353,8 @@ enum Loaded {
|
||||
/// A pending side effect requested by the state (needs the terminal).
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Effect {
|
||||
/// Copy the text to the clipboard (OSC 52).
|
||||
Copy(String),
|
||||
/// Copy the (decoded) bytes to the clipboard (OSC 52).
|
||||
Copy(ghostty_os::osbytes::EncodedOs),
|
||||
}
|
||||
|
||||
/// The preview state (Zig: `Preview`).
|
||||
@@ -373,9 +373,9 @@ pub struct Preview {
|
||||
mouse: Option<MouseEvent>,
|
||||
/// The list height as of the last draw (for clamping the window).
|
||||
list_height: u16,
|
||||
loaded: Option<(String, Loaded)>,
|
||||
loaded: Option<(ghostty_os::osbytes::EncodedOs, Loaded)>,
|
||||
/// Where `w` writes the auto theme file (overridable for tests).
|
||||
pub auto_theme_path: Option<String>,
|
||||
pub auto_theme_path: Option<std::path::PathBuf>,
|
||||
effects: Vec<Effect>,
|
||||
overlays: Vec<Overlay>,
|
||||
}
|
||||
@@ -581,7 +581,8 @@ impl Preview {
|
||||
}
|
||||
if is_char('c') {
|
||||
if let Some(t) = self.selected() {
|
||||
let name = t.theme.clone();
|
||||
// Theme names are in the reversible OS-string encoding.
|
||||
let name = ghostty_os::osbytes::EncodedOs::from_encoded(t.theme.clone());
|
||||
self.effects.push(Effect::Copy(name));
|
||||
}
|
||||
} else if code == KeyCode::Char('C')
|
||||
@@ -965,10 +966,10 @@ impl Preview {
|
||||
(child.area.width / 2).saturating_sub(gw(&text) / 2),
|
||||
);
|
||||
child.print_ex(
|
||||
&theme.path,
|
||||
theme.path.as_encoded(),
|
||||
ui_err,
|
||||
middle,
|
||||
(child.area.width / 2).saturating_sub(gw(&theme.path) / 2),
|
||||
(child.area.width / 2).saturating_sub(gw(theme.path.as_encoded()) / 2),
|
||||
Some(&theme.to_uri()),
|
||||
None,
|
||||
);
|
||||
@@ -1024,10 +1025,10 @@ impl Preview {
|
||||
None,
|
||||
);
|
||||
child.print_ex(
|
||||
&theme.path,
|
||||
theme.path.as_encoded(),
|
||||
standard,
|
||||
2,
|
||||
(child.area.width / 2).saturating_sub(gw(&theme.path) / 2),
|
||||
(child.area.width / 2).saturating_sub(gw(theme.path.as_encoded()) / 2),
|
||||
Some(&uri),
|
||||
None,
|
||||
);
|
||||
@@ -1405,25 +1406,25 @@ fn error_name(e: &ghostty_config::config::Error) -> String {
|
||||
}
|
||||
|
||||
/// `resolveAutoThemePath`: `<config dir>/auto/theme.ghostty`.
|
||||
pub fn resolve_auto_theme_path() -> Result<String, Error> {
|
||||
pub fn resolve_auto_theme_path() -> Result<std::path::PathBuf, Error> {
|
||||
let main_cfg_path = ghostty_config::file_load::preferred_default_file_path()
|
||||
.map_err(|e| Error::Other(e.to_string()))?;
|
||||
let main_cfg_path = ghostty_os::osbytes::config_str_to_path(&main_cfg_path);
|
||||
.map_err(|e| Error::Other(e.to_string()))?
|
||||
.to_path();
|
||||
let base_dir = main_cfg_path
|
||||
.parent()
|
||||
.ok_or_else(|| Error::Other("BadPathName".to_string()))?;
|
||||
Ok(ghostty_os::osbytes::path_to_config_string(
|
||||
&base_dir.join("auto").join("theme.ghostty"),
|
||||
))
|
||||
Ok(base_dir.join("auto").join("theme.ghostty"))
|
||||
}
|
||||
|
||||
/// `writeAutoThemeFile`.
|
||||
pub fn write_auto_theme_file(path: Option<&str>, theme_name: &str) -> Result<(), Error> {
|
||||
pub fn write_auto_theme_file(
|
||||
path: Option<&std::path::Path>,
|
||||
theme_name: &str,
|
||||
) -> Result<(), Error> {
|
||||
let auto_path = match path {
|
||||
Some(p) => p.to_string(),
|
||||
Some(p) => p.to_path_buf(),
|
||||
None => resolve_auto_theme_path()?,
|
||||
};
|
||||
let auto_path = ghostty_os::osbytes::config_str_to_path(&auto_path);
|
||||
if let Some(dir) = auto_path.parent() {
|
||||
std::fs::create_dir_all(dir)?;
|
||||
}
|
||||
@@ -1737,11 +1738,7 @@ fn run_loop(app: &mut Preview) -> Result<(), Error> {
|
||||
match effect {
|
||||
Effect::Copy(text) => {
|
||||
let out = terminal.backend_mut();
|
||||
write!(
|
||||
out,
|
||||
"\x1b]52;c;{}\x1b\\",
|
||||
base64(&ghostty_os::osbytes::config_str_to_bytes(&text))
|
||||
)?;
|
||||
write!(out, "\x1b]52;c;{}\x1b\\", base64(&text.to_bytes()))?;
|
||||
out.flush()?;
|
||||
}
|
||||
}
|
||||
@@ -1788,7 +1785,7 @@ mod tests {
|
||||
.unwrap();
|
||||
ThemeListElement {
|
||||
location: Location::Resources,
|
||||
path: path.to_string_lossy().into_owned(),
|
||||
path: ghostty_os::osbytes::EncodedOs::from_path(&path),
|
||||
theme: (*name).to_string(),
|
||||
rank: None,
|
||||
}
|
||||
@@ -1939,14 +1936,19 @@ mod tests {
|
||||
KeyModifiers::SHIFT,
|
||||
)));
|
||||
let effects = p.take_effects();
|
||||
assert_eq!(Effect::Copy("alpha".into()), effects[0]);
|
||||
assert_eq!(
|
||||
Effect::Copy(tmp.path().join("alpha").to_string_lossy().into_owned()),
|
||||
Effect::Copy(ghostty_os::osbytes::EncodedOs::from_encoded("alpha")),
|
||||
effects[0]
|
||||
);
|
||||
assert_eq!(
|
||||
Effect::Copy(ghostty_os::osbytes::EncodedOs::from_path(
|
||||
&tmp.path().join("alpha")
|
||||
)),
|
||||
effects[1]
|
||||
);
|
||||
|
||||
let auto = tmp.path().join("cfg/auto/theme.ghostty");
|
||||
p.auto_theme_path = Some(auto.to_string_lossy().into_owned());
|
||||
p.auto_theme_path = Some(auto.clone());
|
||||
p.update(&key(KeyCode::Enter));
|
||||
assert_eq!(Mode::Save, p.mode);
|
||||
p.update(&ch('w'));
|
||||
|
||||
@@ -8,6 +8,7 @@ use std::sync::OnceLock;
|
||||
|
||||
use ghostty_apprt::ipc;
|
||||
use ghostty_config::cli_args::{ArgError, ArgIter, CliTarget};
|
||||
use ghostty_os::osbytes::EncodedOs;
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
@@ -56,15 +57,15 @@ impl Options {
|
||||
if stripped == "home" || stripped == "inherit" {
|
||||
return Ok(Some(arg.to_string()));
|
||||
}
|
||||
let expanded =
|
||||
ghostty_os::expand_home(stripped).map_err(|e| ArgError::Other(e.to_string()))?;
|
||||
let realpath =
|
||||
std::fs::canonicalize(ghostty_os::osbytes::config_str_to_path(&expanded))
|
||||
.map_err(|e| ArgError::Other(e.to_string()))?;
|
||||
// The CLI argument is an encoded OS string.
|
||||
let expanded = ghostty_os::expand_home(&EncodedOs::from_encoded(stripped))
|
||||
.map_err(|e| ArgError::Other(e.to_string()))?;
|
||||
let realpath = std::fs::canonicalize(expanded.to_path())
|
||||
.map_err(|e| ArgError::Other(e.to_string()))?;
|
||||
self.working_directory_seen = true;
|
||||
return Ok(Some(format!(
|
||||
"--working-directory={}",
|
||||
ghostty_os::osbytes::path_to_config_string(&realpath)
|
||||
EncodedOs::from_path(&realpath).as_encoded()
|
||||
)));
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ impl DiskCache {
|
||||
home: None,
|
||||
})
|
||||
.map_err(|_| Error::XdgLookupFailed)?;
|
||||
Ok(PathBuf::from(state_dir).join("ssh_cache"))
|
||||
Ok(state_dir.to_path().join("ssh_cache"))
|
||||
}
|
||||
|
||||
/// Clear all cache data stored in the disk cache.
|
||||
@@ -346,6 +346,20 @@ fn is_valid_user(user: &str) -> bool {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn default_path_decodes_non_utf8_xdg_state() {
|
||||
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
||||
let v = std::ffi::OsString::from_vec(b"/tmp/st\xffate".to_vec());
|
||||
let p = temp_env::with_var("XDG_STATE_HOME", Some(&v), || {
|
||||
super::DiskCache::default_path("ghostty").unwrap()
|
||||
});
|
||||
assert_eq!(
|
||||
p.as_os_str().as_bytes(),
|
||||
b"/tmp/st\xffate/ghostty/ssh_cache"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn lock_is_exclusive_until_dropped() {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use std::io::Write;
|
||||
|
||||
use ghostty_config::{config::Config, config_struct};
|
||||
use ghostty_os::osbytes::EncodedOs;
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
@@ -41,8 +42,8 @@ fn run_inner(opts: &Options, stdout: &mut dyn Write) -> Result<u8, Error> {
|
||||
if let Some(config_path) = &opts.config_file {
|
||||
// Byte-preserving: the option string carries non-UTF-8 bytes via the
|
||||
// reversible `osbytes` escape and the canonical path may too.
|
||||
let abs_path = std::fs::canonicalize(ghostty_os::osbytes::config_str_to_path(config_path))?;
|
||||
cfg.load_file(&ghostty_os::osbytes::path_to_config_string(&abs_path))?;
|
||||
let abs_path = std::fs::canonicalize(EncodedOs::from_encoded(config_path).to_path())?;
|
||||
cfg.load_file(&EncodedOs::from_path(&abs_path))?;
|
||||
cfg.load_recursive_files()?;
|
||||
} else {
|
||||
cfg = Config::load()?;
|
||||
|
||||
Reference in new issue
Block a user