1use dashmap::DashMap;
9use serde::{Deserialize, Serialize};
10use serde_json::{json, Value};
11use std::path::Path;
12use std::process::Stdio;
13use std::sync::atomic::{AtomicU64, Ordering};
14use std::sync::Arc;
15use thiserror::Error;
16use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
17use tokio::process::{Child, ChildStdin, ChildStdout, Command};
18use tokio::sync::{oneshot, Mutex};
19use tracing::warn;
20
21#[derive(Debug, Error)]
23pub enum LspError {
24 #[error("failed to spawn LSP server: {0}")]
25 Spawn(String),
26 #[error("LSP protocol error: {0}")]
27 Protocol(String),
28 #[error("LSP server for language {0} not started")]
29 NotStarted(String),
30 #[error("unknown language: {0}")]
31 UnknownLanguage(String),
32}
33
34#[derive(Debug, Clone, Default, PartialEq, Eq)]
36pub struct LspCapabilities {
37 pub has_diagnostics: bool,
38 pub has_references: bool,
39 pub has_definition: bool,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44#[repr(i32)]
45pub enum DiagnosticSeverity {
46 Error = 1,
47 Warning = 2,
48 Information = 3,
49 Hint = 4,
50}
51
52impl Serialize for DiagnosticSeverity {
53 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
54 where
55 S: serde::Serializer,
56 {
57 serializer.serialize_i32(*self as i32)
58 }
59}
60
61impl<'de> Deserialize<'de> for DiagnosticSeverity {
62 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
63 where
64 D: serde::Deserializer<'de>,
65 {
66 let value = i32::deserialize(deserializer)?;
67 Ok(match value {
68 1 => Self::Error,
69 2 => Self::Warning,
70 3 => Self::Information,
71 _ => Self::Hint,
72 })
73 }
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
78pub struct Diagnostic {
79 pub line: u32,
80 pub character: u32,
81 pub message: String,
82 pub severity: DiagnosticSeverity,
83}
84
85#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
87pub struct Location {
88 pub uri: String,
89 pub line: u32,
90 pub character: u32,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
95pub struct TextRange {
96 pub start_line: u32,
97 pub start_char: u32,
98 pub end_line: u32,
99 pub end_char: u32,
100}
101
102#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
104pub struct TextDocumentChange {
105 pub range: Option<TextRange>,
106 pub text: String,
107}
108
109pub struct LspServer {
111 child: Child,
112 stdin: Arc<Mutex<ChildStdin>>,
113 next_id: Arc<AtomicU64>,
114 pending: Arc<DashMap<u64, oneshot::Sender<Value>>>,
115 diagnostics: Arc<DashMap<String, Vec<Diagnostic>>>,
116 capabilities: LspCapabilities,
117}
118
119impl LspServer {
120 pub async fn spawn(
122 command: &str,
123 args: &[&str],
124 workspace_root: &Path,
125 ) -> Result<Self, LspError> {
126 let mut child = Command::new(command)
127 .args(args)
128 .stdin(Stdio::piped())
129 .stdout(Stdio::piped())
130 .stderr(Stdio::piped())
131 .spawn()
132 .map_err(|e| LspError::Spawn(format!("{command}: {e}")))?;
133
134 let stdin = child
135 .stdin
136 .take()
137 .ok_or_else(|| LspError::Spawn("missing stdin".into()))?;
138 let stdout = child
139 .stdout
140 .take()
141 .ok_or_else(|| LspError::Spawn("missing stdout".into()))?;
142
143 let next_id = Arc::new(AtomicU64::new(0));
144 let pending: Arc<DashMap<u64, oneshot::Sender<Value>>> = Arc::new(DashMap::new());
145 let diagnostics: Arc<DashMap<String, Vec<Diagnostic>>> = Arc::new(DashMap::new());
146
147 let reader_pending = pending.clone();
148 let reader_diagnostics = diagnostics.clone();
149 tokio::spawn(async move {
150 if let Err(e) = read_loop(stdout, reader_pending, reader_diagnostics).await {
151 warn!("LSP reader exited: {e}");
152 }
153 });
154
155 let stdin = Arc::new(Mutex::new(stdin));
156 let mut server = Self {
157 child,
158 stdin,
159 next_id,
160 pending,
161 diagnostics,
162 capabilities: LspCapabilities::default(),
163 };
164 server.initialize(workspace_root).await?;
165 Ok(server)
166 }
167
168 pub async fn initialize(&mut self, workspace_root: &Path) -> Result<LspCapabilities, LspError> {
170 let root_uri = path_to_uri(workspace_root);
171 let params = json!({
172 "processId": std::process::id(),
173 "rootUri": root_uri,
174 "capabilities": {
175 "textDocument": {
176 "publishDiagnostics": {"relatedInformation": false}
177 }
178 }
179 });
180 let result = self.send_request("initialize", params).await?;
181 let caps = result.get("capabilities").cloned().unwrap_or(Value::Null);
182 let has_diagnostics = caps.get("textDocumentSync").is_some()
183 || provider_enabled(caps.get("diagnosticProvider").unwrap_or(&Value::Null));
184 let has_references = caps
185 .get("referencesProvider")
186 .map(provider_enabled)
187 .unwrap_or(false);
188 let has_definition = caps
189 .get("definitionProvider")
190 .map(provider_enabled)
191 .unwrap_or(false);
192 self.capabilities = LspCapabilities {
193 has_diagnostics,
194 has_references,
195 has_definition,
196 };
197 self.send_notification("initialized", json!({})).await?;
198 Ok(self.capabilities.clone())
199 }
200
201 pub async fn text_document_did_open(
203 &self,
204 uri: &str,
205 language_id: &str,
206 text: &str,
207 ) -> Result<(), LspError> {
208 let params = json!({
209 "textDocument": {
210 "uri": uri,
211 "languageId": language_id,
212 "version": 1,
213 "text": text,
214 }
215 });
216 self.send_notification("textDocument/didOpen", params).await
217 }
218
219 pub async fn text_document_did_change(
221 &self,
222 uri: &str,
223 changes: &[TextDocumentChange],
224 ) -> Result<(), LspError> {
225 let changes_json: Vec<Value> = changes
226 .iter()
227 .map(|c| {
228 let range = c.range.as_ref().map(|r| {
229 json!({
230 "start": {"line": r.start_line, "character": r.start_char},
231 "end": {"line": r.end_line, "character": r.end_char},
232 })
233 });
234 json!({"range": range, "text": c.text})
235 })
236 .collect();
237 let params = json!({
238 "textDocument": {"uri": uri, "version": 2},
239 "contentChanges": changes_json,
240 });
241 self.send_notification("textDocument/didChange", params)
242 .await
243 }
244
245 pub async fn text_document_did_close(&self, uri: &str) -> Result<(), LspError> {
247 let params = json!({"textDocument": {"uri": uri}});
248 self.send_notification("textDocument/didClose", params)
249 .await
250 }
251
252 pub async fn diagnostics(&self, uri: &str) -> Result<Vec<Diagnostic>, LspError> {
254 Ok(self
255 .diagnostics
256 .get(uri)
257 .map(|entry| entry.clone())
258 .unwrap_or_default())
259 }
260
261 pub async fn references(
263 &self,
264 uri: &str,
265 line: u32,
266 character: u32,
267 ) -> Result<Vec<Location>, LspError> {
268 let params = json!({
269 "textDocument": {"uri": uri},
270 "position": {"line": line, "character": character},
271 "context": {"includeDeclaration": true},
272 });
273 let result = self.send_request("textDocument/references", params).await?;
274 Ok(parse_locations(&result))
275 }
276
277 pub async fn definition(
279 &self,
280 uri: &str,
281 line: u32,
282 character: u32,
283 ) -> Result<Vec<Location>, LspError> {
284 let params = json!({
285 "textDocument": {"uri": uri},
286 "position": {"line": line, "character": character},
287 });
288 let result = self.send_request("textDocument/definition", params).await?;
289 Ok(parse_locations(&result))
290 }
291
292 pub async fn shutdown(&mut self) -> Result<(), LspError> {
294 self.send_request("shutdown", Value::Null).await?;
295 Ok(())
296 }
297
298 pub async fn exit(&mut self) {
300 let _ = self.send_notification("exit", Value::Null).await;
301 let _ = self.child.kill().await;
302 }
303
304 async fn send_request(&self, method: &str, params: Value) -> Result<Value, LspError> {
305 let id = self.next_id.fetch_add(1, Ordering::Relaxed) + 1;
306 let (tx, rx) = oneshot::channel();
307 self.pending.insert(id, tx);
308 let body = json!({
309 "jsonrpc": "2.0",
310 "id": id,
311 "method": method,
312 "params": params,
313 });
314 self.write_message(&body).await?;
315 let response = rx
316 .await
317 .map_err(|_| LspError::Protocol(format!("no response for {method} (id {id})")))?;
318 if let Some(err) = response.get("error") {
319 let message = err
320 .get("message")
321 .and_then(|m| m.as_str())
322 .unwrap_or("unknown error");
323 return Err(LspError::Protocol(format!("{method}: {message}")));
324 }
325 Ok(response.get("result").cloned().unwrap_or(Value::Null))
326 }
327
328 async fn send_notification(&self, method: &str, params: Value) -> Result<(), LspError> {
329 let body = json!({
330 "jsonrpc": "2.0",
331 "method": method,
332 "params": params,
333 });
334 self.write_message(&body).await
335 }
336
337 async fn write_message(&self, body: &Value) -> Result<(), LspError> {
338 let serialized =
339 serde_json::to_string(body).map_err(|e| LspError::Protocol(format!("encode: {e}")))?;
340 let frame = format!("Content-Length: {}\r\n\r\n{}", serialized.len(), serialized);
341 let mut stdin = self.stdin.lock().await;
342 stdin
343 .write_all(frame.as_bytes())
344 .await
345 .map_err(|e| LspError::Protocol(format!("write: {e}")))?;
346 stdin
347 .flush()
348 .await
349 .map_err(|e| LspError::Protocol(format!("flush: {e}")))?;
350 Ok(())
351 }
352}
353
354pub struct LspManager {
356 registered: Vec<(String, String, Vec<String>)>,
357 servers: DashMap<String, Arc<Mutex<LspServer>>>,
358}
359
360impl LspManager {
361 pub fn new() -> Self {
362 Self {
363 registered: Vec::new(),
364 servers: DashMap::new(),
365 }
366 }
367
368 pub fn register(
370 &mut self,
371 language: &str,
372 command: &str,
373 args: &[&str],
374 ) -> Result<(), LspError> {
375 if self.is_registered(language) {
376 return Err(LspError::Protocol(format!(
377 "language already registered: {language}"
378 )));
379 }
380 self.registered.push((
381 language.to_string(),
382 command.to_string(),
383 args.iter().map(|s| s.to_string()).collect(),
384 ));
385 Ok(())
386 }
387
388 pub fn is_registered(&self, language: &str) -> bool {
390 self.registered.iter().any(|(l, _, _)| l == language) || self.servers.contains_key(language)
391 }
392
393 pub async fn start(&mut self, workspace_root: &Path) -> Result<(), LspError> {
395 let registered = std::mem::take(&mut self.registered);
396 for (language, command, args) in registered {
397 if self.servers.contains_key(&language) {
398 continue;
399 }
400 let arg_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
401 let server = LspServer::spawn(&command, &arg_refs, workspace_root).await?;
402 self.servers.insert(language, Arc::new(Mutex::new(server)));
403 }
404 Ok(())
405 }
406
407 pub async fn diagnostics(
409 &self,
410 uri: &str,
411 language: &str,
412 ) -> Result<Vec<Diagnostic>, LspError> {
413 let server = self.lookup(language)?;
414 let arc = server.clone();
415 drop(server);
416 let guard = arc.lock().await;
417 guard.diagnostics(uri).await
418 }
419
420 pub async fn references(
422 &self,
423 uri: &str,
424 language: &str,
425 line: u32,
426 char: u32,
427 ) -> Result<Vec<Location>, LspError> {
428 let server = self.lookup(language)?;
429 let arc = server.clone();
430 drop(server);
431 let guard = arc.lock().await;
432 guard.references(uri, line, char).await
433 }
434
435 pub async fn definition(
437 &self,
438 uri: &str,
439 language: &str,
440 line: u32,
441 char: u32,
442 ) -> Result<Vec<Location>, LspError> {
443 let server = self.lookup(language)?;
444 let arc = server.clone();
445 drop(server);
446 let guard = arc.lock().await;
447 guard.definition(uri, line, char).await
448 }
449
450 pub async fn shutdown_all(&mut self) -> Result<(), LspError> {
452 let languages: Vec<String> = self.servers.iter().map(|r| r.key().clone()).collect();
453 for language in languages {
454 if let Some((_, arc)) = self.servers.remove(&language) {
455 let mut guard = arc.lock().await;
456 if let Err(e) = guard.shutdown().await {
457 warn!("LSP shutdown error for {language}: {e}");
458 }
459 guard.exit().await;
460 }
461 }
462 Ok(())
463 }
464
465 fn lookup(&self, language: &str) -> Result<Arc<Mutex<LspServer>>, LspError> {
466 if let Some(entry) = self.servers.get(language) {
467 Ok(entry.clone())
468 } else if self.is_registered(language) {
469 Err(LspError::NotStarted(language.to_string()))
470 } else {
471 Err(LspError::UnknownLanguage(language.to_string()))
472 }
473 }
474}
475
476impl Default for LspManager {
477 fn default() -> Self {
478 Self::new()
479 }
480}
481
482async fn read_loop(
483 stdout: ChildStdout,
484 pending: Arc<DashMap<u64, oneshot::Sender<Value>>>,
485 diagnostics: Arc<DashMap<String, Vec<Diagnostic>>>,
486) -> Result<(), LspError> {
487 let mut reader = BufReader::new(stdout);
488 loop {
489 let mut content_length: Option<usize> = None;
490 loop {
491 let mut line = String::new();
492 let n = reader
493 .read_line(&mut line)
494 .await
495 .map_err(|e| LspError::Protocol(format!("read header: {e}")))?;
496 if n == 0 {
497 return Ok(());
498 }
499 let trimmed = line.trim_end_matches(['\r', '\n']);
500 if trimmed.is_empty() {
501 break;
502 }
503 if let Some(rest) = trimmed.strip_prefix("Content-Length:") {
504 content_length = Some(
505 rest.trim()
506 .parse::<usize>()
507 .map_err(|e| LspError::Protocol(format!("content-length: {e}")))?,
508 );
509 }
510 }
511 let len =
512 content_length.ok_or_else(|| LspError::Protocol("missing Content-Length".into()))?;
513 let mut buf = vec![0u8; len];
514 reader
515 .read_exact(&mut buf)
516 .await
517 .map_err(|e| LspError::Protocol(format!("read body: {e}")))?;
518 let message: Value = serde_json::from_slice(&buf)
519 .map_err(|e| LspError::Protocol(format!("parse body: {e}")))?;
520 dispatch_message(message, &pending, &diagnostics);
521 }
522}
523
524fn dispatch_message(
525 message: Value,
526 pending: &DashMap<u64, oneshot::Sender<Value>>,
527 diagnostics: &DashMap<String, Vec<Diagnostic>>,
528) {
529 if message.get("id").is_some() {
530 if let Some(id) = message.get("id").and_then(|v| v.as_u64()) {
531 if let Some((_, sender)) = pending.remove(&id) {
532 let _ = sender.send(message);
533 }
534 }
535 return;
536 }
537 if message.get("method").and_then(|m| m.as_str()) == Some("textDocument/publishDiagnostics") {
538 if let Some(params) = message.get("params") {
539 let uri = params
540 .get("uri")
541 .and_then(|u| u.as_str())
542 .unwrap_or("")
543 .to_string();
544 let parsed = parse_diagnostics(params);
545 diagnostics.insert(uri, parsed);
546 }
547 }
548}
549
550fn parse_diagnostics(params: &Value) -> Vec<Diagnostic> {
551 params
552 .get("diagnostics")
553 .and_then(|d| d.as_array())
554 .map(|arr| arr.iter().filter_map(parse_diagnostic).collect())
555 .unwrap_or_default()
556}
557
558fn parse_diagnostic(value: &Value) -> Option<Diagnostic> {
559 let range = value.get("range")?;
560 let start = range.get("start")?;
561 let line = start.get("line").and_then(|l| l.as_u64()).unwrap_or(0) as u32;
562 let character = start.get("character").and_then(|c| c.as_u64()).unwrap_or(0) as u32;
563 let message = value
564 .get("message")
565 .and_then(|m| m.as_str())
566 .unwrap_or("")
567 .to_string();
568 let severity = value
569 .get("severity")
570 .and_then(|s| s.as_i64())
571 .map(|i| match i {
572 1 => DiagnosticSeverity::Error,
573 2 => DiagnosticSeverity::Warning,
574 3 => DiagnosticSeverity::Information,
575 _ => DiagnosticSeverity::Hint,
576 })
577 .unwrap_or(DiagnosticSeverity::Error);
578 Some(Diagnostic {
579 line,
580 character,
581 message,
582 severity,
583 })
584}
585
586fn parse_locations(result: &Value) -> Vec<Location> {
587 let items: Vec<Value> = match result {
588 Value::Null => Vec::new(),
589 Value::Array(arr) => arr.clone(),
590 other => vec![other.clone()],
591 };
592 items.iter().filter_map(parse_location).collect()
593}
594
595fn parse_location(value: &Value) -> Option<Location> {
596 let uri = value.get("uri")?.as_str()?.to_string();
597 let range = value.get("range")?;
598 let start = range.get("start")?;
599 let line = start.get("line").and_then(|l| l.as_u64()).unwrap_or(0) as u32;
600 let character = start.get("character").and_then(|c| c.as_u64()).unwrap_or(0) as u32;
601 Some(Location {
602 uri,
603 line,
604 character,
605 })
606}
607
608fn provider_enabled(value: &Value) -> bool {
609 match value {
610 Value::Bool(b) => *b,
611 Value::Object(_) => true,
612 _ => false,
613 }
614}
615
616fn path_to_uri(path: &Path) -> String {
617 let absolute = if path.is_absolute() {
618 path.to_path_buf()
619 } else {
620 std::env::current_dir().unwrap_or_default().join(path)
621 };
622 format!("file://{}", absolute.display())
623}
624
625#[cfg(test)]
626mod tests {
627 use super::*;
628
629 #[test]
630 fn manager_registration() {
631 let mut manager = LspManager::new();
632 assert!(!manager.is_registered("rust"));
633 manager.register("rust", "rust-analyzer", &[]).unwrap();
634 assert!(manager.is_registered("rust"));
635 }
636
637 #[test]
638 fn manager_duplicate_registration_errors() {
639 let mut manager = LspManager::new();
640 manager.register("rust", "rust-analyzer", &[]).unwrap();
641 let result = manager.register("rust", "rust-analyzer", &[]);
642 assert!(matches!(result, Err(LspError::Protocol(_))));
643 }
644
645 #[tokio::test]
646 async fn manager_unknown_language() {
647 let manager = LspManager::new();
648 let result = manager.diagnostics("file:///x.rs", "python").await;
649 assert!(matches!(result, Err(LspError::UnknownLanguage(_))));
650 }
651
652 #[tokio::test]
653 async fn manager_registered_but_not_started() {
654 let mut manager = LspManager::new();
655 manager.register("rust", "rust-analyzer", &[]).unwrap();
656 let result = manager.diagnostics("file:///x.rs", "rust").await;
657 assert!(matches!(result, Err(LspError::NotStarted(_))));
658 }
659
660 #[test]
661 fn diagnostic_severity_serialization() {
662 assert_eq!(
663 serde_json::to_string(&DiagnosticSeverity::Error).unwrap(),
664 "1"
665 );
666 let warning: DiagnosticSeverity = serde_json::from_str("2").unwrap();
667 assert_eq!(warning, DiagnosticSeverity::Warning);
668 let hint: DiagnosticSeverity = serde_json::from_str("9").unwrap();
669 assert_eq!(hint, DiagnosticSeverity::Hint);
670 }
671
672 #[test]
673 fn location_serialization() {
674 let location = Location {
675 uri: "file:///a.rs".into(),
676 line: 3,
677 character: 5,
678 };
679 let serialized = serde_json::to_string(&location).unwrap();
680 let back: Location = serde_json::from_str(&serialized).unwrap();
681 assert_eq!(back, location);
682 }
683
684 #[test]
685 fn text_document_change_construction() {
686 let range = TextRange {
687 start_line: 0,
688 start_char: 0,
689 end_line: 0,
690 end_char: 5,
691 };
692 let change = TextDocumentChange {
693 range: Some(range),
694 text: "hello".into(),
695 };
696 let serialized = serde_json::to_string(&change).unwrap();
697 assert!(serialized.contains("hello"));
698 let back: TextDocumentChange = serde_json::from_str(&serialized).unwrap();
699 assert_eq!(back.text, "hello");
700 assert!(back.range.is_some());
701 }
702
703 #[test]
704 fn parse_locations_handles_variants() {
705 assert!(parse_locations(&Value::Null).is_empty());
706 let single = json!({
707 "uri": "file:///a.rs",
708 "range": {"start": {"line": 1, "character": 2}, "end": {"line": 1, "character": 4}}
709 });
710 let locs = parse_locations(&single);
711 assert_eq!(locs.len(), 1);
712 assert_eq!(locs[0].line, 1);
713 assert_eq!(locs[0].character, 2);
714 let arr = json!([single, single]);
715 assert_eq!(parse_locations(&arr).len(), 2);
716 }
717
718 #[test]
719 fn parse_diagnostics_from_params() {
720 let params = json!({
721 "uri": "file:///a.rs",
722 "diagnostics": [
723 {"range": {"start": {"line": 4, "character": 7}, "end": {"line": 4, "character": 8}},
724 "message": "unused", "severity": 2}
725 ]
726 });
727 let diags = parse_diagnostics(¶ms);
728 assert_eq!(diags.len(), 1);
729 assert_eq!(diags[0].line, 4);
730 assert_eq!(diags[0].character, 7);
731 assert_eq!(diags[0].severity, DiagnosticSeverity::Warning);
732 }
733
734 #[test]
735 fn provider_enabled_truthiness() {
736 assert!(provider_enabled(&json!(true)));
737 assert!(!provider_enabled(&json!(false)));
738 assert!(provider_enabled(&json!({"options": {}})));
739 assert!(!provider_enabled(&Value::Null));
740 }
741}