diff --git a/crates/paths/src/paths.rs b/crates/paths/src/paths.rs index 7afab7e81692e2b2953d2978b45b7aa20e8d137c..800ac414be160987eece80fe008ee0947e84b4c7 100644 --- a/crates/paths/src/paths.rs +++ b/crates/paths/src/paths.rs @@ -11,6 +11,41 @@ use util::rel_path::RelPath; /// A default editorconfig file name to use when resolving project settings. pub const EDITORCONFIG_NAME: &str = ".editorconfig"; +/// The application name, used to derive platform-specific data, config, cache, +/// and state directory paths. +/// +/// Forks should change this to avoid colliding with Zed's user data. +pub const APP_NAME: &str = "Zed"; + +/// Lowercased form of [`APP_NAME`], for use in XDG-style paths on +/// Linux/FreeBSD and the macOS `~/.config` fallback. +pub const APP_NAME_LOWERCASE: &str = { + assert!(!APP_NAME.is_empty(), "APP_NAME must not be empty"); + assert!(APP_NAME.as_bytes().is_ascii(), "APP_NAME must be ASCII"); + const BYTES: [u8; APP_NAME.len()] = { + let mut bytes = [0u8; APP_NAME.len()]; + let mut i = 0; + while i < APP_NAME.len() { + assert!( + APP_NAME.as_bytes()[i] != b'/' && APP_NAME.as_bytes()[i] != b'\\', + "APP_NAME must not contain path separators", + ); + assert!( + APP_NAME.as_bytes()[i] >= 0x20, + "APP_NAME must not contain control characters" + ); + bytes[i] = APP_NAME.as_bytes()[i]; + i += 1; + } + bytes.make_ascii_lowercase(); + bytes + }; + match std::str::from_utf8(&BYTES) { + Ok(s) => s, + Err(_) => unreachable!(), + } +}; + /// A custom data directory override, set only by `set_custom_data_dir`. /// This is used to override the default data directory location. /// The directory will be created if it doesn't exist when set. @@ -91,16 +126,16 @@ pub fn config_dir() -> &'static PathBuf { } else if cfg!(target_os = "windows") { dirs::config_dir() .expect("failed to determine RoamingAppData directory") - .join("Zed") + .join(APP_NAME) } else if cfg!(any(target_os = "linux", target_os = "freebsd")) { if let Ok(flatpak_xdg_config) = std::env::var("FLATPAK_XDG_CONFIG_HOME") { flatpak_xdg_config.into() } else { dirs::config_dir().expect("failed to determine XDG_CONFIG_HOME directory") } - .join("zed") + .join(APP_NAME_LOWERCASE) } else { - home_dir().join(".config").join("zed") + home_dir().join(".config").join(APP_NAME_LOWERCASE) } }) } @@ -111,18 +146,20 @@ pub fn data_dir() -> &'static PathBuf { if let Some(custom_dir) = CUSTOM_DATA_DIR.get() { custom_dir.clone() } else if cfg!(target_os = "macos") { - home_dir().join("Library/Application Support/Zed") + home_dir() + .join("Library/Application Support") + .join(APP_NAME) } else if cfg!(any(target_os = "linux", target_os = "freebsd")) { if let Ok(flatpak_xdg_data) = std::env::var("FLATPAK_XDG_DATA_HOME") { flatpak_xdg_data.into() } else { dirs::data_local_dir().expect("failed to determine XDG_DATA_HOME directory") } - .join("zed") + .join(APP_NAME_LOWERCASE) } else if cfg!(target_os = "windows") { dirs::data_local_dir() .expect("failed to determine LocalAppData directory") - .join("Zed") + .join(APP_NAME) } else { config_dir().clone() // Fallback } @@ -133,7 +170,7 @@ pub fn state_dir() -> &'static PathBuf { static STATE_DIR: OnceLock = OnceLock::new(); STATE_DIR.get_or_init(|| { if cfg!(target_os = "macos") { - return home_dir().join(".local").join("state").join("Zed"); + return home_dir().join(".local").join("state").join(APP_NAME); } if cfg!(any(target_os = "linux", target_os = "freebsd")) { @@ -142,12 +179,12 @@ pub fn state_dir() -> &'static PathBuf { } else { dirs::state_dir().expect("failed to determine XDG_STATE_HOME directory") } - .join("zed"); + .join(APP_NAME_LOWERCASE); } else { // Windows return dirs::data_local_dir() .expect("failed to determine LocalAppData directory") - .join("Zed"); + .join(APP_NAME); } }) } @@ -159,13 +196,13 @@ pub fn temp_dir() -> &'static PathBuf { if cfg!(target_os = "macos") { return dirs::cache_dir() .expect("failed to determine cachesDirectory directory") - .join("Zed"); + .join(APP_NAME); } if cfg!(target_os = "windows") { return dirs::cache_dir() .expect("failed to determine LocalAppData directory") - .join("Zed"); + .join(APP_NAME); } if cfg!(any(target_os = "linux", target_os = "freebsd")) { @@ -174,10 +211,10 @@ pub fn temp_dir() -> &'static PathBuf { } else { dirs::cache_dir().expect("failed to determine XDG_CACHE_HOME directory") } - .join("zed"); + .join(APP_NAME_LOWERCASE); } - home_dir().join(".cache").join("zed") + home_dir().join(".cache").join(APP_NAME_LOWERCASE) }) } @@ -192,7 +229,7 @@ pub fn logs_dir() -> &'static PathBuf { static LOGS_DIR: OnceLock = OnceLock::new(); LOGS_DIR.get_or_init(|| { if cfg!(target_os = "macos") { - home_dir().join("Library/Logs/Zed") + home_dir().join("Library/Logs").join(APP_NAME) } else { data_dir().join("logs") } @@ -208,13 +245,13 @@ pub fn remote_server_state_dir() -> &'static PathBuf { /// Returns the path to the `Zed.log` file. pub fn log_file() -> &'static PathBuf { static LOG_FILE: OnceLock = OnceLock::new(); - LOG_FILE.get_or_init(|| logs_dir().join("Zed.log")) + LOG_FILE.get_or_init(|| logs_dir().join(format!("{}.log", APP_NAME))) } /// Returns the path to the `Zed.log.old` file. pub fn old_log_file() -> &'static PathBuf { static OLD_LOG_FILE: OnceLock = OnceLock::new(); - OLD_LOG_FILE.get_or_init(|| logs_dir().join("Zed.log.old")) + OLD_LOG_FILE.get_or_init(|| logs_dir().join(format!("{}.log.old", APP_NAME))) } /// Returns the path to the database directory. diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index de49d220cd4b55efdafeb18a037b1e48ddcab5d2..6a706a56321bea110469d36c40425a1ac2a896fc 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -4,6 +4,16 @@ mod reliability; mod zed; +// Ensure the binary name stays in sync with APP_NAME so that the paths used +// at runtime (data dir, config dir, etc.) match what the binary is called. +const _: () = assert!( + paths::APP_NAME_LOWERCASE + .as_bytes() + .eq_ignore_ascii_case(env!("CARGO_BIN_NAME").as_bytes()), + "paths::APP_NAME_LOWERCASE must match the binary name. \ + Forks: update APP_NAME in crates/paths/src/paths.rs when renaming the binary.", +); + use agent::{SharedThread, ThreadStore}; use agent_client_protocol::schema as acp; use agent_ui::AgentPanel;