1use crate::language::language_id_for_path;
7use crate::server::LspServerHandle;
8use crate::types::*;
9use anyhow::{anyhow, Context, Result};
10use lsp_types::request::{
11 CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare,
12 DocumentSymbolRequest, GotoDefinition, GotoImplementation, HoverRequest, References,
13 WorkspaceSymbolRequest,
14};
15use lsp_types::{
16 CallHierarchyIncomingCallsParams, CallHierarchyOutgoingCallsParams, ClientCapabilities,
17 DidChangeTextDocumentParams, DidOpenTextDocumentParams, DocumentSymbolParams,
18 GotoDefinitionParams, HoverParams, InitializeParams, InitializedParams, PartialResultParams,
19 ReferenceContext, ReferenceParams, TextDocumentContentChangeEvent, TextDocumentItem,
20 VersionedTextDocumentIdentifier, WorkDoneProgressParams, WorkspaceSymbolParams,
21};
22use lsp_types::GotoDefinitionParams as GotoImplementationParams;
24use serde::{de::DeserializeOwned, Deserialize, Serialize};
25use std::collections::HashMap;
26use std::io::{BufRead, BufReader, Write};
27use std::path::{Path, PathBuf};
28
29fn path_to_uri(path: &Path) -> Result<Uri> {
31 let url = Url::from_file_path(path).map_err(|_| anyhow!("Invalid file path: {:?}", path))?;
32 url.as_str()
33 .parse()
34 .map_err(|e| anyhow!("Failed to parse URI: {}", e))
35}
36
37fn uri_to_path(uri: &Uri) -> Option<PathBuf> {
39 let url: url::Url = uri.as_str().parse().ok()?;
40 url.to_file_path().ok()
41}
42use std::process::{Child, ChildStdin, ChildStdout};
43use std::sync::atomic::{AtomicI64, Ordering};
44use std::sync::{Arc, Mutex, RwLock};
45use tokio::sync::{mpsc, oneshot};
46use tracing::{debug, error, info, trace, warn};
47
48const REQUEST_TIMEOUT_MS: u64 = 10_000;
50const INIT_TIMEOUT_MS: u64 = 45_000;
52
53#[derive(Debug, Serialize)]
55struct JsonRpcRequest<T: Serialize> {
56 jsonrpc: &'static str,
57 id: i64,
58 method: &'static str,
59 params: T,
60}
61
62#[derive(Debug, Serialize)]
64struct JsonRpcNotification<T: Serialize> {
65 jsonrpc: &'static str,
66 method: &'static str,
67 params: T,
68}
69
70#[derive(Debug, Deserialize)]
72struct JsonRpcResponse<T> {
73 #[allow(dead_code)]
74 jsonrpc: String,
75 id: Option<i64>,
76 result: Option<T>,
77 error: Option<JsonRpcError>,
78}
79
80#[derive(Debug, Deserialize)]
82struct JsonRpcError {
83 code: i64,
84 message: String,
85}
86
87#[derive(Debug, Deserialize)]
89struct JsonRpcMessage {
90 #[allow(dead_code)]
91 jsonrpc: String,
92 id: Option<i64>,
93 method: Option<String>,
94 #[serde(default)]
95 params: serde_json::Value,
96 result: Option<serde_json::Value>,
97 error: Option<JsonRpcError>,
98}
99
100struct PendingRequest {
102 sender: oneshot::Sender<Result<serde_json::Value>>,
103}
104
105pub struct LspClient {
107 server_id: String,
109 root: PathBuf,
111 request_id: AtomicI64,
113 pending: Arc<RwLock<HashMap<i64, PendingRequest>>>,
115 writer: Arc<Mutex<ChildStdin>>,
117 diagnostics: Arc<RwLock<HashMap<PathBuf, Vec<Diagnostic>>>>,
119 file_versions: Arc<RwLock<HashMap<PathBuf, i32>>>,
121 shutdown_tx: Option<mpsc::Sender<()>>,
123 #[allow(dead_code)]
125 process: Child,
126}
127
128impl LspClient {
129 pub async fn new(
131 server_id: impl Into<String>,
132 mut handle: LspServerHandle,
133 root: PathBuf,
134 ) -> Result<Self> {
135 let server_id = server_id.into();
136 info!(server_id = %server_id, root = ?root, "Initializing LSP client");
137
138 let stdin = handle
139 .process
140 .stdin
141 .take()
142 .context("Failed to get stdin")?;
143 let stdout = handle
144 .process
145 .stdout
146 .take()
147 .context("Failed to get stdout")?;
148
149 let pending: Arc<RwLock<HashMap<i64, PendingRequest>>> =
150 Arc::new(RwLock::new(HashMap::new()));
151 let diagnostics: Arc<RwLock<HashMap<PathBuf, Vec<Diagnostic>>>> =
152 Arc::new(RwLock::new(HashMap::new()));
153 let file_versions: Arc<RwLock<HashMap<PathBuf, i32>>> =
154 Arc::new(RwLock::new(HashMap::new()));
155
156 let (shutdown_tx, shutdown_rx) = mpsc::channel::<()>(1);
157
158 let pending_clone = pending.clone();
160 let diagnostics_clone = diagnostics.clone();
161 let server_id_clone = server_id.clone();
162 std::thread::spawn(move || {
163 Self::reader_loop(stdout, pending_clone, diagnostics_clone, server_id_clone);
164 });
165
166 let writer = Arc::new(Mutex::new(stdin));
167
168 let mut client = Self {
169 server_id,
170 root: root.clone(),
171 request_id: AtomicI64::new(1),
172 pending,
173 writer,
174 diagnostics,
175 file_versions,
176 shutdown_tx: Some(shutdown_tx),
177 process: handle.process,
178 };
179
180 client.initialize(&root, handle.initialization).await?;
182
183 Ok(client)
184 }
185
186 pub fn server_id(&self) -> &str {
188 &self.server_id
189 }
190
191 pub fn root(&self) -> &Path {
193 &self.root
194 }
195
196 pub fn diagnostics(&self) -> HashMap<PathBuf, Vec<Diagnostic>> {
198 self.diagnostics.read().unwrap().clone()
199 }
200
201 pub fn diagnostics_for_file(&self, path: &Path) -> Vec<Diagnostic> {
203 self.diagnostics
204 .read()
205 .unwrap()
206 .get(path)
207 .cloned()
208 .unwrap_or_default()
209 }
210
211 async fn initialize(
217 &mut self,
218 root: &Path,
219 initialization_options: Option<serde_json::Value>,
220 ) -> Result<()> {
221 let root_uri = path_to_uri(root)?;
222
223 let params = InitializeParams {
224 process_id: Some(std::process::id()),
225 root_uri: Some(root_uri.clone()),
226 root_path: None,
227 initialization_options,
228 capabilities: Self::client_capabilities(),
229 trace: None,
230 workspace_folders: Some(vec![lsp_types::WorkspaceFolder {
231 uri: root_uri,
232 name: root
233 .file_name()
234 .and_then(|n| n.to_str())
235 .unwrap_or("workspace")
236 .to_string(),
237 }]),
238 client_info: Some(lsp_types::ClientInfo {
239 name: "codive-lsp".to_string(),
240 version: Some(env!("CARGO_PKG_VERSION").to_string()),
241 }),
242 locale: None,
243 work_done_progress_params: WorkDoneProgressParams::default(),
244 };
245
246 let _result: lsp_types::InitializeResult =
247 self.request::<lsp_types::request::Initialize>(params).await?;
248
249 self.notify::<lsp_types::notification::Initialized>(InitializedParams {})?;
251
252 info!(server_id = %self.server_id, "LSP server initialized");
253 Ok(())
254 }
255
256 pub async fn open_file(&self, path: &Path) -> Result<()> {
258 let path = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
259
260 let existing_version = {
262 let versions = self.file_versions.read().unwrap();
263 versions.get(&path).copied()
264 };
265
266 let content = tokio::fs::read_to_string(&path).await?;
267
268 if let Some(version) = existing_version {
269 return self.change_file(&path, &content, version + 1);
271 }
272
273 let uri = path_to_uri(&path)?;
274 let language_id = language_id_for_path(&path);
275
276 debug!(path = ?path, language_id = %language_id, "Opening file in LSP");
277
278 let params = DidOpenTextDocumentParams {
279 text_document: TextDocumentItem {
280 uri,
281 language_id: language_id.to_string(),
282 version: 0,
283 text: content,
284 },
285 };
286
287 self.notify::<lsp_types::notification::DidOpenTextDocument>(params)?;
288
289 self.file_versions.write().unwrap().insert(path, 0);
291
292 Ok(())
293 }
294
295 fn change_file(&self, path: &Path, content: &str, version: i32) -> Result<()> {
297 let uri = path_to_uri(path)?;
298
299 let params = DidChangeTextDocumentParams {
300 text_document: VersionedTextDocumentIdentifier { uri, version },
301 content_changes: vec![TextDocumentContentChangeEvent {
302 range: None,
303 range_length: None,
304 text: content.to_string(),
305 }],
306 };
307
308 self.notify::<lsp_types::notification::DidChangeTextDocument>(params)?;
309
310 self.file_versions.write().unwrap().insert(path.to_path_buf(), version);
312
313 Ok(())
314 }
315
316 pub async fn hover(&self, path: &Path, line: u32, character: u32) -> Result<Option<Hover>> {
318 let uri = path_to_uri(path)?;
319
320 let params = HoverParams {
321 text_document_position_params: TextDocumentPositionParams {
322 text_document: TextDocumentIdentifier { uri },
323 position: Position { line, character },
324 },
325 work_done_progress_params: WorkDoneProgressParams::default(),
326 };
327
328 self.request::<HoverRequest>(params).await
329 }
330
331 pub async fn definition(&self, path: &Path, line: u32, character: u32) -> Result<Vec<Location>> {
333 let uri = path_to_uri(path)?;
334
335 let params = GotoDefinitionParams {
336 text_document_position_params: TextDocumentPositionParams {
337 text_document: TextDocumentIdentifier { uri },
338 position: Position { line, character },
339 },
340 work_done_progress_params: WorkDoneProgressParams::default(),
341 partial_result_params: PartialResultParams::default(),
342 };
343
344 let result: Option<GotoDefinitionResponse> =
345 self.request::<GotoDefinition>(params).await?;
346
347 Ok(match result {
348 Some(GotoDefinitionResponse::Scalar(loc)) => vec![loc],
349 Some(GotoDefinitionResponse::Array(locs)) => locs,
350 Some(GotoDefinitionResponse::Link(links)) => links
351 .into_iter()
352 .map(|l| Location {
353 uri: l.target_uri,
354 range: l.target_selection_range,
355 })
356 .collect(),
357 None => vec![],
358 })
359 }
360
361 pub async fn references(
363 &self,
364 path: &Path,
365 line: u32,
366 character: u32,
367 include_declaration: bool,
368 ) -> Result<Vec<Location>> {
369 let uri = path_to_uri(path)?;
370
371 let params = ReferenceParams {
372 text_document_position: TextDocumentPositionParams {
373 text_document: TextDocumentIdentifier { uri },
374 position: Position { line, character },
375 },
376 work_done_progress_params: WorkDoneProgressParams::default(),
377 partial_result_params: PartialResultParams::default(),
378 context: ReferenceContext {
379 include_declaration,
380 },
381 };
382
383 let result: Option<Vec<Location>> = self.request::<References>(params).await?;
384 Ok(result.unwrap_or_default())
385 }
386
387 pub async fn implementation(
389 &self,
390 path: &Path,
391 line: u32,
392 character: u32,
393 ) -> Result<Vec<Location>> {
394 let uri = path_to_uri(path)?;
395
396 let params = GotoImplementationParams {
397 text_document_position_params: TextDocumentPositionParams {
398 text_document: TextDocumentIdentifier { uri },
399 position: Position { line, character },
400 },
401 work_done_progress_params: WorkDoneProgressParams::default(),
402 partial_result_params: PartialResultParams::default(),
403 };
404
405 let result: Option<GotoDefinitionResponse> =
406 self.request::<GotoImplementation>(params).await?;
407
408 Ok(match result {
409 Some(GotoDefinitionResponse::Scalar(loc)) => vec![loc],
410 Some(GotoDefinitionResponse::Array(locs)) => locs,
411 Some(GotoDefinitionResponse::Link(links)) => links
412 .into_iter()
413 .map(|l| Location {
414 uri: l.target_uri,
415 range: l.target_selection_range,
416 })
417 .collect(),
418 None => vec![],
419 })
420 }
421
422 pub async fn document_symbols(&self, path: &Path) -> Result<DocumentSymbolResponse> {
424 let uri = path_to_uri(path)?;
425
426 let params = DocumentSymbolParams {
427 text_document: TextDocumentIdentifier { uri },
428 work_done_progress_params: WorkDoneProgressParams::default(),
429 partial_result_params: PartialResultParams::default(),
430 };
431
432 let result: Option<DocumentSymbolResponse> =
433 self.request::<DocumentSymbolRequest>(params).await?;
434 Ok(result.unwrap_or(DocumentSymbolResponse::Flat(vec![])))
435 }
436
437 pub async fn workspace_symbols(&self, query: &str) -> Result<Vec<SymbolInformation>> {
439 let params = WorkspaceSymbolParams {
440 query: query.to_string(),
441 work_done_progress_params: WorkDoneProgressParams::default(),
442 partial_result_params: PartialResultParams::default(),
443 };
444
445 let result: Option<WorkspaceSymbolResponse> =
446 self.request::<WorkspaceSymbolRequest>(params).await?;
447
448 Ok(match result {
449 Some(WorkspaceSymbolResponse::Flat(symbols)) => symbols,
450 Some(WorkspaceSymbolResponse::Nested(symbols)) => {
451 symbols
453 .into_iter()
454 .filter_map(|s| {
455 let location = match s.location {
456 lsp_types::OneOf::Left(loc) => loc,
457 lsp_types::OneOf::Right(doc_id) => Location {
458 uri: doc_id.uri,
459 range: Range::default(),
460 },
461 };
462 Some(SymbolInformation {
463 name: s.name,
464 kind: s.kind,
465 tags: s.tags,
466 deprecated: None,
467 location,
468 container_name: s.container_name,
469 })
470 })
471 .collect()
472 }
473 None => vec![],
474 })
475 }
476
477 pub async fn prepare_call_hierarchy(
479 &self,
480 path: &Path,
481 line: u32,
482 character: u32,
483 ) -> Result<Vec<CallHierarchyItem>> {
484 let uri = path_to_uri(path)?;
485
486 let params = lsp_types::CallHierarchyPrepareParams {
487 text_document_position_params: TextDocumentPositionParams {
488 text_document: TextDocumentIdentifier { uri },
489 position: Position { line, character },
490 },
491 work_done_progress_params: WorkDoneProgressParams::default(),
492 };
493
494 let result: Option<Vec<CallHierarchyItem>> =
495 self.request::<CallHierarchyPrepare>(params).await?;
496 Ok(result.unwrap_or_default())
497 }
498
499 pub async fn incoming_calls(
501 &self,
502 item: CallHierarchyItem,
503 ) -> Result<Vec<CallHierarchyIncomingCall>> {
504 let params = CallHierarchyIncomingCallsParams {
505 item,
506 work_done_progress_params: WorkDoneProgressParams::default(),
507 partial_result_params: PartialResultParams::default(),
508 };
509
510 let result: Option<Vec<CallHierarchyIncomingCall>> =
511 self.request::<CallHierarchyIncomingCalls>(params).await?;
512 Ok(result.unwrap_or_default())
513 }
514
515 pub async fn outgoing_calls(
517 &self,
518 item: CallHierarchyItem,
519 ) -> Result<Vec<CallHierarchyOutgoingCall>> {
520 let params = CallHierarchyOutgoingCallsParams {
521 item,
522 work_done_progress_params: WorkDoneProgressParams::default(),
523 partial_result_params: PartialResultParams::default(),
524 };
525
526 let result: Option<Vec<CallHierarchyOutgoingCall>> =
527 self.request::<CallHierarchyOutgoingCalls>(params).await?;
528 Ok(result.unwrap_or_default())
529 }
530
531 pub async fn shutdown(mut self) {
533 info!(server_id = %self.server_id, "Shutting down LSP client");
534
535 let _ = self.request::<lsp_types::request::Shutdown>(()).await;
537
538 let _ = self.notify::<lsp_types::notification::Exit>(());
540
541 if let Some(tx) = self.shutdown_tx.take() {
543 let _ = tx.send(()).await;
544 }
545 }
546
547 async fn request<R>(&self, params: R::Params) -> Result<R::Result>
553 where
554 R: lsp_types::request::Request,
555 R::Params: Serialize,
556 R::Result: DeserializeOwned,
557 {
558 let id = self.request_id.fetch_add(1, Ordering::SeqCst);
559
560 let request = JsonRpcRequest {
561 jsonrpc: "2.0",
562 id,
563 method: R::METHOD,
564 params,
565 };
566
567 let message = serde_json::to_string(&request)?;
568 let header = format!("Content-Length: {}\r\n\r\n", message.len());
569
570 trace!(id = id, method = R::METHOD, "Sending LSP request");
571
572 let (tx, rx) = oneshot::channel();
574
575 {
577 let mut pending = self.pending.write().unwrap();
578 pending.insert(id, PendingRequest { sender: tx });
579 }
580
581 {
583 let mut writer = self.writer.lock().unwrap();
584 writer.write_all(header.as_bytes())?;
585 writer.write_all(message.as_bytes())?;
586 writer.flush()?;
587 }
588
589 let result = tokio::time::timeout(
591 std::time::Duration::from_millis(REQUEST_TIMEOUT_MS),
592 rx,
593 )
594 .await
595 .map_err(|_| anyhow!("LSP request timed out"))??;
596
597 let value = result?;
598 let result: R::Result = serde_json::from_value(value)?;
599 Ok(result)
600 }
601
602 fn notify<N>(&self, params: N::Params) -> Result<()>
604 where
605 N: lsp_types::notification::Notification,
606 N::Params: Serialize,
607 {
608 let notification = JsonRpcNotification {
609 jsonrpc: "2.0",
610 method: N::METHOD,
611 params,
612 };
613
614 let message = serde_json::to_string(¬ification)?;
615 let header = format!("Content-Length: {}\r\n\r\n", message.len());
616
617 trace!(method = N::METHOD, "Sending LSP notification");
618
619 let mut writer = self.writer.lock().unwrap();
620 writer.write_all(header.as_bytes())?;
621 writer.write_all(message.as_bytes())?;
622 writer.flush()?;
623
624 Ok(())
625 }
626
627 fn reader_loop(
629 stdout: ChildStdout,
630 pending: Arc<RwLock<HashMap<i64, PendingRequest>>>,
631 diagnostics: Arc<RwLock<HashMap<PathBuf, Vec<Diagnostic>>>>,
632 server_id: String,
633 ) {
634 let mut reader = BufReader::new(stdout);
635 let mut headers = String::new();
636
637 loop {
638 headers.clear();
639
640 let mut content_length: Option<usize> = None;
642 loop {
643 let mut line = String::new();
644 match reader.read_line(&mut line) {
645 Ok(0) => {
646 debug!(server_id = %server_id, "LSP server stdout closed");
647 return;
648 }
649 Ok(_) => {
650 if line == "\r\n" {
651 break;
652 }
653 if line.to_lowercase().starts_with("content-length:") {
654 if let Some(len_str) = line.split(':').nth(1) {
655 content_length = len_str.trim().parse().ok();
656 }
657 }
658 }
659 Err(e) => {
660 error!(server_id = %server_id, error = ?e, "Error reading from LSP server");
661 return;
662 }
663 }
664 }
665
666 let content_length = match content_length {
668 Some(len) => len,
669 None => {
670 warn!(server_id = %server_id, "No Content-Length header");
671 continue;
672 }
673 };
674
675 let mut content = vec![0u8; content_length];
676 if let Err(e) = std::io::Read::read_exact(&mut reader, &mut content) {
677 error!(server_id = %server_id, error = ?e, "Error reading LSP content");
678 continue;
679 }
680
681 let message: JsonRpcMessage = match serde_json::from_slice(&content) {
683 Ok(msg) => msg,
684 Err(e) => {
685 warn!(server_id = %server_id, error = ?e, "Failed to parse LSP message");
686 continue;
687 }
688 };
689
690 if let Some(id) = message.id {
692 if message.method.is_none() {
693 let mut pending = pending.write().unwrap();
695 if let Some(req) = pending.remove(&id) {
696 let result = if let Some(error) = message.error {
697 Err(anyhow!("LSP error {}: {}", error.code, error.message))
698 } else {
699 Ok(message.result.unwrap_or(serde_json::Value::Null))
700 };
701 let _ = req.sender.send(result);
702 }
703 continue;
704 }
705 }
706
707 if let Some(method) = &message.method {
709 match method.as_str() {
710 "textDocument/publishDiagnostics" => {
711 if let Ok(params) =
712 serde_json::from_value::<lsp_types::PublishDiagnosticsParams>(
713 message.params,
714 )
715 {
716 if let Some(path) = uri_to_path(¶ms.uri) {
717 debug!(
718 server_id = %server_id,
719 path = ?path,
720 count = params.diagnostics.len(),
721 "Received diagnostics"
722 );
723 diagnostics.write().unwrap().insert(path, params.diagnostics);
724 }
725 }
726 }
727 "window/logMessage" | "window/showMessage" => {
728 if let Ok(params) =
730 serde_json::from_value::<lsp_types::LogMessageParams>(message.params)
731 {
732 debug!(server_id = %server_id, message = %params.message, "LSP server message");
733 }
734 }
735 _ => {
736 trace!(server_id = %server_id, method = %method, "Unhandled LSP notification");
737 }
738 }
739 }
740 }
741 }
742
743 fn client_capabilities() -> ClientCapabilities {
745 ClientCapabilities {
746 text_document: Some(lsp_types::TextDocumentClientCapabilities {
747 synchronization: Some(lsp_types::TextDocumentSyncClientCapabilities {
748 dynamic_registration: Some(false),
749 will_save: Some(false),
750 will_save_wait_until: Some(false),
751 did_save: Some(true),
752 }),
753 hover: Some(lsp_types::HoverClientCapabilities {
754 dynamic_registration: Some(false),
755 content_format: Some(vec![MarkupKind::Markdown, MarkupKind::PlainText]),
756 }),
757 definition: Some(lsp_types::GotoCapability {
758 dynamic_registration: Some(false),
759 link_support: Some(true),
760 }),
761 references: Some(lsp_types::DynamicRegistrationClientCapabilities {
762 dynamic_registration: Some(false),
763 }),
764 implementation: Some(lsp_types::GotoCapability {
765 dynamic_registration: Some(false),
766 link_support: Some(true),
767 }),
768 document_symbol: Some(lsp_types::DocumentSymbolClientCapabilities {
769 dynamic_registration: Some(false),
770 symbol_kind: None,
771 hierarchical_document_symbol_support: Some(true),
772 tag_support: None,
773 }),
774 publish_diagnostics: Some(lsp_types::PublishDiagnosticsClientCapabilities {
775 related_information: Some(true),
776 tag_support: None,
777 version_support: Some(true),
778 code_description_support: Some(true),
779 data_support: Some(true),
780 }),
781 call_hierarchy: Some(lsp_types::CallHierarchyClientCapabilities {
782 dynamic_registration: Some(false),
783 }),
784 ..Default::default()
785 }),
786 workspace: Some(lsp_types::WorkspaceClientCapabilities {
787 workspace_folders: Some(true),
788 symbol: Some(lsp_types::WorkspaceSymbolClientCapabilities {
789 dynamic_registration: Some(false),
790 symbol_kind: None,
791 tag_support: None,
792 resolve_support: None,
793 }),
794 ..Default::default()
795 }),
796 window: Some(lsp_types::WindowClientCapabilities {
797 work_done_progress: Some(true),
798 show_message: None,
799 show_document: None,
800 }),
801 ..Default::default()
802 }
803 }
804}