Skip to main content

lean_ctx/core/editor_registry/
paths.rs

1use std::path::{Path, PathBuf};
2
3pub fn zed_settings_path(home: &std::path::Path) -> PathBuf {
4    if cfg!(target_os = "macos") {
5        home.join("Library/Application Support/Zed/settings.json")
6    } else {
7        home.join(".config/zed/settings.json")
8    }
9}
10
11pub fn zed_config_dir(home: &std::path::Path) -> PathBuf {
12    if cfg!(target_os = "macos") {
13        home.join("Library/Application Support/Zed")
14    } else {
15        home.join(".config/zed")
16    }
17}
18
19pub fn vscode_mcp_path() -> PathBuf {
20    if let Some(home) = dirs::home_dir() {
21        #[cfg(target_os = "macos")]
22        {
23            return home.join("Library/Application Support/Code/User/mcp.json");
24        }
25        #[cfg(target_os = "linux")]
26        {
27            return resolve_vscode_global_storage(&home, "User/mcp.json");
28        }
29        #[cfg(target_os = "windows")]
30        {
31            if let Ok(appdata) = std::env::var("APPDATA") {
32                return PathBuf::from(appdata).join("Code/User/mcp.json");
33            }
34        }
35        #[allow(unreachable_code)]
36        home.join(".config/Code/User/mcp.json")
37    } else {
38        PathBuf::from("/nonexistent")
39    }
40}
41
42pub fn qoder_mcp_path(home: &Path) -> PathBuf {
43    #[cfg(target_os = "windows")]
44    {
45        if let Ok(appdata) = std::env::var("APPDATA") {
46            return PathBuf::from(appdata)
47                .join("Qoder")
48                .join("SharedClientCache")
49                .join("mcp.json");
50        }
51    }
52    home.join(".qoder").join("mcp.json")
53}
54
55#[cfg(target_os = "macos")]
56pub fn qoder_mcp_paths(home: &Path) -> Vec<PathBuf> {
57    let mut paths = vec![qoder_mcp_path(home)];
58    paths.push(home.join("Library/Application Support/Qoder/User/mcp.json"));
59    paths.push(home.join("Library/Application Support/Qoder/SharedClientCache/mcp.json"));
60    paths
61}
62
63#[cfg(not(target_os = "macos"))]
64pub fn qoder_mcp_paths(home: &Path) -> Vec<PathBuf> {
65    vec![qoder_mcp_path(home)]
66}
67
68#[allow(unreachable_code)]
69pub fn cline_mcp_path() -> PathBuf {
70    #[cfg(target_os = "windows")]
71    {
72        if let Ok(appdata) = std::env::var("APPDATA") {
73            return PathBuf::from(appdata).join(
74                "Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json",
75            );
76        }
77        return PathBuf::from("/nonexistent");
78    }
79
80    let Some(home) = dirs::home_dir() else {
81        return PathBuf::from("/nonexistent");
82    };
83    #[cfg(target_os = "macos")]
84    {
85        return home.join("Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json");
86    }
87    #[cfg(target_os = "linux")]
88    {
89        let suffix = "User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json";
90        return resolve_vscode_global_storage(&home, suffix);
91    }
92    PathBuf::from("/nonexistent")
93}
94
95#[allow(unreachable_code)]
96pub fn roo_mcp_path() -> PathBuf {
97    #[cfg(target_os = "windows")]
98    {
99        if let Ok(appdata) = std::env::var("APPDATA") {
100            return PathBuf::from(appdata)
101                .join("Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json");
102        }
103        return PathBuf::from("/nonexistent");
104    }
105
106    let Some(home) = dirs::home_dir() else {
107        return PathBuf::from("/nonexistent");
108    };
109    #[cfg(target_os = "macos")]
110    {
111        return home.join("Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json");
112    }
113    #[cfg(target_os = "linux")]
114    {
115        let suffix =
116            "User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json";
117        return resolve_vscode_global_storage(&home, suffix);
118    }
119    PathBuf::from("/nonexistent")
120}
121
122/// Resolves the correct VS Code-family base directory on Linux.
123/// Checks VSCodium, Code - OSS, Code, Code - Insiders, then the dev-container
124/// server dir (in that order) — returns the first existing path, falling back
125/// to the standard `Code` path for fresh installs. Most-specific first: a
126/// VSCodium user with a leftover `~/.config/Code` (one accidental Code launch
127/// is enough) must keep resolving to VSCodium. Dev containers use
128/// `.vscode-server/data` instead of `.config`, so we check both.
129#[cfg(target_os = "linux")]
130fn resolve_vscode_global_storage(home: &Path, suffix: &str) -> PathBuf {
131    const CANDIDATES: &[&str] = &[
132        ".config/VSCodium",
133        ".config/Code - OSS",
134        ".config/Code",
135        ".config/Code - Insiders",
136        ".vscode-server/data",
137    ];
138    for base in CANDIDATES {
139        let path = home.join(base);
140        if path.exists() {
141            return path.join(suffix);
142        }
143    }
144    home.join(".config/Code").join(suffix)
145}
146
147pub fn qoder_settings_path(home: &Path) -> PathBuf {
148    #[cfg(target_os = "windows")]
149    {
150        if let Ok(appdata) = std::env::var("APPDATA") {
151            return PathBuf::from(appdata)
152                .join("Qoder")
153                .join("SharedClientCache")
154                .join("mcp.json");
155        }
156    }
157    home.join(".qoder/mcp.json")
158}
159
160pub fn qoder_all_mcp_paths(home: &Path) -> Vec<PathBuf> {
161    let paths = vec![qoder_settings_path(home)];
162    #[cfg(target_os = "macos")]
163    let paths = {
164        let mut paths = paths;
165        paths.push(home.join("Library/Application Support/Qoder/User/mcp.json"));
166        paths.push(home.join("Library/Application Support/Qoder/SharedClientCache/mcp.json"));
167        paths
168    };
169    paths
170}
171
172pub fn qoderwork_mcp_path(home: &Path) -> PathBuf {
173    home.join(".qoderwork/mcp.json")
174}
175
176pub fn claude_mcp_json_path(home: &Path) -> PathBuf {
177    if let Ok(dir) = std::env::var("CLAUDE_CONFIG_DIR") {
178        let dir = dir.trim();
179        if !dir.is_empty() {
180            return PathBuf::from(dir).join(".claude.json");
181        }
182    }
183    home.join(".claude.json")
184}
185
186pub fn claude_state_dir(home: &Path) -> PathBuf {
187    if let Ok(dir) = std::env::var("CLAUDE_CONFIG_DIR") {
188        let dir = dir.trim();
189        if !dir.is_empty() {
190            return PathBuf::from(dir);
191        }
192    }
193    home.join(".claude")
194}
195
196pub fn claude_rules_dir(home: &Path) -> PathBuf {
197    claude_state_dir(home).join("rules")
198}
199
200pub fn codebuddy_mcp_json_path(home: &Path) -> PathBuf {
201    codebuddy_state_dir(home).join("mcp.json")
202}
203
204pub fn codebuddy_state_dir(home: &Path) -> PathBuf {
205    if let Ok(dir) = std::env::var("CODEBUDDY_CONFIG_DIR") {
206        let dir = dir.trim();
207        if !dir.is_empty() {
208            return PathBuf::from(dir);
209        }
210    }
211    home.join(".codebuddy")
212}
213
214pub fn codebuddy_rules_dir(home: &Path) -> PathBuf {
215    codebuddy_state_dir(home).join("rules")
216}
217
218pub fn augment_cli_settings_path(home: &Path) -> PathBuf {
219    home.join(".augment/settings.json")
220}
221
222/// MCP server list for the Augment VS Code extension.
223///
224/// The extension persists registered MCP servers as a top-level JSON array in
225/// its globalStorage directory. Confirmed empirically against
226/// `augment.vscode-augment` build shipped on 2026-05-21 (see PR description).
227///
228/// On Windows the User dir lives under `%APPDATA%/Code` rather than the
229/// user's home, so we honour that when the env var is set; we fall back to
230/// the home-relative path for tests and unusual setups.
231pub fn augment_vscode_mcp_path(home: &Path) -> PathBuf {
232    const TAIL: &str = "globalStorage/augment.vscode-augment/augment-global-state/mcpServers.json";
233
234    #[cfg(target_os = "macos")]
235    {
236        return home
237            .join("Library/Application Support/Code/User")
238            .join(TAIL);
239    }
240    #[cfg(target_os = "linux")]
241    {
242        for path in [
243            ".config/Code/User",
244            ".config/Code - Insiders/User",
245            ".vscode-server/data/User",
246        ] {
247            let full_path = home.join(path).join(TAIL);
248            if full_path.exists() {
249                return full_path;
250            }
251        }
252        // Fall back to primary path if none exist
253        return home.join(".config/Code/User").join(TAIL);
254    }
255    #[cfg(target_os = "windows")]
256    {
257        if let Ok(appdata) = std::env::var("APPDATA") {
258            return PathBuf::from(appdata).join("Code/User").join(TAIL);
259        }
260    }
261    #[allow(unreachable_code)]
262    home.join(".config/Code/User").join(TAIL)
263}
264
265#[cfg(test)]
266mod augment_tests {
267    use super::*;
268
269    #[test]
270    fn augment_cli_settings_path_is_under_dot_augment() {
271        let home = Path::new("/home/tester");
272        assert_eq!(
273            augment_cli_settings_path(home),
274            home.join(".augment").join("settings.json")
275        );
276    }
277
278    #[test]
279    #[cfg(target_os = "linux")]
280    fn augment_vscode_mcp_path_uses_linux_globalstorage() {
281        let home = Path::new("/home/tester");
282        assert_eq!(
283            augment_vscode_mcp_path(home),
284            home.join(".config/Code/User/globalStorage/augment.vscode-augment/augment-global-state/mcpServers.json")
285        );
286    }
287
288    #[test]
289    #[cfg(target_os = "macos")]
290    fn augment_vscode_mcp_path_uses_macos_application_support() {
291        let home = Path::new("/Users/tester");
292        assert_eq!(
293            augment_vscode_mcp_path(home),
294            home.join("Library/Application Support/Code/User/globalStorage/augment.vscode-augment/augment-global-state/mcpServers.json")
295        );
296    }
297
298    /// On Windows we honour `%APPDATA%` when set, falling back to a
299    /// home-relative path only when it is missing. We can't reliably mutate
300    /// process-wide env vars in a parallel test runner, so this test only
301    /// asserts the invariant tail (which is platform-agnostic) and that the
302    /// final segment is the expected file name. Both branches share that tail.
303    #[test]
304    #[cfg(target_os = "windows")]
305    fn augment_vscode_mcp_path_ends_with_globalstorage_tail() {
306        let home = Path::new("C:/Users/tester");
307        let path = augment_vscode_mcp_path(home);
308        let s = path.to_string_lossy().replace('\\', "/");
309        assert!(
310            s.ends_with(
311                "Code/User/globalStorage/augment.vscode-augment/augment-global-state/mcpServers.json"
312            ),
313            "unexpected windows path: {s}"
314        );
315    }
316}
317
318#[cfg(all(test, target_os = "macos"))]
319mod tests {
320    use super::*;
321
322    #[test]
323    #[cfg(target_os = "macos")]
324    fn qoder_mcp_paths_include_macos_user_and_shared_cache_locations() {
325        let home = Path::new("/Users/tester");
326        let paths = qoder_mcp_paths(home);
327
328        assert_eq!(
329            paths,
330            vec![
331                home.join(".qoder/mcp.json"),
332                home.join("Library/Application Support/Qoder/User/mcp.json"),
333                home.join("Library/Application Support/Qoder/SharedClientCache/mcp.json"),
334            ]
335        );
336    }
337}