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}