1use crate::client::LspClient;
7use crate::server::{LspRegistry, LspServerInfo};
8use crate::types::*;
9use anyhow::{anyhow, Result};
10use std::collections::{HashMap, HashSet};
11use std::path::{Path, PathBuf};
12use std::sync::Arc;
13use tokio::sync::{Mutex, RwLock};
14use tracing::{debug, error, info, warn};
15
16pub struct Lsp {
18 registry: LspRegistry,
20 clients: RwLock<HashMap<(PathBuf, String), Arc<LspClient>>>,
22 broken: RwLock<HashSet<(PathBuf, String)>>,
24 spawning: Mutex<HashMap<(PathBuf, String), tokio::sync::broadcast::Sender<()>>>,
26 working_dir: PathBuf,
28}
29
30impl Lsp {
31 pub fn new(working_dir: PathBuf) -> Self {
33 Self {
34 registry: LspRegistry::new(),
35 clients: RwLock::new(HashMap::new()),
36 broken: RwLock::new(HashSet::new()),
37 spawning: Mutex::new(HashMap::new()),
38 working_dir,
39 }
40 }
41
42 pub fn with_registry(working_dir: PathBuf, registry: LspRegistry) -> Self {
44 Self {
45 registry,
46 clients: RwLock::new(HashMap::new()),
47 broken: RwLock::new(HashSet::new()),
48 spawning: Mutex::new(HashMap::new()),
49 working_dir,
50 }
51 }
52
53 pub fn working_dir(&self) -> &Path {
55 &self.working_dir
56 }
57
58 pub async fn has_clients(&self, path: &Path) -> bool {
60 let path = self.resolve_path(path);
61 let ext = path
62 .extension()
63 .and_then(|e| e.to_str())
64 .map(|e| format!(".{}", e))
65 .unwrap_or_default();
66
67 for server in self.registry.servers_for_extension(&ext) {
68 if let Some(root) = server.detect_root(&path) {
69 let key = (root, server.id.clone());
70 let broken = self.broken.read().await;
71 if !broken.contains(&key) {
72 return true;
73 }
74 }
75 }
76
77 false
78 }
79
80 pub async fn clients_for_file(&self, path: &Path) -> Result<Vec<Arc<LspClient>>> {
82 let path = self.resolve_path(path);
83 let ext = path
84 .extension()
85 .and_then(|e| e.to_str())
86 .map(|e| format!(".{}", e))
87 .unwrap_or_default();
88
89 let mut result = Vec::new();
90
91 for server in self.registry.servers_for_extension(&ext) {
92 if let Some(client) = self.get_or_spawn_client(&path, server).await? {
93 result.push(client);
94 }
95 }
96
97 Ok(result)
98 }
99
100 pub async fn touch_file(&self, path: &Path, wait_for_diagnostics: bool) -> Result<()> {
102 let path = self.resolve_path(path);
103 let clients = self.clients_for_file(&path).await?;
104
105 for client in clients {
106 if let Err(e) = client.open_file(&path).await {
107 warn!(
108 server_id = %client.server_id(),
109 path = ?path,
110 error = ?e,
111 "Failed to open file in LSP"
112 );
113 }
114 }
115
116 if wait_for_diagnostics {
118 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
120 }
121
122 Ok(())
123 }
124
125 pub async fn hover(
131 &self,
132 path: &Path,
133 line: u32,
134 character: u32,
135 ) -> Result<Vec<Option<Hover>>> {
136 let path = self.resolve_path(path);
137 let path_clone = path.clone();
138 self.run_on_file(&path, move |client| {
139 let p = path_clone.clone();
140 async move { client.hover(&p, line, character).await }
141 })
142 .await
143 }
144
145 pub async fn definition(
147 &self,
148 path: &Path,
149 line: u32,
150 character: u32,
151 ) -> Result<Vec<Location>> {
152 let path = self.resolve_path(path);
153 let path_clone = path.clone();
154 let results = self
155 .run_on_file(&path, move |client| {
156 let p = path_clone.clone();
157 async move { client.definition(&p, line, character).await }
158 })
159 .await?;
160 Ok(results.into_iter().flatten().collect())
161 }
162
163 pub async fn references(
165 &self,
166 path: &Path,
167 line: u32,
168 character: u32,
169 ) -> Result<Vec<Location>> {
170 let path = self.resolve_path(path);
171 let path_clone = path.clone();
172 let results = self
173 .run_on_file(&path, move |client| {
174 let p = path_clone.clone();
175 async move { client.references(&p, line, character, true).await }
176 })
177 .await?;
178 Ok(results.into_iter().flatten().collect())
179 }
180
181 pub async fn implementation(
183 &self,
184 path: &Path,
185 line: u32,
186 character: u32,
187 ) -> Result<Vec<Location>> {
188 let path = self.resolve_path(path);
189 let path_clone = path.clone();
190 let results = self
191 .run_on_file(&path, move |client| {
192 let p = path_clone.clone();
193 async move { client.implementation(&p, line, character).await }
194 })
195 .await?;
196 Ok(results.into_iter().flatten().collect())
197 }
198
199 pub async fn document_symbols(&self, path: &Path) -> Result<Vec<DocumentSymbolResponse>> {
201 let path = self.resolve_path(path);
202 let path_clone = path.clone();
203 self.run_on_file(&path, move |client| {
204 let p = path_clone.clone();
205 async move { client.document_symbols(&p).await }
206 })
207 .await
208 }
209
210 pub async fn workspace_symbols(&self, query: &str) -> Result<Vec<SymbolInformation>> {
212 let clients = self.clients.read().await;
213 let mut results = Vec::new();
214
215 for client in clients.values() {
216 match client.workspace_symbols(query).await {
217 Ok(symbols) => {
218 let filtered: Vec<_> = symbols
220 .into_iter()
221 .filter(|s| IMPORTANT_SYMBOL_KINDS.contains(&s.kind))
222 .take(10)
223 .collect();
224 results.extend(filtered);
225 }
226 Err(e) => {
227 warn!(
228 server_id = %client.server_id(),
229 error = ?e,
230 "Failed to get workspace symbols"
231 );
232 }
233 }
234 }
235
236 Ok(results)
237 }
238
239 pub async fn prepare_call_hierarchy(
241 &self,
242 path: &Path,
243 line: u32,
244 character: u32,
245 ) -> Result<Vec<CallHierarchyItem>> {
246 let path = self.resolve_path(path);
247 let path_clone = path.clone();
248 let results = self
249 .run_on_file(&path, move |client| {
250 let p = path_clone.clone();
251 async move { client.prepare_call_hierarchy(&p, line, character).await }
252 })
253 .await?;
254 Ok(results.into_iter().flatten().collect())
255 }
256
257 pub async fn incoming_calls(
259 &self,
260 path: &Path,
261 line: u32,
262 character: u32,
263 ) -> Result<Vec<CallHierarchyIncomingCall>> {
264 let path = self.resolve_path(path);
265 let items = self.prepare_call_hierarchy(&path, line, character).await?;
266
267 if items.is_empty() {
268 return Ok(vec![]);
269 }
270
271 let item = items.into_iter().next().unwrap();
273 let results = self
274 .run_on_file(&path, |client| {
275 let item = item.clone();
276 async move { client.incoming_calls(item).await }
277 })
278 .await?;
279 Ok(results.into_iter().flatten().collect())
280 }
281
282 pub async fn outgoing_calls(
284 &self,
285 path: &Path,
286 line: u32,
287 character: u32,
288 ) -> Result<Vec<CallHierarchyOutgoingCall>> {
289 let path = self.resolve_path(path);
290 let items = self.prepare_call_hierarchy(&path, line, character).await?;
291
292 if items.is_empty() {
293 return Ok(vec![]);
294 }
295
296 let item = items.into_iter().next().unwrap();
298 let results = self
299 .run_on_file(&path, |client| {
300 let item = item.clone();
301 async move { client.outgoing_calls(item).await }
302 })
303 .await?;
304 Ok(results.into_iter().flatten().collect())
305 }
306
307 pub async fn diagnostics(&self) -> HashMap<PathBuf, Vec<Diagnostic>> {
309 let clients = self.clients.read().await;
310 let mut result = HashMap::new();
311
312 for client in clients.values() {
313 for (path, diags) in client.diagnostics() {
314 result
315 .entry(path)
316 .or_insert_with(Vec::new)
317 .extend(diags);
318 }
319 }
320
321 result
322 }
323
324 pub async fn diagnostics_for_file(&self, path: &Path) -> Vec<Diagnostic> {
326 let path = self.resolve_path(path);
327 let clients = self.clients.read().await;
328 let mut result = Vec::new();
329
330 for client in clients.values() {
331 result.extend(client.diagnostics_for_file(&path));
332 }
333
334 result
335 }
336
337 pub async fn status(&self) -> Vec<LspStatus> {
339 let clients = self.clients.read().await;
340 clients
341 .values()
342 .map(|client| LspStatus {
343 id: client.server_id().to_string(),
344 name: client.server_id().to_string(),
345 root: client
346 .root()
347 .strip_prefix(&self.working_dir)
348 .unwrap_or(client.root())
349 .to_string_lossy()
350 .to_string(),
351 status: LspConnectionStatus::Connected,
352 })
353 .collect()
354 }
355
356 pub async fn shutdown(self) {
358 info!("Shutting down all LSP clients");
359 let clients = self.clients.into_inner();
360 for (_, client) in clients {
361 if let Ok(client) = Arc::try_unwrap(client) {
362 client.shutdown().await;
363 }
364 }
365 }
366
367 fn resolve_path(&self, path: &Path) -> PathBuf {
373 if path.is_absolute() {
374 path.to_path_buf()
375 } else {
376 self.working_dir.join(path)
377 }
378 }
379
380 async fn get_or_spawn_client(
382 &self,
383 path: &Path,
384 server: &LspServerInfo,
385 ) -> Result<Option<Arc<LspClient>>> {
386 let root = match server.detect_root(path) {
387 Some(root) => root,
388 None => return Ok(None),
389 };
390
391 let key = (root.clone(), server.id.clone());
392
393 {
395 let broken = self.broken.read().await;
396 if broken.contains(&key) {
397 return Ok(None);
398 }
399 }
400
401 {
403 let clients = self.clients.read().await;
404 if let Some(client) = clients.get(&key) {
405 return Ok(Some(client.clone()));
406 }
407 }
408
409 {
411 let spawning = self.spawning.lock().await;
412 if let Some(tx) = spawning.get(&key) {
413 let mut rx = tx.subscribe();
414 drop(spawning);
415 let _ = rx.recv().await;
416
417 let clients = self.clients.read().await;
419 return Ok(clients.get(&key).cloned());
420 }
421 }
422
423 info!(server_id = %server.id, root = ?root, "Spawning LSP server");
425
426 let (tx, _) = tokio::sync::broadcast::channel(1);
427 {
428 let mut spawning = self.spawning.lock().await;
429 spawning.insert(key.clone(), tx.clone());
430 }
431
432 let result = self.spawn_client(server, &root).await;
433
434 {
436 let mut spawning = self.spawning.lock().await;
437 spawning.remove(&key);
438 }
439 let _ = tx.send(());
440
441 match result {
442 Ok(client) => {
443 let client = Arc::new(client);
444 let mut clients = self.clients.write().await;
445 clients.insert(key, client.clone());
446 Ok(Some(client))
447 }
448 Err(e) => {
449 error!(
450 server_id = %server.id,
451 root = ?root,
452 error = ?e,
453 "Failed to spawn LSP server"
454 );
455 let mut broken = self.broken.write().await;
456 broken.insert(key);
457 Ok(None)
458 }
459 }
460 }
461
462 async fn spawn_client(&self, server: &LspServerInfo, root: &Path) -> Result<LspClient> {
464 let handle = server.spawn(root)?;
465 LspClient::new(&server.id, handle, root.to_path_buf()).await
466 }
467
468 async fn run_on_file<F, Fut, T>(&self, path: &Path, f: F) -> Result<Vec<T>>
470 where
471 F: Fn(Arc<LspClient>) -> Fut,
472 Fut: std::future::Future<Output = Result<T>>,
473 {
474 let clients = self.clients_for_file(path).await?;
475
476 for client in &clients {
478 if let Err(e) = client.open_file(path).await {
479 warn!(
480 server_id = %client.server_id(),
481 path = ?path,
482 error = ?e,
483 "Failed to open file"
484 );
485 }
486 }
487
488 let mut results = Vec::new();
489 for client in clients {
490 match f(client.clone()).await {
491 Ok(result) => results.push(result),
492 Err(e) => {
493 warn!(
494 server_id = %client.server_id(),
495 error = ?e,
496 "LSP operation failed"
497 );
498 }
499 }
500 }
501
502 Ok(results)
503 }
504}
505
506use std::sync::OnceLock;
511
512static LSP_INSTANCE: OnceLock<Lsp> = OnceLock::new();
513
514pub fn init(working_dir: PathBuf) -> &'static Lsp {
516 LSP_INSTANCE.get_or_init(|| Lsp::new(working_dir))
517}
518
519pub fn lsp() -> Option<&'static Lsp> {
521 LSP_INSTANCE.get()
522}
523
524#[cfg(test)]
525mod tests {
526 use super::*;
527 use tempfile::tempdir;
528
529 #[tokio::test]
530 async fn test_lsp_new() {
531 let temp = tempdir().unwrap();
532 let lsp = Lsp::new(temp.path().to_path_buf());
533 assert_eq!(lsp.working_dir(), temp.path());
534 }
535
536 #[tokio::test]
537 async fn test_has_clients_no_root() {
538 let temp = tempdir().unwrap();
539 let lsp = Lsp::new(temp.path().to_path_buf());
540
541 let file = temp.path().join("orphan.rs");
543 std::fs::write(&file, "fn main() {}").unwrap();
544
545 assert!(!lsp.has_clients(&file).await);
546 }
547
548 #[tokio::test]
549 async fn test_status_empty() {
550 let temp = tempdir().unwrap();
551 let lsp = Lsp::new(temp.path().to_path_buf());
552
553 let status = lsp.status().await;
554 assert!(status.is_empty());
555 }
556}