1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3use std::str::FromStr;
4
5use crossbeam_channel::{unbounded, Receiver, Sender};
6use lsp_types::notification::{DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument};
7use lsp_types::{
8 DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
9 TextDocumentContentChangeEvent, TextDocumentIdentifier, TextDocumentItem,
10 VersionedTextDocumentIdentifier,
11};
12
13use crate::lsp::client::{LspClient, LspEvent, ServerState};
14use crate::lsp::diagnostics::{from_lsp_diagnostics, DiagnosticsStore, StoredDiagnostic};
15use crate::lsp::document::DocumentStore;
16use crate::lsp::registry::{servers_for_file, ServerDef, ServerKind};
17use crate::lsp::roots::{find_workspace_root, ServerKey};
18use crate::lsp::LspError;
19
20pub struct LspManager {
21 clients: HashMap<ServerKey, LspClient>,
23 documents: HashMap<ServerKey, DocumentStore>,
25 diagnostics: DiagnosticsStore,
27 event_tx: Sender<LspEvent>,
29 event_rx: Receiver<LspEvent>,
30 binary_overrides: HashMap<ServerKind, PathBuf>,
32}
33
34impl LspManager {
35 pub fn new() -> Self {
36 let (event_tx, event_rx) = unbounded();
37 Self {
38 clients: HashMap::new(),
39 documents: HashMap::new(),
40 diagnostics: DiagnosticsStore::new(),
41 event_tx,
42 event_rx,
43 binary_overrides: HashMap::new(),
44 }
45 }
46
47 pub fn override_binary(&mut self, kind: ServerKind, binary_path: PathBuf) {
49 self.binary_overrides.insert(kind, binary_path);
50 }
51
52 pub fn ensure_server_for_file(&mut self, file_path: &Path) -> Vec<ServerKey> {
55 let defs = servers_for_file(file_path);
56 let mut keys = Vec::new();
57
58 for def in defs {
59 let Some(root) = find_workspace_root(file_path, def.root_markers) else {
60 continue;
61 };
62
63 let key = ServerKey {
64 kind: def.kind,
65 root,
66 };
67
68 if !self.clients.contains_key(&key) {
69 match self.spawn_server(def, &key.root) {
70 Ok(client) => {
71 self.clients.insert(key.clone(), client);
72 self.documents.entry(key.clone()).or_default();
73 }
74 Err(err) => {
75 log::error!("failed to spawn {}: {}", def.name, err);
76 continue;
77 }
78 }
79 }
80
81 keys.push(key);
82 }
83
84 keys
85 }
86 pub fn ensure_file_open(&mut self, file_path: &Path) -> Result<Vec<ServerKey>, LspError> {
90 let canonical_path = canonicalize_for_lsp(file_path)?;
91 let server_keys = self.ensure_server_for_file(&canonical_path);
92 if server_keys.is_empty() {
93 return Ok(server_keys);
94 }
95
96 let uri = uri_for_path(&canonical_path)?;
97 let language_id = language_id_for_extension(
98 canonical_path
99 .extension()
100 .and_then(|ext| ext.to_str())
101 .unwrap_or_default(),
102 )
103 .to_string();
104
105 for key in &server_keys {
106 let already_open = self
107 .documents
108 .get(key)
109 .map_or(false, |store| store.is_open(&canonical_path));
110
111 if !already_open {
112 let content = std::fs::read_to_string(&canonical_path).map_err(LspError::Io)?;
113 if let Some(client) = self.clients.get_mut(key) {
114 client.send_notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
115 text_document: TextDocumentItem::new(
116 uri.clone(),
117 language_id.clone(),
118 0,
119 content,
120 ),
121 })?;
122 }
123 self.documents
124 .entry(key.clone())
125 .or_default()
126 .open(canonical_path.clone());
127 }
128 }
129
130 Ok(server_keys)
131 }
132
133 pub fn notify_file_changed(&mut self, file_path: &Path, content: &str) -> Result<(), LspError> {
139 let canonical_path = canonicalize_for_lsp(file_path)?;
140 let server_keys = self.ensure_server_for_file(&canonical_path);
141 if server_keys.is_empty() {
142 return Ok(());
143 }
144
145 let uri = uri_for_path(&canonical_path)?;
146 let language_id = language_id_for_extension(
147 canonical_path
148 .extension()
149 .and_then(|ext| ext.to_str())
150 .unwrap_or_default(),
151 )
152 .to_string();
153
154 for key in server_keys {
155 let current_version = self
156 .documents
157 .get(&key)
158 .and_then(|store| store.version(&canonical_path));
159
160 if let Some(version) = current_version {
161 let next_version = version + 1;
162 if let Some(client) = self.clients.get_mut(&key) {
163 client.send_notification::<DidChangeTextDocument>(
164 DidChangeTextDocumentParams {
165 text_document: VersionedTextDocumentIdentifier::new(
166 uri.clone(),
167 next_version,
168 ),
169 content_changes: vec![TextDocumentContentChangeEvent {
170 range: None,
171 range_length: None,
172 text: content.to_string(),
173 }],
174 },
175 )?;
176 }
177 if let Some(store) = self.documents.get_mut(&key) {
178 store.bump_version(&canonical_path);
179 }
180 continue;
181 }
182
183 if let Some(client) = self.clients.get_mut(&key) {
184 client.send_notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
185 text_document: TextDocumentItem::new(
186 uri.clone(),
187 language_id.clone(),
188 0,
189 content.to_string(),
190 ),
191 })?;
192 }
193 self.documents
194 .entry(key)
195 .or_default()
196 .open(canonical_path.clone());
197 }
198
199 Ok(())
200 }
201
202 pub fn notify_file_closed(&mut self, file_path: &Path) -> Result<(), LspError> {
204 let canonical_path = canonicalize_for_lsp(file_path)?;
205 let uri = uri_for_path(&canonical_path)?;
206 let keys: Vec<ServerKey> = self.documents.keys().cloned().collect();
207
208 for key in keys {
209 let was_open = self
210 .documents
211 .get(&key)
212 .map(|store| store.is_open(&canonical_path))
213 .unwrap_or(false);
214 if !was_open {
215 continue;
216 }
217
218 if let Some(client) = self.clients.get_mut(&key) {
219 client.send_notification::<DidCloseTextDocument>(DidCloseTextDocumentParams {
220 text_document: TextDocumentIdentifier::new(uri.clone()),
221 })?;
222 }
223
224 if let Some(store) = self.documents.get_mut(&key) {
225 store.close(&canonical_path);
226 }
227 }
228
229 Ok(())
230 }
231
232 pub fn client_for_file(&self, file_path: &Path) -> Option<&LspClient> {
234 let key = self.server_key_for_file(file_path)?;
235 self.clients.get(&key)
236 }
237
238 pub fn client_for_file_mut(&mut self, file_path: &Path) -> Option<&mut LspClient> {
240 let key = self.server_key_for_file(file_path)?;
241 self.clients.get_mut(&key)
242 }
243
244 pub fn active_client_count(&self) -> usize {
246 self.clients.len()
247 }
248
249 pub fn drain_events(&mut self) -> Vec<LspEvent> {
251 let mut events = Vec::new();
252 while let Ok(event) = self.event_rx.try_recv() {
253 match &event {
254 LspEvent::Notification {
255 server_kind,
256 method,
257 params,
258 ..
259 } if method == "textDocument/publishDiagnostics" => {
260 if let Some(params) = params {
261 self.handle_publish_diagnostics(*server_kind, params);
262 }
263 }
264 LspEvent::ServerExited { server_kind, root } => {
265 let key = ServerKey {
266 kind: *server_kind,
267 root: root.clone(),
268 };
269 self.clients.remove(&key);
270 self.documents.remove(&key);
271 self.diagnostics.clear_server(*server_kind);
272 }
273 _ => {}
274 }
275 events.push(event);
276 }
277 events
278 }
279
280 pub fn wait_for_diagnostics(
286 &mut self,
287 file_path: &Path,
288 timeout: std::time::Duration,
289 ) -> Vec<StoredDiagnostic> {
290 let lookup_path = normalize_lookup_path(file_path);
291
292 if !timeout.is_zero() {
293 std::thread::sleep(timeout);
294 }
295 self.drain_events();
296
297 self.diagnostics
298 .for_file(&lookup_path)
299 .into_iter()
300 .cloned()
301 .collect()
302 }
303
304 pub fn shutdown_all(&mut self) {
306 for (key, mut client) in self.clients.drain() {
307 if let Err(err) = client.shutdown() {
308 log::error!("error shutting down {:?}: {}", key, err);
309 }
310 }
311 self.documents.clear();
312 self.diagnostics = DiagnosticsStore::new();
313 }
314
315 pub fn has_active_servers(&self) -> bool {
317 self.clients
318 .values()
319 .any(|client| client.state() == ServerState::Ready)
320 }
321
322 pub fn get_diagnostics_for_file(&self, file: &Path) -> Vec<&StoredDiagnostic> {
323 let normalized = normalize_lookup_path(file);
324 self.diagnostics.for_file(&normalized)
325 }
326
327 pub fn get_diagnostics_for_directory(&self, dir: &Path) -> Vec<&StoredDiagnostic> {
328 let normalized = normalize_lookup_path(dir);
329 self.diagnostics.for_directory(&normalized)
330 }
331
332 pub fn get_all_diagnostics(&self) -> Vec<&StoredDiagnostic> {
333 self.diagnostics.all()
334 }
335
336 fn handle_publish_diagnostics(&mut self, server: ServerKind, params: &serde_json::Value) {
337 if let Ok(publish_params) =
338 serde_json::from_value::<lsp_types::PublishDiagnosticsParams>(params.clone())
339 {
340 let Some(file) = uri_to_path(&publish_params.uri) else {
341 return;
342 };
343 let stored = from_lsp_diagnostics(file.clone(), publish_params.diagnostics);
344 self.diagnostics.publish(server, file, stored);
345 }
346 }
347
348 fn spawn_server(&self, def: &ServerDef, root: &Path) -> Result<LspClient, LspError> {
349 let binary = self.resolve_binary(def)?;
350 let mut client = LspClient::spawn(
351 def.kind,
352 root.to_path_buf(),
353 &binary,
354 def.args,
355 self.event_tx.clone(),
356 )?;
357 client.initialize(root)?;
358 Ok(client)
359 }
360
361 fn resolve_binary(&self, def: &ServerDef) -> Result<PathBuf, LspError> {
362 if let Some(path) = self.binary_overrides.get(&def.kind) {
363 if path.exists() {
364 return Ok(path.clone());
365 }
366 return Err(LspError::NotFound(format!(
367 "override binary for {:?} not found: {}",
368 def.kind,
369 path.display()
370 )));
371 }
372
373 if let Some(path) = env_binary_override(def.kind) {
374 if path.exists() {
375 return Ok(path);
376 }
377 return Err(LspError::NotFound(format!(
378 "environment override binary for {:?} not found: {}",
379 def.kind,
380 path.display()
381 )));
382 }
383
384 which::which(def.binary).map_err(|_| {
385 LspError::NotFound(format!(
386 "language server binary '{}' not found on PATH",
387 def.binary
388 ))
389 })
390 }
391
392 fn server_key_for_file(&self, file_path: &Path) -> Option<ServerKey> {
393 for def in servers_for_file(file_path) {
394 let root = find_workspace_root(file_path, def.root_markers)?;
395 let key = ServerKey {
396 kind: def.kind,
397 root,
398 };
399 if self.clients.contains_key(&key) {
400 return Some(key);
401 }
402 }
403 None
404 }
405}
406
407impl Default for LspManager {
408 fn default() -> Self {
409 Self::new()
410 }
411}
412
413fn canonicalize_for_lsp(file_path: &Path) -> Result<PathBuf, LspError> {
414 std::fs::canonicalize(file_path).map_err(LspError::from)
415}
416
417fn uri_for_path(path: &Path) -> Result<lsp_types::Uri, LspError> {
418 let url = url::Url::from_file_path(path).map_err(|_| {
419 LspError::NotFound(format!(
420 "failed to convert '{}' to file URI",
421 path.display()
422 ))
423 })?;
424 lsp_types::Uri::from_str(url.as_str()).map_err(|_| {
425 LspError::NotFound(format!("failed to parse file URI for '{}'", path.display()))
426 })
427}
428
429fn language_id_for_extension(ext: &str) -> &'static str {
430 match ext {
431 "ts" => "typescript",
432 "tsx" => "typescriptreact",
433 "js" | "mjs" | "cjs" => "javascript",
434 "jsx" => "javascriptreact",
435 "py" | "pyi" => "python",
436 "rs" => "rust",
437 "go" => "go",
438 _ => "plaintext",
439 }
440}
441
442fn normalize_lookup_path(path: &Path) -> PathBuf {
443 std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
444}
445
446fn uri_to_path(uri: &lsp_types::Uri) -> Option<PathBuf> {
447 let url = url::Url::parse(uri.as_str()).ok()?;
448 url.to_file_path()
449 .ok()
450 .map(|path| normalize_lookup_path(&path))
451}
452
453fn env_binary_override(kind: ServerKind) -> Option<PathBuf> {
454 let key = match kind {
455 ServerKind::TypeScript => "AFT_LSP_TYPESCRIPT_BINARY",
456 ServerKind::Python => "AFT_LSP_PYTHON_BINARY",
457 ServerKind::Rust => "AFT_LSP_RUST_BINARY",
458 ServerKind::Go => "AFT_LSP_GO_BINARY",
459 };
460 std::env::var_os(key).map(PathBuf::from)
461}