1use super::transport::LspTransport;
9use super::types::*;
10use anyhow::Result;
11use lsp_types::{
12 ClientCapabilities, CompletionContext, CompletionParams, CompletionTriggerKind,
13 DocumentSymbolParams, HoverParams, Position, TextDocumentIdentifier, TextDocumentItem,
14 TextDocumentPositionParams,
15};
16use std::collections::HashMap;
17use std::path::Path;
18use std::sync::Arc;
19use tokio::sync::RwLock;
20use tracing::{debug, info, warn};
21
22pub struct LspClient {
24 transport: LspTransport,
25 config: LspConfig,
26 server_capabilities: RwLock<Option<lsp_types::ServerCapabilities>>,
27 open_documents: RwLock<HashMap<String, i32>>,
29}
30
31impl LspClient {
32 pub async fn new(config: LspConfig) -> Result<Self> {
34 super::types::ensure_server_installed(&config).await?;
36
37 let transport = LspTransport::spawn(&config.command, &config.args).await?;
38
39 Ok(Self {
40 transport,
41 config,
42 server_capabilities: RwLock::new(None),
43 open_documents: RwLock::new(HashMap::new()),
44 })
45 }
46
47 pub async fn for_language(language: &str, root_uri: Option<String>) -> Result<Self> {
49 let mut config = get_language_server_config(language)
50 .ok_or_else(|| anyhow::anyhow!("Unknown language: {}", language))?;
51 config.root_uri = root_uri;
52 Self::new(config).await
53 }
54
55 pub async fn initialize(&self) -> Result<()> {
57 let root_uri = self.config.root_uri.clone();
58
59 let params = InitializeParams {
60 process_id: Some(std::process::id() as i64),
61 client_info: ClientInfo {
62 name: "codetether".to_string(),
63 version: env!("CARGO_PKG_VERSION").to_string(),
64 },
65 locale: None,
66 root_path: None,
67 root_uri: root_uri.clone(),
68 initialization_options: self.config.initialization_options.clone(),
69 capabilities: ClientCapabilities::default(),
70 trace: None,
71 workspace_folders: None,
72 };
73
74 let response = self
75 .transport
76 .request("initialize", Some(serde_json::to_value(params)?))
77 .await?;
78
79 if let Some(error) = response.error {
80 return Err(anyhow::anyhow!("LSP initialize error: {}", error.message));
81 }
82
83 if let Some(result) = response.result {
84 let init_result: InitializeResult = serde_json::from_value(result)?;
85 *self.server_capabilities.write().await = Some(init_result.capabilities);
86 info!(
87 server_info = ?init_result.server_info,
88 "LSP server initialized"
89 );
90 }
91
92 self.transport.notify("initialized", None).await?;
94 self.transport.set_initialized(true);
95
96 Ok(())
97 }
98
99 pub async fn shutdown(&self) -> Result<()> {
101 let response = self.transport.request("shutdown", None).await?;
102
103 if let Some(error) = response.error {
104 warn!("LSP shutdown error: {}", error.message);
105 }
106
107 self.transport.notify("exit", None).await?;
108 info!("LSP server shutdown complete");
109
110 Ok(())
111 }
112
113 pub async fn open_document(&self, path: &Path, content: &str) -> Result<()> {
115 let uri = path_to_uri(path);
116 let language_id = detect_language_from_path(path.to_string_lossy().as_ref())
117 .unwrap_or("plaintext")
118 .to_string();
119
120 let text_document = TextDocumentItem {
121 uri: parse_uri(&uri)?,
122 language_id,
123 version: 1,
124 text: content.to_string(),
125 };
126
127 let params = DidOpenTextDocumentParams { text_document };
128 self.transport
129 .notify("textDocument/didOpen", Some(serde_json::to_value(params)?))
130 .await?;
131
132 self.open_documents.write().await.insert(uri, 1);
133 debug!(path = %path.display(), "Opened document");
134
135 Ok(())
136 }
137
138 pub async fn close_document(&self, path: &Path) -> Result<()> {
140 let uri = path_to_uri(path);
141
142 let text_document = TextDocumentIdentifier {
143 uri: parse_uri(&uri)?,
144 };
145
146 let params = DidCloseTextDocumentParams { text_document };
147 self.transport
148 .notify("textDocument/didClose", Some(serde_json::to_value(params)?))
149 .await?;
150
151 self.open_documents.write().await.remove(&uri);
152 debug!(path = %path.display(), "Closed document");
153
154 Ok(())
155 }
156
157 pub async fn change_document(&self, path: &Path, content: &str) -> Result<()> {
159 let uri = path_to_uri(path);
160 let mut open_docs = self.open_documents.write().await;
161
162 let version = open_docs.entry(uri.clone()).or_insert(0);
163 *version += 1;
164
165 let text_document = VersionedTextDocumentIdentifier {
166 uri,
167 version: *version,
168 };
169
170 let content_changes = vec![super::types::TextDocumentContentChangeEvent {
171 range: None, range_length: None,
173 text: content.to_string(),
174 }];
175
176 let params = DidChangeTextDocumentParams {
177 text_document,
178 content_changes,
179 };
180
181 self.transport
182 .notify(
183 "textDocument/didChange",
184 Some(serde_json::to_value(params)?),
185 )
186 .await?;
187
188 debug!(path = %path.display(), version = *version, "Changed document");
189
190 Ok(())
191 }
192
193 pub async fn go_to_definition(
195 &self,
196 path: &Path,
197 line: u32,
198 character: u32,
199 ) -> Result<LspActionResult> {
200 let uri = path_to_uri(path);
201 self.ensure_document_open(path).await?;
202
203 let params = serde_json::json!({
204 "textDocument": { "uri": uri },
205 "position": { "line": line.saturating_sub(1), "character": character.saturating_sub(1) },
206 });
207
208 let response = self
209 .transport
210 .request("textDocument/definition", Some(params))
211 .await?;
212
213 parse_location_response(response, "definition")
214 }
215
216 pub async fn find_references(
218 &self,
219 path: &Path,
220 line: u32,
221 character: u32,
222 include_declaration: bool,
223 ) -> Result<LspActionResult> {
224 let uri = path_to_uri(path);
225 self.ensure_document_open(path).await?;
226
227 let params = ReferenceParams {
228 text_document: TextDocumentIdentifier {
229 uri: parse_uri(&uri)?,
230 },
231 position: Position {
232 line: line.saturating_sub(1),
233 character: character.saturating_sub(1),
234 },
235 context: ReferenceContext {
236 include_declaration,
237 },
238 };
239
240 let response = self
241 .transport
242 .request(
243 "textDocument/references",
244 Some(serde_json::to_value(params)?),
245 )
246 .await?;
247
248 parse_location_response(response, "references")
249 }
250
251 pub async fn hover(&self, path: &Path, line: u32, character: u32) -> Result<LspActionResult> {
253 let uri = path_to_uri(path);
254 self.ensure_document_open(path).await?;
255
256 let params = HoverParams {
257 text_document_position_params: TextDocumentPositionParams {
258 text_document: TextDocumentIdentifier {
259 uri: parse_uri(&uri)?,
260 },
261 position: Position {
262 line: line.saturating_sub(1),
263 character: character.saturating_sub(1),
264 },
265 },
266 work_done_progress_params: Default::default(),
267 };
268
269 let response = self
270 .transport
271 .request("textDocument/hover", Some(serde_json::to_value(params)?))
272 .await?;
273
274 parse_hover_response(response)
275 }
276
277 pub async fn document_symbols(&self, path: &Path) -> Result<LspActionResult> {
279 let uri = path_to_uri(path);
280 self.ensure_document_open(path).await?;
281
282 let params = DocumentSymbolParams {
283 text_document: TextDocumentIdentifier {
284 uri: parse_uri(&uri)?,
285 },
286 work_done_progress_params: Default::default(),
287 partial_result_params: Default::default(),
288 };
289
290 let response = self
291 .transport
292 .request(
293 "textDocument/documentSymbol",
294 Some(serde_json::to_value(params)?),
295 )
296 .await?;
297
298 parse_document_symbols_response(response)
299 }
300
301 pub async fn workspace_symbols(&self, query: &str) -> Result<LspActionResult> {
303 let params = WorkspaceSymbolParams {
304 query: query.to_string(),
305 };
306
307 let response = self
308 .transport
309 .request("workspace/symbol", Some(serde_json::to_value(params)?))
310 .await?;
311
312 parse_workspace_symbols_response(response)
313 }
314
315 pub async fn go_to_implementation(
317 &self,
318 path: &Path,
319 line: u32,
320 character: u32,
321 ) -> Result<LspActionResult> {
322 let uri = path_to_uri(path);
323 self.ensure_document_open(path).await?;
324
325 let params = serde_json::json!({
326 "textDocument": { "uri": uri },
327 "position": { "line": line.saturating_sub(1), "character": character.saturating_sub(1) },
328 });
329
330 let response = self
331 .transport
332 .request("textDocument/implementation", Some(params))
333 .await?;
334
335 parse_location_response(response, "implementation")
336 }
337
338 pub async fn completion(
340 &self,
341 path: &Path,
342 line: u32,
343 character: u32,
344 ) -> Result<LspActionResult> {
345 let uri = path_to_uri(path);
346 self.ensure_document_open(path).await?;
347
348 let params = CompletionParams {
349 text_document_position: TextDocumentPositionParams {
350 text_document: TextDocumentIdentifier {
351 uri: parse_uri(&uri)?,
352 },
353 position: Position {
354 line: line.saturating_sub(1),
355 character: character.saturating_sub(1),
356 },
357 },
358 work_done_progress_params: Default::default(),
359 partial_result_params: Default::default(),
360 context: Some(CompletionContext {
361 trigger_kind: CompletionTriggerKind::INVOKED,
362 trigger_character: None,
363 }),
364 };
365
366 let response = self
367 .transport
368 .request(
369 "textDocument/completion",
370 Some(serde_json::to_value(params)?),
371 )
372 .await?;
373
374 parse_completion_response(response)
375 }
376
377 async fn ensure_document_open(&self, path: &Path) -> Result<()> {
379 let uri = path_to_uri(path);
380 if !self.open_documents.read().await.contains_key(&uri) {
381 let content = tokio::fs::read_to_string(path).await?;
382 self.open_document(path, &content).await?;
383 }
384 Ok(())
385 }
386
387 pub async fn capabilities(&self) -> Option<lsp_types::ServerCapabilities> {
389 self.server_capabilities.read().await.clone()
390 }
391
392 pub fn handles_file(&self, path: &Path) -> bool {
394 let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
395 self.config.file_extensions.iter().any(|fe| fe == ext)
396 }
397
398 pub fn handles_language(&self, language: &str) -> bool {
400 let extensions = match language {
401 "rust" => &["rs"][..],
402 "typescript" => &["ts", "tsx"],
403 "javascript" => &["js", "jsx"],
404 "python" => &["py"],
405 "go" => &["go"],
406 "c" => &["c", "h"],
407 "cpp" => &["cpp", "cc", "cxx", "hpp", "h"],
408 _ => &[],
409 };
410
411 extensions
412 .iter()
413 .any(|ext| self.config.file_extensions.iter().any(|fe| fe == *ext))
414 }
415}
416
417fn path_to_uri(path: &Path) -> String {
419 let absolute = std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
420 format!("file://{}", absolute.display())
421}
422
423fn parse_uri(uri_str: &str) -> Result<lsp_types::Uri> {
425 uri_str
426 .parse()
427 .map_err(|e| anyhow::anyhow!("Invalid URI: {e}"))
428}
429
430fn parse_location_response(response: JsonRpcResponse, _operation: &str) -> Result<LspActionResult> {
432 if let Some(error) = response.error {
433 return Ok(LspActionResult::Error {
434 message: error.message,
435 });
436 }
437
438 let Some(result) = response.result else {
439 return Ok(LspActionResult::Definition { locations: vec![] });
440 };
441
442 if let Ok(loc) = serde_json::from_value::<lsp_types::Location>(result.clone()) {
444 return Ok(LspActionResult::Definition {
445 locations: vec![LocationInfo::from(loc)],
446 });
447 }
448
449 if let Ok(locs) = serde_json::from_value::<Vec<lsp_types::Location>>(result.clone()) {
451 return Ok(LspActionResult::Definition {
452 locations: locs.into_iter().map(LocationInfo::from).collect(),
453 });
454 }
455
456 if let Ok(links) = serde_json::from_value::<Vec<lsp_types::LocationLink>>(result) {
458 return Ok(LspActionResult::Definition {
459 locations: links
460 .into_iter()
461 .filter_map(|link| {
462 Some(LocationInfo {
463 uri: link.target_uri.to_string(),
464 range: RangeInfo::from(link.target_selection_range),
465 })
466 })
467 .collect(),
468 });
469 }
470
471 Ok(LspActionResult::Definition { locations: vec![] })
472}
473
474fn parse_hover_response(response: JsonRpcResponse) -> Result<LspActionResult> {
476 if let Some(error) = response.error {
477 return Ok(LspActionResult::Error {
478 message: error.message,
479 });
480 }
481
482 let Some(result) = response.result else {
483 return Ok(LspActionResult::Hover {
484 contents: String::new(),
485 range: None,
486 });
487 };
488
489 if result.is_null() {
490 return Ok(LspActionResult::Hover {
491 contents: "No hover information available".to_string(),
492 range: None,
493 });
494 }
495
496 let hover: lsp_types::Hover = serde_json::from_value(result)?;
497
498 let contents = match hover.contents {
499 lsp_types::HoverContents::Scalar(markup) => match markup {
500 lsp_types::MarkedString::String(s) => s,
501 lsp_types::MarkedString::LanguageString(ls) => ls.value,
502 },
503 lsp_types::HoverContents::Array(markups) => markups
504 .into_iter()
505 .map(|m| match m {
506 lsp_types::MarkedString::String(s) => s,
507 lsp_types::MarkedString::LanguageString(ls) => ls.value,
508 })
509 .collect::<Vec<_>>()
510 .join("\n\n"),
511 lsp_types::HoverContents::Markup(markup) => markup.value,
512 };
513
514 Ok(LspActionResult::Hover {
515 contents,
516 range: hover.range.map(RangeInfo::from),
517 })
518}
519
520fn parse_document_symbols_response(response: JsonRpcResponse) -> Result<LspActionResult> {
522 if let Some(error) = response.error {
523 return Ok(LspActionResult::Error {
524 message: error.message,
525 });
526 }
527
528 let Some(result) = response.result else {
529 return Ok(LspActionResult::DocumentSymbols { symbols: vec![] });
530 };
531
532 if result.is_null() {
533 return Ok(LspActionResult::DocumentSymbols { symbols: vec![] });
534 }
535
536 if let Ok(symbols) = serde_json::from_value::<Vec<lsp_types::DocumentSymbol>>(result.clone()) {
538 return Ok(LspActionResult::DocumentSymbols {
539 symbols: symbols.into_iter().map(SymbolInfo::from).collect(),
540 });
541 }
542
543 if let Ok(symbols) = serde_json::from_value::<Vec<lsp_types::SymbolInformation>>(result) {
545 return Ok(LspActionResult::DocumentSymbols {
546 symbols: symbols.into_iter().map(SymbolInfo::from).collect(),
547 });
548 }
549
550 Ok(LspActionResult::DocumentSymbols { symbols: vec![] })
551}
552
553fn parse_workspace_symbols_response(response: JsonRpcResponse) -> Result<LspActionResult> {
555 if let Some(error) = response.error {
556 return Ok(LspActionResult::Error {
557 message: error.message,
558 });
559 }
560
561 let Some(result) = response.result else {
562 return Ok(LspActionResult::WorkspaceSymbols { symbols: vec![] });
563 };
564
565 if result.is_null() {
566 return Ok(LspActionResult::WorkspaceSymbols { symbols: vec![] });
567 }
568
569 if let Ok(symbols) = serde_json::from_value::<Vec<lsp_types::SymbolInformation>>(result.clone())
571 {
572 return Ok(LspActionResult::WorkspaceSymbols {
573 symbols: symbols.into_iter().map(SymbolInfo::from).collect(),
574 });
575 }
576
577 if let Ok(symbols) = serde_json::from_value::<Vec<lsp_types::WorkspaceSymbol>>(result) {
579 return Ok(LspActionResult::WorkspaceSymbols {
580 symbols: symbols
581 .into_iter()
582 .map(|s| {
583 let (uri, range) = match s.location {
584 lsp_types::OneOf::Left(loc) => {
585 (loc.uri.to_string(), Some(RangeInfo::from(loc.range)))
586 }
587 lsp_types::OneOf::Right(wl) => (wl.uri.to_string(), None),
588 };
589 SymbolInfo {
590 name: s.name,
591 kind: format!("{:?}", s.kind),
592 detail: None,
593 uri: Some(uri),
594 range,
595 container_name: s.container_name,
596 }
597 })
598 .collect(),
599 });
600 }
601
602 Ok(LspActionResult::WorkspaceSymbols { symbols: vec![] })
603}
604
605fn parse_completion_response(response: JsonRpcResponse) -> Result<LspActionResult> {
607 if let Some(error) = response.error {
608 return Ok(LspActionResult::Error {
609 message: error.message,
610 });
611 }
612
613 let Some(result) = response.result else {
614 return Ok(LspActionResult::Completion { items: vec![] });
615 };
616
617 if result.is_null() {
618 return Ok(LspActionResult::Completion { items: vec![] });
619 }
620
621 if let Ok(list) = serde_json::from_value::<lsp_types::CompletionList>(result.clone()) {
623 return Ok(LspActionResult::Completion {
624 items: list
625 .items
626 .into_iter()
627 .map(CompletionItemInfo::from)
628 .collect(),
629 });
630 }
631
632 if let Ok(items) = serde_json::from_value::<Vec<lsp_types::CompletionItem>>(result) {
634 return Ok(LspActionResult::Completion {
635 items: items.into_iter().map(CompletionItemInfo::from).collect(),
636 });
637 }
638
639 Ok(LspActionResult::Completion { items: vec![] })
640}
641
642pub struct LspManager {
644 clients: RwLock<HashMap<String, Arc<LspClient>>>,
645 root_uri: Option<String>,
646}
647
648impl LspManager {
649 pub fn new(root_uri: Option<String>) -> Self {
651 Self {
652 clients: RwLock::new(HashMap::new()),
653 root_uri,
654 }
655 }
656
657 pub async fn get_client(&self, language: &str) -> Result<Arc<LspClient>> {
659 {
661 let clients = self.clients.read().await;
662 if let Some(client) = clients.get(language) {
663 return Ok(Arc::clone(client));
664 }
665 }
666
667 let client = LspClient::for_language(language, self.root_uri.clone()).await?;
669 client.initialize().await?;
670
671 let client = Arc::new(client);
672 self.clients
673 .write()
674 .await
675 .insert(language.to_string(), Arc::clone(&client));
676
677 Ok(client)
678 }
679
680 pub async fn get_client_for_file(&self, path: &Path) -> Result<Arc<LspClient>> {
682 let language = detect_language_from_path(path.to_string_lossy().as_ref())
683 .ok_or_else(|| anyhow::anyhow!("Unknown language for file: {}", path.display()))?;
684 self.get_client(language).await
685 }
686
687 pub async fn handles_file(&self, path: &Path) -> bool {
689 let clients = self.clients.read().await;
690 clients.values().any(|c| c.handles_file(path))
691 }
692
693 pub async fn capabilities_for(&self, language: &str) -> Option<lsp_types::ServerCapabilities> {
695 let clients = self.clients.read().await;
696 if let Some(client) = clients.get(language) {
697 client.capabilities().await
698 } else {
699 None
700 }
701 }
702
703 pub async fn close_document(&self, path: &Path) -> Result<()> {
705 if let Ok(client) = self.get_client_for_file(path).await {
706 client.close_document(path).await?;
707 }
708 Ok(())
709 }
710
711 pub async fn change_document(&self, path: &Path, content: &str) -> Result<()> {
713 if let Ok(client) = self.get_client_for_file(path).await {
714 client.change_document(path, content).await?;
715 }
716 Ok(())
717 }
718
719 pub async fn shutdown_all(&self) {
721 let clients = self.clients.read().await;
722 for (lang, client) in clients.iter() {
723 if let Err(e) = client.shutdown().await {
724 warn!("Failed to shutdown {} language server: {}", lang, e);
725 }
726 }
727 }
728}
729
730impl Default for LspManager {
731 fn default() -> Self {
732 Self::new(None)
733 }
734}