Add --user-data-dir CLI flag and propose renaming support_dir to data_dir (#26886)

This PR introduces support for a `--user-data-dir` CLI flag to override
Zed's data directory and proposes renaming `support_dir` to `data_dir`
for better cross-platform clarity. It builds on the discussion in #25349
about custom data directories, aiming to provide a flexible
cross-platform solution.

### Changes

The PR is split into two commits:
1. **[feat(cli): add --user-data-dir to override data
directory](https://github.com/zed-industries/zed/pull/26886/commits/28e8889105847401e783d1739722d0998459fe5a)**
2. **[refactor(paths): rename support_dir to data_dir for cross-platform
clarity](https://github.com/zed-industries/zed/pull/26886/commits/affd2fc606b39af1b25432a688a9006229a8fc3a)**


### Context
Inspired by the need for custom data directories discussed in #25349,
this PR provides an immediate implementation in the first commit, while
the second commit suggests a naming improvement for broader appeal.
@mikayla-maki, I’d appreciate your feedback, especially on the rename
proposal, given your involvement in the original discussion!

### Testing
- `cargo build `
- `./target/debug/zed --user-data-dir ~/custom-data-dir`

Release Notes:
- Added --user-data-dir CLI flag

---------

Signed-off-by: Marko Kungla <marko.kungla@gmail.com>
This commit is contained in:
Marko Kungla 2025-04-11 00:16:43 +03:00 committed by GitHub
parent d88694f8da
commit 384868e597
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 172 additions and 71 deletions

View file

@ -5,61 +5,109 @@ use std::sync::OnceLock;
pub use util::paths::home_dir;
/// A default editorconfig file name to use when resolving project settings.
pub const EDITORCONFIG_NAME: &str = ".editorconfig";
/// 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.
static CUSTOM_DATA_DIR: OnceLock<PathBuf> = OnceLock::new();
/// The resolved data directory, combining custom override or platform defaults.
/// This is set once and cached for subsequent calls.
/// On macOS, this is `~/Library/Application Support/Zed`.
/// On Linux/FreeBSD, this is `$XDG_DATA_HOME/zed`.
/// On Windows, this is `%LOCALAPPDATA%\Zed`.
static CURRENT_DATA_DIR: OnceLock<PathBuf> = OnceLock::new();
/// The resolved config directory, combining custom override or platform defaults.
/// This is set once and cached for subsequent calls.
/// On macOS, this is `~/.config/zed`.
/// On Linux/FreeBSD, this is `$XDG_CONFIG_HOME/zed`.
/// On Windows, this is `%APPDATA%\Zed`.
static CONFIG_DIR: OnceLock<PathBuf> = OnceLock::new();
/// Returns the relative path to the zed_server directory on the ssh host.
pub fn remote_server_dir_relative() -> &'static Path {
Path::new(".zed_server")
}
/// Sets a custom directory for all user data, overriding the default data directory.
/// This function must be called before any other path operations that depend on the data directory.
/// The directory will be created if it doesn't exist.
///
/// # Arguments
///
/// * `dir` - The path to use as the custom data directory. This will be used as the base
/// directory for all user data, including databases, extensions, and logs.
///
/// # Returns
///
/// A reference to the static `PathBuf` containing the custom data directory path.
///
/// # Panics
///
/// Panics if:
/// * Called after the data directory has been initialized (e.g., via `data_dir` or `config_dir`)
/// * The directory cannot be created
pub fn set_custom_data_dir(dir: &str) -> &'static PathBuf {
if CURRENT_DATA_DIR.get().is_some() || CONFIG_DIR.get().is_some() {
panic!("set_custom_data_dir called after data_dir or config_dir was initialized");
}
CUSTOM_DATA_DIR.get_or_init(|| {
let path = PathBuf::from(dir);
std::fs::create_dir_all(&path).expect("failed to create custom data directory");
path
})
}
/// Returns the path to the configuration directory used by Zed.
pub fn config_dir() -> &'static PathBuf {
static CONFIG_DIR: OnceLock<PathBuf> = OnceLock::new();
CONFIG_DIR.get_or_init(|| {
if cfg!(target_os = "windows") {
return dirs::config_dir()
if let Some(custom_dir) = CUSTOM_DATA_DIR.get() {
custom_dir.join("config")
} else if cfg!(target_os = "windows") {
dirs::config_dir()
.expect("failed to determine RoamingAppData directory")
.join("Zed");
}
if cfg!(any(target_os = "linux", target_os = "freebsd")) {
return if let Ok(flatpak_xdg_config) = std::env::var("FLATPAK_XDG_CONFIG_HOME") {
.join("Zed")
} 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")
dirs::config_dir()
.expect("failed to determine XDG_CONFIG_HOME directory")
.join("zed")
}
.join("zed");
} else {
home_dir().join(".config").join("zed")
}
home_dir().join(".config").join("zed")
})
}
/// Returns the path to the support directory used by Zed.
pub fn support_dir() -> &'static PathBuf {
static SUPPORT_DIR: OnceLock<PathBuf> = OnceLock::new();
SUPPORT_DIR.get_or_init(|| {
if cfg!(target_os = "macos") {
return home_dir().join("Library/Application Support/Zed");
}
if cfg!(any(target_os = "linux", target_os = "freebsd")) {
return if let Ok(flatpak_xdg_data) = std::env::var("FLATPAK_XDG_DATA_HOME") {
/// Returns the path to the data directory used by Zed.
pub fn data_dir() -> &'static PathBuf {
CURRENT_DATA_DIR.get_or_init(|| {
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")
} 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")
dirs::data_local_dir()
.expect("failed to determine XDG_DATA_HOME directory")
.join("zed")
}
.join("zed");
}
if cfg!(target_os = "windows") {
return dirs::data_local_dir()
} else if cfg!(target_os = "windows") {
dirs::data_local_dir()
.expect("failed to determine LocalAppData directory")
.join("Zed");
.join("Zed")
} else {
config_dir().clone() // Fallback
}
config_dir().clone()
})
}
/// Returns the path to the temp directory used by Zed.
pub fn temp_dir() -> &'static PathBuf {
static TEMP_DIR: OnceLock<PathBuf> = OnceLock::new();
@ -96,7 +144,7 @@ pub fn logs_dir() -> &'static PathBuf {
if cfg!(target_os = "macos") {
home_dir().join("Library/Logs/Zed")
} else {
support_dir().join("logs")
data_dir().join("logs")
}
})
}
@ -104,7 +152,7 @@ pub fn logs_dir() -> &'static PathBuf {
/// Returns the path to the Zed server directory on this SSH host.
pub fn remote_server_state_dir() -> &'static PathBuf {
static REMOTE_SERVER_STATE: OnceLock<PathBuf> = OnceLock::new();
REMOTE_SERVER_STATE.get_or_init(|| support_dir().join("server_state"))
REMOTE_SERVER_STATE.get_or_init(|| data_dir().join("server_state"))
}
/// Returns the path to the `Zed.log` file.
@ -122,7 +170,7 @@ pub fn old_log_file() -> &'static PathBuf {
/// Returns the path to the database directory.
pub fn database_dir() -> &'static PathBuf {
static DATABASE_DIR: OnceLock<PathBuf> = OnceLock::new();
DATABASE_DIR.get_or_init(|| support_dir().join("db"))
DATABASE_DIR.get_or_init(|| data_dir().join("db"))
}
/// Returns the path to the crashes directory, if it exists for the current platform.
@ -180,7 +228,7 @@ pub fn debug_tasks_file() -> &'static PathBuf {
/// This is where installed extensions are stored.
pub fn extensions_dir() -> &'static PathBuf {
static EXTENSIONS_DIR: OnceLock<PathBuf> = OnceLock::new();
EXTENSIONS_DIR.get_or_init(|| support_dir().join("extensions"))
EXTENSIONS_DIR.get_or_init(|| data_dir().join("extensions"))
}
/// Returns the path to the extensions directory.
@ -188,7 +236,7 @@ pub fn extensions_dir() -> &'static PathBuf {
/// This is where installed extensions are stored on a remote.
pub fn remote_extensions_dir() -> &'static PathBuf {
static EXTENSIONS_DIR: OnceLock<PathBuf> = OnceLock::new();
EXTENSIONS_DIR.get_or_init(|| support_dir().join("remote_extensions"))
EXTENSIONS_DIR.get_or_init(|| data_dir().join("remote_extensions"))
}
/// Returns the path to the extensions directory.
@ -222,7 +270,7 @@ pub fn contexts_dir() -> &'static PathBuf {
if cfg!(target_os = "macos") {
config_dir().join("conversations")
} else {
support_dir().join("conversations")
data_dir().join("conversations")
}
})
}
@ -236,7 +284,7 @@ pub fn prompts_dir() -> &'static PathBuf {
if cfg!(target_os = "macos") {
config_dir().join("prompts")
} else {
support_dir().join("prompts")
data_dir().join("prompts")
}
})
}
@ -262,7 +310,7 @@ pub fn prompt_overrides_dir(repo_path: Option<&Path>) -> PathBuf {
if cfg!(target_os = "macos") {
config_dir().join("prompt_overrides")
} else {
support_dir().join("prompt_overrides")
data_dir().join("prompt_overrides")
}
})
.clone()
@ -277,7 +325,7 @@ pub fn embeddings_dir() -> &'static PathBuf {
if cfg!(target_os = "macos") {
config_dir().join("embeddings")
} else {
support_dir().join("embeddings")
data_dir().join("embeddings")
}
})
}
@ -287,7 +335,7 @@ pub fn embeddings_dir() -> &'static PathBuf {
/// This is where language servers are downloaded to for languages built-in to Zed.
pub fn languages_dir() -> &'static PathBuf {
static LANGUAGES_DIR: OnceLock<PathBuf> = OnceLock::new();
LANGUAGES_DIR.get_or_init(|| support_dir().join("languages"))
LANGUAGES_DIR.get_or_init(|| data_dir().join("languages"))
}
/// Returns the path to the debug adapters directory
@ -295,31 +343,31 @@ pub fn languages_dir() -> &'static PathBuf {
/// This is where debug adapters are downloaded to for DAPs that are built-in to Zed.
pub fn debug_adapters_dir() -> &'static PathBuf {
static DEBUG_ADAPTERS_DIR: OnceLock<PathBuf> = OnceLock::new();
DEBUG_ADAPTERS_DIR.get_or_init(|| support_dir().join("debug_adapters"))
DEBUG_ADAPTERS_DIR.get_or_init(|| data_dir().join("debug_adapters"))
}
/// Returns the path to the Copilot directory.
pub fn copilot_dir() -> &'static PathBuf {
static COPILOT_DIR: OnceLock<PathBuf> = OnceLock::new();
COPILOT_DIR.get_or_init(|| support_dir().join("copilot"))
COPILOT_DIR.get_or_init(|| data_dir().join("copilot"))
}
/// Returns the path to the Supermaven directory.
pub fn supermaven_dir() -> &'static PathBuf {
static SUPERMAVEN_DIR: OnceLock<PathBuf> = OnceLock::new();
SUPERMAVEN_DIR.get_or_init(|| support_dir().join("supermaven"))
SUPERMAVEN_DIR.get_or_init(|| data_dir().join("supermaven"))
}
/// Returns the path to the default Prettier directory.
pub fn default_prettier_dir() -> &'static PathBuf {
static DEFAULT_PRETTIER_DIR: OnceLock<PathBuf> = OnceLock::new();
DEFAULT_PRETTIER_DIR.get_or_init(|| support_dir().join("prettier"))
DEFAULT_PRETTIER_DIR.get_or_init(|| data_dir().join("prettier"))
}
/// Returns the path to the remote server binaries directory.
pub fn remote_servers_dir() -> &'static PathBuf {
static REMOTE_SERVERS_DIR: OnceLock<PathBuf> = OnceLock::new();
REMOTE_SERVERS_DIR.get_or_init(|| support_dir().join("remote_servers"))
REMOTE_SERVERS_DIR.get_or_init(|| data_dir().join("remote_servers"))
}
/// Returns the relative path to a `.zed` folder within a project.
@ -359,6 +407,3 @@ pub fn local_debug_file_relative_path() -> &'static Path {
pub fn local_vscode_launch_file_relative_path() -> &'static Path {
Path::new(".vscode/launch.json")
}
/// A default editorconfig file name to use when resolving project settings.
pub const EDITORCONFIG_NAME: &str = ".editorconfig";