Detailed changes
@@ -491,7 +491,7 @@ impl ThreadsDatabase {
let database_future = executor
.spawn({
let executor = executor.clone();
- let database_path = paths::support_dir().join("threads/threads-db.1.mdb");
+ let database_path = paths::data_dir().join("threads/threads-db.1.mdb");
async move { ThreadsDatabase::new(database_path, executor) }
})
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))
@@ -16,6 +16,7 @@ pub enum CliRequest {
wait: bool,
open_new_workspace: Option<bool>,
env: Option<HashMap<String, String>>,
+ user_data_dir: Option<String>,
},
}
@@ -26,7 +26,11 @@ struct Detect;
trait InstalledApp {
fn zed_version_string(&self) -> String;
fn launch(&self, ipc_url: String) -> anyhow::Result<()>;
- fn run_foreground(&self, ipc_url: String) -> io::Result<ExitStatus>;
+ fn run_foreground(
+ &self,
+ ipc_url: String,
+ user_data_dir: Option<&str>,
+ ) -> io::Result<ExitStatus>;
fn path(&self) -> PathBuf;
}
@@ -58,6 +62,13 @@ struct Args {
/// Create a new workspace
#[arg(short, long, overrides_with = "add")]
new: bool,
+ /// Sets a custom directory for all user data (e.g., database, extensions, logs).
+ /// This overrides the default platform-specific data directory location.
+ /// On macOS, the default is `~/Library/Application Support/Zed`.
+ /// On Linux/FreeBSD, the default is `$XDG_DATA_HOME/zed`.
+ /// On Windows, the default is `%LOCALAPPDATA%\Zed`.
+ #[arg(long, value_name = "DIR")]
+ user_data_dir: Option<String>,
/// The paths to open in Zed (space-separated).
///
/// Use `path:line:column` syntax to open a file at the given line and column.
@@ -135,6 +146,12 @@ fn main() -> Result<()> {
}
let args = Args::parse();
+ // Set custom data directory before any path operations
+ let user_data_dir = args.user_data_dir.clone();
+ if let Some(dir) = &user_data_dir {
+ paths::set_custom_data_dir(dir);
+ }
+
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
let args = flatpak::set_bin_if_no_escape(args);
@@ -246,6 +263,7 @@ fn main() -> Result<()> {
let sender: JoinHandle<anyhow::Result<()>> = thread::spawn({
let exit_status = exit_status.clone();
+ let user_data_dir_for_thread = user_data_dir.clone();
move || {
let (_, handshake) = server.accept().context("Handshake after Zed spawn")?;
let (tx, rx) = (handshake.requests, handshake.responses);
@@ -256,6 +274,7 @@ fn main() -> Result<()> {
wait: args.wait,
open_new_workspace,
env,
+ user_data_dir: user_data_dir_for_thread,
})?;
while let Ok(response) = rx.recv() {
@@ -291,7 +310,7 @@ fn main() -> Result<()> {
.collect();
if args.foreground {
- app.run_foreground(url)?;
+ app.run_foreground(url, user_data_dir.as_deref())?;
} else {
app.launch(url)?;
sender.join().unwrap()?;
@@ -437,7 +456,7 @@ mod linux {
}
fn launch(&self, ipc_url: String) -> anyhow::Result<()> {
- let sock_path = paths::support_dir().join(format!("zed-{}.sock", *RELEASE_CHANNEL));
+ let sock_path = paths::data_dir().join(format!("zed-{}.sock", *RELEASE_CHANNEL));
let sock = UnixDatagram::unbound()?;
if sock.connect(&sock_path).is_err() {
self.boot_background(ipc_url)?;
@@ -447,10 +466,17 @@ mod linux {
Ok(())
}
- fn run_foreground(&self, ipc_url: String) -> io::Result<ExitStatus> {
- std::process::Command::new(self.0.clone())
- .arg(ipc_url)
- .status()
+ fn run_foreground(
+ &self,
+ ipc_url: String,
+ user_data_dir: Option<&str>,
+ ) -> io::Result<ExitStatus> {
+ let mut cmd = std::process::Command::new(self.0.clone());
+ cmd.arg(ipc_url);
+ if let Some(dir) = user_data_dir {
+ cmd.arg("--user-data-dir").arg(dir);
+ }
+ cmd.status()
}
fn path(&self) -> PathBuf {
@@ -688,12 +714,17 @@ mod windows {
Ok(())
}
- fn run_foreground(&self, ipc_url: String) -> io::Result<ExitStatus> {
- std::process::Command::new(self.0.clone())
- .arg(ipc_url)
- .arg("--foreground")
- .spawn()?
- .wait()
+ fn run_foreground(
+ &self,
+ ipc_url: String,
+ user_data_dir: Option<&str>,
+ ) -> io::Result<ExitStatus> {
+ let mut cmd = std::process::Command::new(self.0.clone());
+ cmd.arg(ipc_url).arg("--foreground");
+ if let Some(dir) = user_data_dir {
+ cmd.arg("--user-data-dir").arg(dir);
+ }
+ cmd.spawn()?.wait()
}
fn path(&self) -> PathBuf {
@@ -875,13 +906,22 @@ mod mac_os {
Ok(())
}
- fn run_foreground(&self, ipc_url: String) -> io::Result<ExitStatus> {
+ fn run_foreground(
+ &self,
+ ipc_url: String,
+ user_data_dir: Option<&str>,
+ ) -> io::Result<ExitStatus> {
let path = match self {
Bundle::App { app_bundle, .. } => app_bundle.join("Contents/MacOS/zed"),
Bundle::LocalPath { executable, .. } => executable.clone(),
};
- std::process::Command::new(path).arg(ipc_url).status()
+ let mut cmd = std::process::Command::new(path);
+ cmd.arg(ipc_url);
+ if let Some(dir) = user_data_dir {
+ cmd.arg("--user-data-dir").arg(dir);
+ }
+ cmd.status()
}
fn path(&self) -> PathBuf {
@@ -53,7 +53,7 @@ impl IndexedDocsProvider for LocalRustdocProvider {
}
fn database_path(&self) -> PathBuf {
- paths::support_dir().join("docs/rust/rustdoc-db.1.mdb")
+ paths::data_dir().join("docs/rust/rustdoc-db.1.mdb")
}
async fn suggest_packages(&self) -> Result<Vec<PackageName>> {
@@ -144,7 +144,7 @@ impl IndexedDocsProvider for DocsDotRsProvider {
}
fn database_path(&self) -> PathBuf {
- paths::support_dir().join("docs/rust/docs-rs-db.1.mdb")
+ paths::data_dir().join("docs/rust/docs-rs-db.1.mdb")
}
async fn suggest_packages(&self) -> Result<Vec<PackageName>> {
@@ -312,7 +312,7 @@ impl ManagedNodeRuntime {
let version = Self::VERSION;
let folder_name = format!("node-{version}-{os}-{arch}");
- let node_containing_dir = paths::support_dir().join("node");
+ let node_containing_dir = paths::data_dir().join("node");
let node_dir = node_containing_dir.join(folder_name);
let node_binary = node_dir.join(Self::NODE_PATH);
let npm_file = node_dir.join(Self::NPM_PATH);
@@ -498,7 +498,7 @@ impl SystemNodeRuntime {
)
}
- let scratch_dir = paths::support_dir().join("node");
+ let scratch_dir = paths::data_dir().join("node");
fs::create_dir(&scratch_dir).await.ok();
fs::create_dir(scratch_dir.join("cache")).await.ok();
@@ -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";
@@ -172,6 +172,11 @@ fn fail_to_open_window(e: anyhow::Error, _cx: &mut App) {
fn main() {
let args = Args::parse();
+ // Set custom data directory.
+ if let Some(dir) = &args.user_data_dir {
+ paths::set_custom_data_dir(dir);
+ }
+
#[cfg(all(not(debug_assertions), target_os = "windows"))]
unsafe {
use windows::Win32::System::Console::{ATTACH_PARENT_PROCESS, AttachConsole};
@@ -962,6 +967,14 @@ struct Args {
/// URLs can either be `file://` or `zed://` scheme, or relative to <https://zed.dev>.
paths_or_urls: Vec<String>,
+ /// Sets a custom directory for all user data (e.g., database, extensions, logs).
+ /// This overrides the default platform-specific data directory location.
+ /// On macOS, the default is `~/Library/Application Support/Zed`.
+ /// On Linux/FreeBSD, the default is `$XDG_DATA_HOME/zed`.
+ /// On Windows, the default is `%LOCALAPPDATA%\Zed`.
+ #[arg(long, value_name = "DIR")]
+ user_data_dir: Option<String>,
+
/// Instructs zed to run as a dev server on this machine. (not implemented)
#[arg(long)]
dev_server_token: Option<String>,
@@ -151,7 +151,7 @@ pub fn listen_for_cli_connections(opener: OpenListener) -> Result<()> {
use release_channel::RELEASE_CHANNEL_NAME;
use std::os::unix::net::UnixDatagram;
- let sock_path = paths::support_dir().join(format!("zed-{}.sock", *RELEASE_CHANNEL_NAME));
+ let sock_path = paths::data_dir().join(format!("zed-{}.sock", *RELEASE_CHANNEL_NAME));
// remove the socket if the process listening on it has died
if let Err(e) = UnixDatagram::unbound()?.connect(&sock_path) {
if e.kind() == std::io::ErrorKind::ConnectionRefused {
@@ -261,6 +261,7 @@ pub async fn handle_cli_connection(
wait,
open_new_workspace,
env,
+ user_data_dir: _, // Ignore user_data_dir
} => {
if !urls.is_empty() {
cx.update(|cx| {
@@ -130,6 +130,7 @@ fn send_args_to_instance(args: &Args) -> anyhow::Result<()> {
wait: false,
open_new_workspace: None,
env: None,
+ user_data_dir: args.user_data_dir.clone(),
}
};