1use std::path::{Path, PathBuf};
2
3use crate::lsp::registry::ServerKind;
4
5pub fn find_workspace_root(file_path: &Path, markers: &[&str]) -> Option<PathBuf> {
10 let resolved_path = match std::fs::canonicalize(file_path) {
11 Ok(path) => path,
12 Err(_) => file_path.to_path_buf(),
13 };
14
15 let start_dir = if resolved_path.is_dir() {
16 resolved_path
17 } else {
18 resolved_path.parent()?.to_path_buf()
19 };
20
21 let mut current = Some(start_dir.as_path());
22 while let Some(dir) = current {
23 if markers.iter().any(|marker| dir.join(marker).exists()) {
24 return Some(dir.to_path_buf());
25 }
26
27 current = dir.parent();
28 }
29
30 None
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, Hash)]
36pub struct ServerKey {
37 pub kind: ServerKind,
38 pub root: PathBuf,
39}
40
41#[cfg(test)]
42mod tests {
43 use std::fs;
44 use std::path::PathBuf;
45
46 use tempfile::tempdir;
47
48 use super::{find_workspace_root, ServerKey};
49 use crate::lsp::registry::ServerKind;
50
51 #[test]
52 fn test_find_root_with_cargo_toml() {
53 let temp_dir = tempdir().unwrap();
54 let root = temp_dir.path().join("workspace");
55 let src_dir = root.join("src");
56 let file = src_dir.join("lib.rs");
57
58 fs::create_dir_all(&src_dir).unwrap();
59 fs::write(root.join("Cargo.toml"), "[package]\nname = \"demo\"\n").unwrap();
60 fs::write(&file, "fn main() {}\n").unwrap();
61
62 let expected_root = fs::canonicalize(&root).unwrap();
63 assert_eq!(
64 find_workspace_root(&file, &["Cargo.toml"]),
65 Some(expected_root)
66 );
67 }
68
69 #[test]
70 fn test_find_root_nested() {
71 let temp_dir = tempdir().unwrap();
72 let repo_root = temp_dir.path().join("repo");
73 let crate_root = repo_root.join("crates").join("foo");
74 let src_dir = crate_root.join("src");
75 let file = src_dir.join("lib.rs");
76
77 fs::create_dir_all(&src_dir).unwrap();
78 fs::write(repo_root.join("Cargo.toml"), "[workspace]\n").unwrap();
79 fs::write(crate_root.join("Cargo.toml"), "[package]\nname = \"foo\"\n").unwrap();
80 fs::write(&file, "fn main() {}\n").unwrap();
81
82 let expected_root = fs::canonicalize(&crate_root).unwrap();
83 assert_eq!(
84 find_workspace_root(&file, &["Cargo.toml"]),
85 Some(expected_root)
86 );
87 }
88
89 #[test]
90 fn test_find_root_none() {
91 let temp_dir = tempdir().unwrap();
92 let src_dir = temp_dir.path().join("src");
93 let file = src_dir.join("main.rs");
94
95 fs::create_dir_all(&src_dir).unwrap();
96 fs::write(&file, "fn main() {}\n").unwrap();
97
98 assert_eq!(find_workspace_root(&file, &["Cargo.toml"]), None);
99 }
100
101 #[test]
102 fn test_find_root_multiple_markers() {
103 let temp_dir = tempdir().unwrap();
104 let root = temp_dir.path().join("web");
105 let src_dir = root.join("src");
106 let file = src_dir.join("index.ts");
107
108 fs::create_dir_all(&src_dir).unwrap();
109 fs::write(root.join("tsconfig.json"), "{}\n").unwrap();
110 fs::create_dir(root.join("package.json")).unwrap();
111 fs::write(&file, "export {};\n").unwrap();
112
113 let expected_root = fs::canonicalize(&root).unwrap();
114 assert_eq!(
115 find_workspace_root(&file, &["tsconfig.json", "package.json"]),
116 Some(expected_root)
117 );
118 }
119
120 #[test]
121 fn test_server_key_equality() {
122 let root = PathBuf::from("/tmp/workspace");
123 let same = ServerKey {
124 kind: ServerKind::Rust,
125 root: root.clone(),
126 };
127 let equal = ServerKey {
128 kind: ServerKind::Rust,
129 root,
130 };
131 let different = ServerKey {
132 kind: ServerKind::Rust,
133 root: PathBuf::from("/tmp/other"),
134 };
135
136 assert_eq!(same, equal);
137 assert_ne!(same, different);
138 }
139}