paths.rs

  1//! Paths to locations used by Zed.
  2
  3use std::env;
  4use std::path::{Path, PathBuf};
  5use std::sync::OnceLock;
  6
  7pub use util::paths::home_dir;
  8use util::paths::{SanitizedPath, SanitizedPathBuf};
  9
 10/// A default editorconfig file name to use when resolving project settings.
 11pub const EDITORCONFIG_NAME: &str = ".editorconfig";
 12
 13/// A custom data directory override, set only by `set_custom_data_dir`.
 14/// This is used to override the default data directory location.
 15/// The directory will be created if it doesn't exist when set.
 16static CUSTOM_DATA_DIR: OnceLock<SanitizedPathBuf> = OnceLock::new();
 17
 18/// The resolved data directory, combining custom override or platform defaults.
 19/// This is set once and cached for subsequent calls.
 20/// On macOS, this is `~/Library/Application Support/Zed`.
 21/// On Linux/FreeBSD, this is `$XDG_DATA_HOME/zed`.
 22/// On Windows, this is `%LOCALAPPDATA%\Zed`.
 23static CURRENT_DATA_DIR: OnceLock<SanitizedPathBuf> = OnceLock::new();
 24
 25/// The resolved config directory, combining custom override or platform defaults.
 26/// This is set once and cached for subsequent calls.
 27/// On macOS, this is `~/.config/zed`.
 28/// On Linux/FreeBSD, this is `$XDG_CONFIG_HOME/zed`.
 29/// On Windows, this is `%APPDATA%\Zed`.
 30static CONFIG_DIR: OnceLock<SanitizedPathBuf> = OnceLock::new();
 31
 32/// Returns the relative path to the zed_server directory on the ssh host.
 33pub fn remote_server_dir_relative() -> &'static SanitizedPath {
 34    SanitizedPath::new(".zed_server")
 35}
 36
 37/// Returns the relative path to the zed_wsl_server directory on the wsl host.
 38pub fn remote_wsl_server_dir_relative() -> &'static SanitizedPath {
 39    SanitizedPath::new(".zed_wsl_server")
 40}
 41
 42/// Sets a custom directory for all user data, overriding the default data directory.
 43/// This function must be called before any other path operations that depend on the data directory.
 44/// The directory's path will be canonicalized to an absolute path by a blocking FS operation.
 45/// The directory will be created if it doesn't exist.
 46///
 47/// # Arguments
 48///
 49/// * `dir` - The path to use as the custom data directory. This will be used as the base
 50///   directory for all user data, including databases, extensions, and logs.
 51///
 52/// # Returns
 53///
 54/// A reference to the static `PathBuf` containing the custom data directory path.
 55///
 56/// # Panics
 57///
 58/// Panics if:
 59/// * Called after the data directory has been initialized (e.g., via `data_dir` or `config_dir`)
 60/// * The directory's path cannot be canonicalized to an absolute path
 61/// * The directory cannot be created
 62pub fn set_custom_data_dir(dir: &str) -> &'static SanitizedPathBuf {
 63    if CURRENT_DATA_DIR.get().is_some() || CONFIG_DIR.get().is_some() {
 64        panic!("set_custom_data_dir called after data_dir or config_dir was initialized");
 65    }
 66    CUSTOM_DATA_DIR.get_or_init(|| {
 67        let mut path = SanitizedPathBuf::from(dir);
 68        if path.is_relative() {
 69            let abs_path = path
 70                .canonicalize()
 71                .expect("failed to canonicalize custom data directory's path to an absolute path");
 72            path = util::paths::SanitizedPath::new(&abs_path).into()
 73        }
 74        std::fs::create_dir_all(&path).expect("failed to create custom data directory");
 75        path
 76    })
 77}
 78
 79/// Returns the path to the configuration directory used by Zed.
 80pub fn config_dir() -> &'static SanitizedPathBuf {
 81    CONFIG_DIR.get_or_init(|| {
 82        if let Some(custom_dir) = CUSTOM_DATA_DIR.get() {
 83            custom_dir.join("config")
 84        } else if cfg!(target_os = "windows") {
 85            dirs::config_dir()
 86                .expect("failed to determine RoamingAppData directory")
 87                .join("Zed")
 88                .into()
 89        } else if cfg!(any(target_os = "linux", target_os = "freebsd")) {
 90            if let Ok(flatpak_xdg_config) = std::env::var("FLATPAK_XDG_CONFIG_HOME") {
 91                flatpak_xdg_config.into()
 92            } else {
 93                dirs::config_dir().expect("failed to determine XDG_CONFIG_HOME directory")
 94            }
 95            .join("zed")
 96            .into()
 97        } else {
 98            home_dir().join(".config").join("zed")
 99        }
100    })
101}
102
103/// Returns the path to the data directory used by Zed.
104pub fn data_dir() -> &'static SanitizedPathBuf {
105    CURRENT_DATA_DIR.get_or_init(|| {
106        if let Some(custom_dir) = CUSTOM_DATA_DIR.get() {
107            custom_dir.clone()
108        } else if cfg!(target_os = "macos") {
109            home_dir().join("Library/Application Support/Zed")
110        } else if cfg!(any(target_os = "linux", target_os = "freebsd")) {
111            if let Ok(flatpak_xdg_data) = std::env::var("FLATPAK_XDG_DATA_HOME") {
112                flatpak_xdg_data.into()
113            } else {
114                dirs::data_local_dir().expect("failed to determine XDG_DATA_HOME directory")
115            }
116            .join("zed")
117            .into()
118        } else if cfg!(target_os = "windows") {
119            dirs::data_local_dir()
120                .expect("failed to determine LocalAppData directory")
121                .join("Zed")
122                .into()
123        } else {
124            config_dir().clone() // Fallback
125        }
126    })
127}
128
129/// Returns the path to the temp directory used by Zed.
130pub fn temp_dir() -> &'static SanitizedPathBuf {
131    static TEMP_DIR: OnceLock<SanitizedPathBuf> = OnceLock::new();
132    TEMP_DIR.get_or_init(|| {
133        if cfg!(target_os = "macos") {
134            return dirs::cache_dir()
135                .expect("failed to determine cachesDirectory directory")
136                .join("Zed")
137                .into();
138        }
139
140        if cfg!(target_os = "windows") {
141            return dirs::cache_dir()
142                .expect("failed to determine LocalAppData directory")
143                .join("Zed")
144                .into();
145        }
146
147        if cfg!(any(target_os = "linux", target_os = "freebsd")) {
148            return if let Ok(flatpak_xdg_cache) = std::env::var("FLATPAK_XDG_CACHE_HOME") {
149                flatpak_xdg_cache.into()
150            } else {
151                dirs::cache_dir().expect("failed to determine XDG_CACHE_HOME directory")
152            }
153            .join("zed")
154            .into();
155        }
156
157        home_dir().join(".cache").join("zed")
158    })
159}
160
161/// Returns the path to the logs directory.
162pub fn logs_dir() -> &'static SanitizedPathBuf {
163    static LOGS_DIR: OnceLock<SanitizedPathBuf> = OnceLock::new();
164    LOGS_DIR.get_or_init(|| {
165        if cfg!(target_os = "macos") {
166            home_dir().join("Library/Logs/Zed")
167        } else {
168            data_dir().join("logs")
169        }
170    })
171}
172
173/// Returns the path to the Zed server directory on this SSH host.
174pub fn remote_server_state_dir() -> &'static SanitizedPathBuf {
175    static REMOTE_SERVER_STATE: OnceLock<SanitizedPathBuf> = OnceLock::new();
176    REMOTE_SERVER_STATE.get_or_init(|| data_dir().join("server_state"))
177}
178
179/// Returns the path to the `Zed.log` file.
180pub fn log_file() -> &'static SanitizedPathBuf {
181    static LOG_FILE: OnceLock<SanitizedPathBuf> = OnceLock::new();
182    LOG_FILE.get_or_init(|| logs_dir().join("Zed.log"))
183}
184
185/// Returns the path to the `Zed.log.old` file.
186pub fn old_log_file() -> &'static SanitizedPathBuf {
187    static OLD_LOG_FILE: OnceLock<SanitizedPathBuf> = OnceLock::new();
188    OLD_LOG_FILE.get_or_init(|| logs_dir().join("Zed.log.old"))
189}
190
191/// Returns the path to the database directory.
192pub fn database_dir() -> &'static SanitizedPathBuf {
193    static DATABASE_DIR: OnceLock<SanitizedPathBuf> = OnceLock::new();
194    DATABASE_DIR.get_or_init(|| data_dir().join("db"))
195}
196
197/// Returns the path to the crashes directory, if it exists for the current platform.
198pub fn crashes_dir() -> &'static Option<SanitizedPathBuf> {
199    static CRASHES_DIR: OnceLock<Option<SanitizedPathBuf>> = OnceLock::new();
200    CRASHES_DIR.get_or_init(|| {
201        cfg!(target_os = "macos").then_some(home_dir().join("Library/Logs/DiagnosticReports"))
202    })
203}
204
205/// Returns the path to the retired crashes directory, if it exists for the current platform.
206pub fn crashes_retired_dir() -> &'static Option<SanitizedPathBuf> {
207    static CRASHES_RETIRED_DIR: OnceLock<Option<SanitizedPathBuf>> = OnceLock::new();
208    CRASHES_RETIRED_DIR.get_or_init(|| crashes_dir().as_ref().map(|dir| dir.join("Retired")))
209}
210
211/// Returns the path to the `settings.json` file.
212pub fn settings_file() -> &'static SanitizedPathBuf {
213    static SETTINGS_FILE: OnceLock<SanitizedPathBuf> = OnceLock::new();
214    SETTINGS_FILE.get_or_init(|| config_dir().join("settings.json"))
215}
216
217/// Returns the path to the global settings file.
218pub fn global_settings_file() -> &'static SanitizedPathBuf {
219    static GLOBAL_SETTINGS_FILE: OnceLock<SanitizedPathBuf> = OnceLock::new();
220    GLOBAL_SETTINGS_FILE.get_or_init(|| config_dir().join("global_settings.json"))
221}
222
223/// Returns the path to the `settings_backup.json` file.
224pub fn settings_backup_file() -> &'static SanitizedPathBuf {
225    static SETTINGS_FILE: OnceLock<SanitizedPathBuf> = OnceLock::new();
226    SETTINGS_FILE.get_or_init(|| config_dir().join("settings_backup.json"))
227}
228
229/// Returns the path to the `keymap.json` file.
230pub fn keymap_file() -> &'static SanitizedPathBuf {
231    static KEYMAP_FILE: OnceLock<SanitizedPathBuf> = OnceLock::new();
232    KEYMAP_FILE.get_or_init(|| config_dir().join("keymap.json"))
233}
234
235/// Returns the path to the `keymap_backup.json` file.
236pub fn keymap_backup_file() -> &'static SanitizedPathBuf {
237    static KEYMAP_FILE: OnceLock<SanitizedPathBuf> = OnceLock::new();
238    KEYMAP_FILE.get_or_init(|| config_dir().join("keymap_backup.json"))
239}
240
241/// Returns the path to the `tasks.json` file.
242pub fn tasks_file() -> &'static SanitizedPathBuf {
243    static TASKS_FILE: OnceLock<SanitizedPathBuf> = OnceLock::new();
244    TASKS_FILE.get_or_init(|| config_dir().join("tasks.json"))
245}
246
247/// Returns the path to the `debug.json` file.
248pub fn debug_scenarios_file() -> &'static SanitizedPathBuf {
249    static DEBUG_SCENARIOS_FILE: OnceLock<SanitizedPathBuf> = OnceLock::new();
250    DEBUG_SCENARIOS_FILE.get_or_init(|| config_dir().join("debug.json"))
251}
252
253/// Returns the path to the extensions directory.
254///
255/// This is where installed extensions are stored.
256pub fn extensions_dir() -> &'static SanitizedPathBuf {
257    static EXTENSIONS_DIR: OnceLock<SanitizedPathBuf> = OnceLock::new();
258    EXTENSIONS_DIR.get_or_init(|| data_dir().join("extensions"))
259}
260
261/// Returns the path to the extensions directory.
262///
263/// This is where installed extensions are stored on a remote.
264pub fn remote_extensions_dir() -> &'static SanitizedPathBuf {
265    static EXTENSIONS_DIR: OnceLock<SanitizedPathBuf> = OnceLock::new();
266    EXTENSIONS_DIR.get_or_init(|| data_dir().join("remote_extensions"))
267}
268
269/// Returns the path to the extensions directory.
270///
271/// This is where installed extensions are stored on a remote.
272pub fn remote_extensions_uploads_dir() -> &'static SanitizedPathBuf {
273    static UPLOAD_DIR: OnceLock<SanitizedPathBuf> = OnceLock::new();
274    UPLOAD_DIR.get_or_init(|| remote_extensions_dir().join("uploads"))
275}
276
277/// Returns the path to the themes directory.
278///
279/// This is where themes that are not provided by extensions are stored.
280pub fn themes_dir() -> &'static SanitizedPathBuf {
281    static THEMES_DIR: OnceLock<SanitizedPathBuf> = OnceLock::new();
282    THEMES_DIR.get_or_init(|| config_dir().join("themes"))
283}
284
285/// Returns the path to the snippets directory.
286pub fn snippets_dir() -> &'static SanitizedPathBuf {
287    static SNIPPETS_DIR: OnceLock<SanitizedPathBuf> = OnceLock::new();
288    SNIPPETS_DIR.get_or_init(|| config_dir().join("snippets"))
289}
290
291/// Returns the path to the contexts directory.
292///
293/// This is where the saved contexts from the Assistant are stored.
294pub fn contexts_dir() -> &'static SanitizedPathBuf {
295    static CONTEXTS_DIR: OnceLock<SanitizedPathBuf> = OnceLock::new();
296    CONTEXTS_DIR.get_or_init(|| {
297        if cfg!(target_os = "macos") {
298            config_dir().join("conversations")
299        } else {
300            data_dir().join("conversations")
301        }
302    })
303}
304
305/// Returns the path to the contexts directory.
306///
307/// This is where the prompts for use with the Assistant are stored.
308pub fn prompts_dir() -> &'static SanitizedPathBuf {
309    static PROMPTS_DIR: OnceLock<SanitizedPathBuf> = OnceLock::new();
310    PROMPTS_DIR.get_or_init(|| {
311        if cfg!(target_os = "macos") {
312            config_dir().join("prompts")
313        } else {
314            data_dir().join("prompts")
315        }
316    })
317}
318
319/// Returns the path to the prompt templates directory.
320///
321/// This is where the prompt templates for core features can be overridden with templates.
322///
323/// # Arguments
324///
325/// * `dev_mode` - If true, assumes the current working directory is the Zed repository.
326pub fn prompt_overrides_dir(repo_path: Option<&SanitizedPath>) -> SanitizedPathBuf {
327    if let Some(path) = repo_path {
328        let dev_path = path.join("assets").join("prompts");
329        if dev_path.exists() {
330            return dev_path.into();
331        }
332    }
333
334    static PROMPT_TEMPLATES_DIR: OnceLock<SanitizedPathBuf> = OnceLock::new();
335    PROMPT_TEMPLATES_DIR
336        .get_or_init(|| {
337            if cfg!(target_os = "macos") {
338                config_dir().join("prompt_overrides")
339            } else {
340                data_dir().join("prompt_overrides")
341            }
342        })
343        .clone()
344}
345
346/// Returns the path to the semantic search's embeddings directory.
347///
348/// This is where the embeddings used to power semantic search are stored.
349pub fn embeddings_dir() -> &'static SanitizedPathBuf {
350    static EMBEDDINGS_DIR: OnceLock<SanitizedPathBuf> = OnceLock::new();
351    EMBEDDINGS_DIR.get_or_init(|| {
352        if cfg!(target_os = "macos") {
353            config_dir().join("embeddings")
354        } else {
355            data_dir().join("embeddings")
356        }
357    })
358}
359
360/// Returns the path to the languages directory.
361///
362/// This is where language servers are downloaded to for languages built-in to Zed.
363pub fn languages_dir() -> &'static SanitizedPathBuf {
364    static LANGUAGES_DIR: OnceLock<SanitizedPathBuf> = OnceLock::new();
365    LANGUAGES_DIR.get_or_init(|| data_dir().join("languages"))
366}
367
368/// Returns the path to the debug adapters directory
369///
370/// This is where debug adapters are downloaded to for DAPs that are built-in to Zed.
371pub fn debug_adapters_dir() -> &'static SanitizedPathBuf {
372    static DEBUG_ADAPTERS_DIR: OnceLock<SanitizedPathBuf> = OnceLock::new();
373    DEBUG_ADAPTERS_DIR.get_or_init(|| data_dir().join("debug_adapters"))
374}
375
376/// Returns the path to the agent servers directory
377///
378/// This is where agent servers are downloaded to
379pub fn agent_servers_dir() -> &'static SanitizedPathBuf {
380    static AGENT_SERVERS_DIR: OnceLock<SanitizedPathBuf> = OnceLock::new();
381    AGENT_SERVERS_DIR.get_or_init(|| data_dir().join("agent_servers"))
382}
383
384/// Returns the path to the Copilot directory.
385pub fn copilot_dir() -> &'static SanitizedPathBuf {
386    static COPILOT_DIR: OnceLock<SanitizedPathBuf> = OnceLock::new();
387    COPILOT_DIR.get_or_init(|| data_dir().join("copilot"))
388}
389
390/// Returns the path to the Supermaven directory.
391pub fn supermaven_dir() -> &'static SanitizedPathBuf {
392    static SUPERMAVEN_DIR: OnceLock<SanitizedPathBuf> = OnceLock::new();
393    SUPERMAVEN_DIR.get_or_init(|| data_dir().join("supermaven"))
394}
395
396/// Returns the path to the default Prettier directory.
397pub fn default_prettier_dir() -> &'static SanitizedPathBuf {
398    static DEFAULT_PRETTIER_DIR: OnceLock<SanitizedPathBuf> = OnceLock::new();
399    DEFAULT_PRETTIER_DIR.get_or_init(|| data_dir().join("prettier"))
400}
401
402/// Returns the path to the remote server binaries directory.
403pub fn remote_servers_dir() -> &'static SanitizedPathBuf {
404    static REMOTE_SERVERS_DIR: OnceLock<SanitizedPathBuf> = OnceLock::new();
405    REMOTE_SERVERS_DIR.get_or_init(|| data_dir().join("remote_servers"))
406}
407
408/// Returns the relative path to a `.zed` folder within a project.
409pub fn local_settings_folder_relative_path() -> &'static SanitizedPath {
410    SanitizedPath::new(".zed")
411}
412
413/// Returns the relative path to a `.vscode` folder within a project.
414pub fn local_vscode_folder_relative_path() -> &'static SanitizedPath {
415    SanitizedPath::new(".vscode")
416}
417
418/// Returns the relative path to a `settings.json` file within a project.
419pub fn local_settings_file_relative_path() -> &'static SanitizedPath {
420    SanitizedPath::new(".zed/settings.json")
421}
422
423/// Returns the relative path to a `tasks.json` file within a project.
424pub fn local_tasks_file_relative_path() -> &'static SanitizedPath {
425    SanitizedPath::new(".zed/tasks.json")
426}
427
428/// Returns the relative path to a `.vscode/tasks.json` file within a project.
429pub fn local_vscode_tasks_file_relative_path() -> &'static SanitizedPath {
430    SanitizedPath::new(".vscode/tasks.json")
431}
432
433pub fn debug_task_file_name() -> &'static str {
434    "debug.json"
435}
436
437pub fn task_file_name() -> &'static str {
438    "tasks.json"
439}
440
441/// Returns the relative path to a `debug.json` file within a project.
442/// .zed/debug.json
443pub fn local_debug_file_relative_path() -> &'static SanitizedPath {
444    SanitizedPath::new(".zed/debug.json")
445}
446
447/// Returns the relative path to a `.vscode/launch.json` file within a project.
448pub fn local_vscode_launch_file_relative_path() -> &'static SanitizedPath {
449    SanitizedPath::new(".vscode/launch.json")
450}
451
452pub fn user_ssh_config_file() -> SanitizedPathBuf {
453    home_dir().join(".ssh/config")
454}
455
456pub fn global_ssh_config_file() -> &'static SanitizedPath {
457    SanitizedPath::new("/etc/ssh/ssh_config")
458}
459
460/// Returns candidate paths for the vscode user settings file
461pub fn vscode_settings_file_paths() -> Vec<SanitizedPathBuf> {
462    let mut paths = vscode_user_data_paths();
463    for path in paths.iter_mut() {
464        path.push("User/settings.json");
465    }
466    paths
467}
468
469/// Returns candidate paths for the cursor user settings file
470pub fn cursor_settings_file_paths() -> Vec<SanitizedPathBuf> {
471    let mut paths = cursor_user_data_paths();
472    for path in paths.iter_mut() {
473        path.push("User/settings.json");
474    }
475    paths
476}
477
478fn vscode_user_data_paths() -> Vec<SanitizedPathBuf> {
479    // https://github.com/microsoft/vscode/blob/23e7148cdb6d8a27f0109ff77e5b1e019f8da051/src/vs/platform/environment/node/userDataPath.ts#L45
480    const VSCODE_PRODUCT_NAMES: &[&str] = &[
481        "Code",
482        "Code - OSS",
483        "VSCodium",
484        "Code Dev",
485        "Code - OSS Dev",
486        "code-oss-dev",
487    ];
488    let mut paths = Vec::new();
489    if let Ok(portable_path) = env::var("VSCODE_PORTABLE") {
490        paths.push(SanitizedPath::new(&portable_path).join("user-data"));
491    }
492    if let Ok(vscode_appdata) = env::var("VSCODE_APPDATA") {
493        for product_name in VSCODE_PRODUCT_NAMES {
494            paths.push(SanitizedPath::new(&vscode_appdata).join(product_name));
495        }
496    }
497    for product_name in VSCODE_PRODUCT_NAMES {
498        add_vscode_user_data_paths(&mut paths, product_name);
499    }
500    paths
501}
502
503fn cursor_user_data_paths() -> Vec<SanitizedPathBuf> {
504    let mut paths = Vec::new();
505    add_vscode_user_data_paths(&mut paths, "Cursor");
506    paths
507}
508
509fn add_vscode_user_data_paths(paths: &mut Vec<SanitizedPathBuf>, product_name: &str) {
510    if cfg!(target_os = "macos") {
511        paths.push(
512            home_dir()
513                .join("Library/Application Support")
514                .join(product_name),
515        );
516    } else if cfg!(target_os = "windows") {
517        if let Some(data_local_dir) = dirs::data_local_dir() {
518            paths.push(data_local_dir.join(product_name).into());
519        }
520        if let Some(data_dir) = dirs::data_dir() {
521            paths.push(data_dir.join(product_name).into());
522        }
523    } else {
524        paths.push(
525            dirs::config_dir()
526                .map(|e| e.into())
527                .unwrap_or(home_dir().join(".config"))
528                .join(product_name),
529        );
530    }
531}