1pub mod config;
9pub mod overlay;
10
11const METHOD_PROGRESS: &str = "$/progress";
13const METHOD_WORK_DONE_PROGRESS_CREATE: &str = "window/workDoneProgress/create";
15const PROGRESS_KIND_END: &str = "end";
17
18use self::config::{
19 LspCommand, ensure_rust_analyzer_config, lsp_server_config,
20 resolve_lsp_command,
21};
22use crate::canvas_editor::lsp::{
23 LspClient, LspDocument, LspPosition, LspTextChange,
24};
25use crate::text_utils::char_to_byte_index;
26use serde_json::json;
27use std::collections::HashMap;
28use std::io::{BufRead, BufReader, Read, Write};
29use std::process::{Child, Command, Stdio};
30use std::sync::atomic::{AtomicU64, Ordering};
31use std::sync::{Arc, Mutex, mpsc};
32use std::thread;
33
34struct TextModel {
42 lines: Vec<String>,
44}
45
46impl TextModel {
47 fn from_text(text: &str) -> Self {
52 let lines = if text.is_empty() {
53 vec![String::new()]
54 } else {
55 text.lines().map(String::from).collect()
56 };
57 Self { lines }
58 }
59
60 fn apply_change(&mut self, change: &LspTextChange) {
64 let start_line = change.range.start.line as usize;
65 let end_line = change.range.end.line as usize;
66
67 if start_line >= self.lines.len() || end_line >= self.lines.len() {
68 return;
69 }
70
71 let start_col = change.range.start.character as usize;
72 let end_col = change.range.end.character as usize;
73
74 let start_byte = char_to_byte_index(&self.lines[start_line], start_col);
75 let end_byte = char_to_byte_index(&self.lines[end_line], end_col);
76
77 let prefix = self.lines[start_line][..start_byte].to_string();
78 let suffix = self.lines[end_line][end_byte..].to_string();
79
80 let inserted: Vec<&str> = change.text.split('\n').collect();
81 let mut replacement: Vec<String> = Vec::new();
82
83 if inserted.len() == 1 {
84 replacement.push(format!("{}{}{}", prefix, inserted[0], suffix));
85 } else {
86 replacement.push(format!("{}{}", prefix, inserted[0]));
87 for mid in inserted.iter().take(inserted.len() - 1).skip(1) {
88 replacement.push((*mid).to_string());
89 }
90 replacement.push(format!(
91 "{}{}",
92 inserted[inserted.len() - 1],
93 suffix
94 ));
95 }
96
97 self.lines.splice(start_line..=end_line, replacement);
98 }
99
100 fn to_utf16_position(&self, position: LspPosition) -> LspPosition {
104 let line_index = position.line as usize;
105 let char_index = position.character as usize;
106 let line = self.lines.get(line_index).map_or("", |l| l.as_str());
107
108 let utf16_col =
109 line.chars().take(char_index).map(|c| c.len_utf16() as u32).sum();
110 LspPosition { line: position.line, character: utf16_col }
111 }
112}
113
114struct DocumentState {
120 text: TextModel,
122}
123
124enum LspRequestKind {
130 Hover,
132 Completion,
134 Definition,
136}
137
138pub enum LspEvent {
147 Hover {
149 text: String,
151 },
152 Completion {
154 items: Vec<String>,
156 },
157 Definition {
159 uri: String,
161 range: crate::canvas_editor::lsp::LspRange,
163 },
164 Progress {
166 token: String,
168 server_key: String,
170 title: String,
172 message: Option<String>,
174 percentage: Option<u32>,
176 done: bool,
178 },
179 Log {
181 server_key: String,
183 message: String,
185 },
186}
187
188pub struct LspProcessClient {
213 child: Child,
215 writer: mpsc::Sender<Vec<u8>>,
217 documents: Arc<Mutex<HashMap<String, DocumentState>>>,
219 request_id: AtomicU64,
221 pending_requests: Arc<Mutex<HashMap<u64, LspRequestKind>>>,
223 _writer_thread: thread::JoinHandle<()>,
225 _reader_thread: thread::JoinHandle<()>,
227 _stderr_thread: thread::JoinHandle<()>,
229}
230
231impl LspProcessClient {
232 pub fn new_with_server(
260 root_uri: &str,
261 events: mpsc::Sender<LspEvent>,
262 server_key: &str,
263 ) -> Result<Self, String> {
264 let config = lsp_server_config(server_key)
265 .ok_or_else(|| format!("Unsupported LSP server: {}", server_key))?;
266
267 if server_key == "rust-analyzer" {
268 ensure_rust_analyzer_config();
269 }
270
271 let command = resolve_lsp_command(config)?;
272 Self::new_with_command(root_uri, events, &command, server_key)
273 }
274
275 fn new_with_command(
284 root_uri: &str,
285 events: mpsc::Sender<LspEvent>,
286 command: &LspCommand,
287 server_key: &str,
288 ) -> Result<Self, String> {
289 let mut child = Command::new(&command.program)
290 .args(&command.args)
291 .stdin(Stdio::piped())
292 .stdout(Stdio::piped())
293 .stderr(Stdio::piped())
294 .spawn()
295 .map_err(|e| {
296 if e.kind() == std::io::ErrorKind::NotFound {
297 if command.program == "rust-analyzer" {
298 "LSP server program rust-analyzer not found. Please install rust-analyzer or set RUST_ANALYZER/RUST_ANALYZER_PATH environment variable".to_string()
299 } else {
300 format!("LSP server program {} not found", command.program)
301 }
302 } else {
303 e.to_string()
304 }
305 })?;
306
307 let stdin = child.stdin.take().ok_or("stdin unavailable")?;
308 let stdout = child.stdout.take().ok_or("stdout unavailable")?;
309 let stderr = child.stderr.take().ok_or("stderr unavailable")?;
310
311 let (tx, rx) = mpsc::channel::<Vec<u8>>();
312 let pending_requests = Arc::new(Mutex::new(HashMap::new()));
313 let pending_reader = pending_requests.clone();
314 let events_reader = events.clone();
315 let events_log = events;
316 let server_key = server_key.to_string();
317 let server_key_reader = server_key.clone();
318 let server_key_log = server_key;
319 let tx_reader = tx.clone();
320
321 let writer_thread = thread::spawn(move || {
322 let mut input = stdin;
323 for bytes in rx {
324 if input.write_all(&bytes).is_err() {
325 break;
326 }
327 let _ = input.flush();
328 }
329 });
330
331 let reader_thread = thread::spawn(move || {
332 let mut reader = BufReader::new(stdout);
333 loop {
334 let mut content_length: Option<usize> = None;
335 let mut line = String::new();
336
337 loop {
338 line.clear();
339 if reader
340 .read_line(&mut line)
341 .ok()
342 .filter(|n| *n > 0)
343 .is_none()
344 {
345 return;
346 }
347 let trimmed = line.trim();
348 if trimmed.is_empty() {
349 break;
350 }
351 if let Some(value) = trimmed.strip_prefix("Content-Length:")
352 && let Ok(len) = value.trim().parse::<usize>()
353 {
354 content_length = Some(len);
355 }
356 }
357
358 let Some(len) = content_length else { continue };
359 let mut buf = vec![0u8; len];
360 if reader.read_exact(&mut buf).is_err() {
361 return;
362 }
363
364 if let Ok(value) =
365 serde_json::from_slice::<serde_json::Value>(&buf)
366 {
367 if let Some(id) = value.get("id").and_then(|v| v.as_u64()) {
368 if let Some(method) =
369 value.get("method").and_then(|m| m.as_str())
370 {
371 handle_server_request(id, method, &tx_reader);
372 } else {
373 handle_client_response(
374 id,
375 &value,
376 &pending_reader,
377 &events_reader,
378 );
379 }
380 } else if let Some(method) =
381 value.get("method").and_then(|m| m.as_str())
382 && let Some(params) = value.get("params")
383 {
384 handle_server_notification(
385 method,
386 params,
387 &events_reader,
388 &server_key_reader,
389 );
390 }
391 }
392 }
393 });
394
395 let stderr_thread = thread::spawn(move || {
396 let reader = BufReader::new(stderr);
397 for line in reader.lines() {
398 let Ok(line) = line else { break };
399 let line = line.trim();
400 if line.is_empty() {
401 continue;
402 }
403 let _ = events_log.send(LspEvent::Log {
404 server_key: server_key_log.clone(),
405 message: line.to_string(),
406 });
407 }
408 });
409
410 let client = Self {
411 child,
412 writer: tx,
413 documents: Arc::new(Mutex::new(HashMap::new())),
414 request_id: AtomicU64::new(1),
415 pending_requests,
416 _writer_thread: writer_thread,
417 _reader_thread: reader_thread,
418 _stderr_thread: stderr_thread,
419 };
420
421 let initialize = json!({
422 "jsonrpc": "2.0",
423 "id": client.next_id(),
424 "method": "initialize",
425 "params": {
426 "processId": std::process::id(),
427 "rootUri": root_uri,
428 "capabilities": {
429 "textDocument": {
430 "synchronization": {
431 "dynamicRegistration": false,
432 "willSave": false,
433 "didSave": true
434 }
435 },
436 "window": {
437 "workDoneProgress": true
438 }
439 },
440 "workspaceFolders": null
441 }
442 });
443 client.send_message(&initialize);
444
445 let initialized = json!({
446 "jsonrpc": "2.0",
447 "method": "initialized",
448 "params": {}
449 });
450 client.send_message(&initialized);
451
452 Ok(client)
453 }
454
455 fn next_id(&self) -> u64 {
457 self.request_id.fetch_add(1, Ordering::Relaxed)
458 }
459
460 fn send_message(&self, value: &serde_json::Value) {
464 if let Ok(data) = serde_json::to_vec(&value) {
465 let mut header =
466 format!("Content-Length: {}\r\n\r\n", data.len()).into_bytes();
467 header.extend_from_slice(&data);
468 let _ = self.writer.send(header);
469 }
470 }
471
472 fn apply_change_and_convert(
476 &self,
477 uri: &str,
478 changes: &[LspTextChange],
479 ) -> Vec<serde_json::Value> {
480 let mut out = Vec::new();
481 let mut docs = self.documents.lock().unwrap_or_else(|e| e.into_inner());
482 let Some(state) = docs.get_mut(uri) else { return out };
483
484 for change in changes {
485 let start = state.text.to_utf16_position(change.range.start);
486 let end = state.text.to_utf16_position(change.range.end);
487
488 out.push(json!({
489 "range": {
490 "start": { "line": start.line, "character": start.character },
491 "end": { "line": end.line, "character": end.character }
492 },
493 "text": change.text
494 }));
495
496 state.text.apply_change(change);
497 }
498 out
499 }
500}
501
502fn handle_server_request(id: u64, method: &str, tx: &mpsc::Sender<Vec<u8>>) {
511 if method == METHOD_WORK_DONE_PROGRESS_CREATE {
512 let response = json!({
513 "jsonrpc": "2.0",
514 "id": id,
515 "result": null
516 });
517 if let Ok(data) = serde_json::to_vec(&response) {
518 let mut header =
519 format!("Content-Length: {}\r\n\r\n", data.len()).into_bytes();
520 header.extend_from_slice(&data);
521 let _ = tx.send(header);
522 }
523 }
524}
525
526fn handle_client_response(
531 id: u64,
532 value: &serde_json::Value,
533 pending: &Arc<Mutex<HashMap<u64, LspRequestKind>>>,
534 events: &mpsc::Sender<LspEvent>,
535) {
536 let kind = {
537 let mut map = pending.lock().unwrap_or_else(|e| e.into_inner());
538 map.remove(&id)
539 };
540
541 let Some(kind) = kind else { return };
542 let result = value.get("result").unwrap_or(&serde_json::Value::Null);
543
544 match kind {
545 LspRequestKind::Hover => {
546 let text = parse_hover_text(result).unwrap_or_default();
547 let _ = events.send(LspEvent::Hover { text });
548 }
549 LspRequestKind::Completion => {
550 let items = parse_completion_items(result);
551 if !items.is_empty() {
552 let _ = events.send(LspEvent::Completion { items });
553 }
554 }
555 LspRequestKind::Definition => {
556 if let Some((uri, range)) = parse_definition_location(result) {
557 let _ = events.send(LspEvent::Definition { uri, range });
558 }
559 }
560 }
561}
562
563fn handle_server_notification(
568 method: &str,
569 params: &serde_json::Value,
570 events: &mpsc::Sender<LspEvent>,
571 server_key: &str,
572) {
573 if method != METHOD_PROGRESS {
574 return;
575 }
576
577 let Some(token) = params.get("token").and_then(|t| {
578 t.as_str()
579 .map(String::from)
580 .or_else(|| t.as_i64().map(|i| i.to_string()))
581 }) else {
582 return;
583 };
584
585 let Some(val) = params.get("value") else { return };
586
587 let kind = val.get("kind").and_then(|k| k.as_str()).unwrap_or("");
588 let title = val
589 .get("title")
590 .and_then(|t| t.as_str())
591 .map(String::from)
592 .unwrap_or_default();
593 let message = val.get("message").and_then(|m| m.as_str()).map(String::from);
594 let percentage =
595 val.get("percentage").and_then(|p| p.as_u64()).map(|p| p as u32);
596 let done = kind == PROGRESS_KIND_END;
597
598 let _ = events.send(LspEvent::Progress {
599 token,
600 server_key: server_key.to_string(),
601 title,
602 message,
603 percentage,
604 done,
605 });
606}
607
608impl Drop for LspProcessClient {
610 fn drop(&mut self) {
611 let shutdown = json!({
612 "jsonrpc": "2.0",
613 "id": self.next_id(),
614 "method": "shutdown",
615 "params": null
616 });
617 self.send_message(&shutdown);
618
619 let exit = json!({
620 "jsonrpc": "2.0",
621 "method": "exit",
622 "params": {}
623 });
624 self.send_message(&exit);
625
626 if self.child.try_wait().ok().flatten().is_none() {
627 let _ = self.child.kill();
628 }
629 }
630}
631
632fn parse_hover_text(result: &serde_json::Value) -> Option<String> {
638 let contents = result.get("contents")?;
639 hover_text_from_contents(contents)
640}
641
642fn hover_text_from_contents(value: &serde_json::Value) -> Option<String> {
646 match value {
647 serde_json::Value::String(text) => Some(text.clone()),
648 serde_json::Value::Array(items) => {
649 let parts: Vec<String> =
650 items.iter().filter_map(hover_text_from_contents).collect();
651 if parts.is_empty() { None } else { Some(parts.join("\n")) }
652 }
653 serde_json::Value::Object(map) => {
654 map.get("value").and_then(|v| v.as_str()).map(String::from)
655 }
656 _ => None,
657 }
658}
659
660fn parse_completion_items(result: &serde_json::Value) -> Vec<String> {
664 let mut items = Vec::new();
665
666 if let Some(array) = result.as_array() {
667 items.extend(array.iter());
668 } else if let Some(array) = result.get("items").and_then(|v| v.as_array()) {
669 items.extend(array.iter());
670 }
671
672 items
673 .iter()
674 .filter_map(|item| item.get("label").and_then(|v| v.as_str()))
675 .map(String::from)
676 .collect()
677}
678
679fn parse_definition_location(
683 result: &serde_json::Value,
684) -> Option<(String, crate::canvas_editor::lsp::LspRange)> {
685 fn extract_location(
686 loc: &serde_json::Value,
687 ) -> Option<(String, crate::canvas_editor::lsp::LspRange)> {
688 let uri = loc.get("uri")?.as_str()?.to_string();
689 let range_val = loc.get("range")?;
690
691 let start = range_val.get("start")?;
692 let end = range_val.get("end")?;
693
694 let start_line = start.get("line")?.as_u64()? as u32;
695 let start_char = start.get("character")?.as_u64()? as u32;
696 let end_line = end.get("line")?.as_u64()? as u32;
697 let end_char = end.get("character")?.as_u64()? as u32;
698
699 Some((
700 uri,
701 crate::canvas_editor::lsp::LspRange {
702 start: crate::canvas_editor::lsp::LspPosition {
703 line: start_line,
704 character: start_char,
705 },
706 end: crate::canvas_editor::lsp::LspPosition {
707 line: end_line,
708 character: end_char,
709 },
710 },
711 ))
712 }
713
714 fn extract_link(
715 link: &serde_json::Value,
716 ) -> Option<(String, crate::canvas_editor::lsp::LspRange)> {
717 let uri = link.get("targetUri")?.as_str()?.to_string();
718 let range_val =
719 link.get("targetSelectionRange").or(link.get("targetRange"))?;
720
721 let start = range_val.get("start")?;
722 let end = range_val.get("end")?;
723
724 let start_line = start.get("line")?.as_u64()? as u32;
725 let start_char = start.get("character")?.as_u64()? as u32;
726 let end_line = end.get("line")?.as_u64()? as u32;
727 let end_char = end.get("character")?.as_u64()? as u32;
728
729 Some((
730 uri,
731 crate::canvas_editor::lsp::LspRange {
732 start: crate::canvas_editor::lsp::LspPosition {
733 line: start_line,
734 character: start_char,
735 },
736 end: crate::canvas_editor::lsp::LspPosition {
737 line: end_line,
738 character: end_char,
739 },
740 },
741 ))
742 }
743
744 if let Some(array) = result.as_array() {
745 if let Some(first) = array.first() {
746 if first.get("targetUri").is_some() {
747 extract_link(first)
748 } else {
749 extract_location(first)
750 }
751 } else {
752 None
753 }
754 } else if result.is_object() {
755 extract_location(result)
756 } else {
757 None
758 }
759}
760
761impl LspClient for LspProcessClient {
766 fn did_open(&mut self, document: &LspDocument, text: &str) {
767 let mut docs = self.documents.lock().unwrap_or_else(|e| e.into_inner());
768 docs.insert(
769 document.uri.clone(),
770 DocumentState { text: TextModel::from_text(text) },
771 );
772
773 let msg = json!({
774 "jsonrpc": "2.0",
775 "method": "textDocument/didOpen",
776 "params": {
777 "textDocument": {
778 "uri": document.uri,
779 "languageId": document.language_id,
780 "version": document.version,
781 "text": text
782 }
783 }
784 });
785 self.send_message(&msg);
786 }
787
788 fn did_change(
789 &mut self,
790 document: &LspDocument,
791 changes: &[LspTextChange],
792 ) {
793 let content_changes =
794 self.apply_change_and_convert(&document.uri, changes);
795 if content_changes.is_empty() {
796 return;
797 }
798
799 let msg = json!({
800 "jsonrpc": "2.0",
801 "method": "textDocument/didChange",
802 "params": {
803 "textDocument": {
804 "uri": document.uri,
805 "version": document.version
806 },
807 "contentChanges": content_changes
808 }
809 });
810 self.send_message(&msg);
811 }
812
813 fn did_save(&mut self, document: &LspDocument, text: &str) {
814 let msg = json!({
815 "jsonrpc": "2.0",
816 "method": "textDocument/didSave",
817 "params": {
818 "textDocument": { "uri": document.uri },
819 "text": text
820 }
821 });
822 self.send_message(&msg);
823 }
824
825 fn did_close(&mut self, document: &LspDocument) {
826 let mut docs = self.documents.lock().unwrap_or_else(|e| e.into_inner());
827 docs.remove(&document.uri);
828
829 let msg = json!({
830 "jsonrpc": "2.0",
831 "method": "textDocument/didClose",
832 "params": {
833 "textDocument": { "uri": document.uri }
834 }
835 });
836 self.send_message(&msg);
837 }
838
839 fn request_hover(&mut self, document: &LspDocument, position: LspPosition) {
840 let docs = self.documents.lock().unwrap_or_else(|e| e.into_inner());
841 let Some(state) = docs.get(&document.uri) else { return };
842 let pos = state.text.to_utf16_position(position);
843
844 let id = self.next_id();
845 {
846 let mut pending =
847 self.pending_requests.lock().unwrap_or_else(|e| e.into_inner());
848 pending.insert(id, LspRequestKind::Hover);
849 }
850
851 let msg = json!({
852 "jsonrpc": "2.0",
853 "id": id,
854 "method": "textDocument/hover",
855 "params": {
856 "textDocument": { "uri": document.uri },
857 "position": { "line": pos.line, "character": pos.character }
858 }
859 });
860 self.send_message(&msg);
861 }
862
863 fn request_completion(
864 &mut self,
865 document: &LspDocument,
866 position: LspPosition,
867 ) {
868 let docs = self.documents.lock().unwrap_or_else(|e| e.into_inner());
869 let Some(state) = docs.get(&document.uri) else { return };
870 let pos = state.text.to_utf16_position(position);
871
872 let id = self.next_id();
873 {
874 let mut pending =
875 self.pending_requests.lock().unwrap_or_else(|e| e.into_inner());
876 pending.insert(id, LspRequestKind::Completion);
877 }
878
879 let msg = json!({
880 "jsonrpc": "2.0",
881 "id": id,
882 "method": "textDocument/completion",
883 "params": {
884 "textDocument": { "uri": document.uri },
885 "position": { "line": pos.line, "character": pos.character },
886 "context": { "triggerKind": 1 }
887 }
888 });
889 self.send_message(&msg);
890 }
891
892 fn request_definition(
893 &mut self,
894 document: &LspDocument,
895 position: LspPosition,
896 ) {
897 let docs = self.documents.lock().unwrap_or_else(|e| e.into_inner());
898 let Some(state) = docs.get(&document.uri) else { return };
899 let pos = state.text.to_utf16_position(position);
900
901 let id = self.next_id();
902 {
903 let mut pending =
904 self.pending_requests.lock().unwrap_or_else(|e| e.into_inner());
905 pending.insert(id, LspRequestKind::Definition);
906 }
907
908 let msg = json!({
909 "jsonrpc": "2.0",
910 "id": id,
911 "method": "textDocument/definition",
912 "params": {
913 "textDocument": { "uri": document.uri },
914 "position": { "line": pos.line, "character": pos.character }
915 }
916 });
917 self.send_message(&msg);
918 }
919}
920
921#[cfg(test)]
922mod tests {
923 use super::*;
924
925 fn decode_sent(data: Vec<u8>) -> serde_json::Value {
927 let header_end = data
928 .windows(4)
929 .position(|w| w == b"\r\n\r\n")
930 .expect("missing header separator");
931 let body = &data[header_end + 4..];
932 serde_json::from_slice(body).expect("invalid JSON body")
933 }
934
935 #[test]
940 fn test_handle_server_request_work_done_progress_create() {
941 let (tx, rx) = mpsc::channel::<Vec<u8>>();
942 handle_server_request(42, METHOD_WORK_DONE_PROGRESS_CREATE, &tx);
943
944 let bytes = rx.try_recv().expect("expected a response on the channel");
945 let value = decode_sent(bytes);
946 assert_eq!(value["id"], 42);
947 assert_eq!(value["jsonrpc"], "2.0");
948 assert!(value["result"].is_null());
949 }
950
951 #[test]
952 fn test_handle_server_request_unknown_method_ignored() {
953 let (tx, rx) = mpsc::channel::<Vec<u8>>();
954 handle_server_request(1, "unknown/method", &tx);
955 assert!(
956 rx.try_recv().is_err(),
957 "unknown methods must not send a reply"
958 );
959 }
960
961 #[test]
966 fn test_handle_client_response_hover() {
967 let (events_tx, events_rx) = mpsc::channel::<LspEvent>();
968 let pending = Arc::new(Mutex::new(HashMap::new()));
969 pending.lock().unwrap().insert(1u64, LspRequestKind::Hover);
970
971 let value = serde_json::json!({
972 "id": 1,
973 "result": { "contents": { "value": "hover info" } }
974 });
975 handle_client_response(1, &value, &pending, &events_tx);
976
977 match events_rx.try_recv().expect("expected a Hover event") {
978 LspEvent::Hover { text } => assert_eq!(text, "hover info"),
979 _ => panic!("expected LspEvent::Hover"),
980 }
981 assert!(pending.lock().unwrap().is_empty());
982 }
983
984 #[test]
985 fn test_handle_client_response_completion() {
986 let (events_tx, events_rx) = mpsc::channel::<LspEvent>();
987 let pending = Arc::new(Mutex::new(HashMap::new()));
988 pending.lock().unwrap().insert(2u64, LspRequestKind::Completion);
989
990 let value = serde_json::json!({
991 "id": 2,
992 "result": { "items": [{ "label": "foo" }, { "label": "bar" }] }
993 });
994 handle_client_response(2, &value, &pending, &events_tx);
995
996 match events_rx.try_recv().expect("expected a Completion event") {
997 LspEvent::Completion { items } => {
998 assert_eq!(items, vec!["foo", "bar"]);
999 }
1000 _ => panic!("expected LspEvent::Completion"),
1001 }
1002 }
1003
1004 #[test]
1005 fn test_handle_client_response_definition() {
1006 let (events_tx, events_rx) = mpsc::channel::<LspEvent>();
1007 let pending = Arc::new(Mutex::new(HashMap::new()));
1008 pending.lock().unwrap().insert(3u64, LspRequestKind::Definition);
1009
1010 let value = serde_json::json!({
1011 "id": 3,
1012 "result": {
1013 "uri": "file:///foo/bar.rs",
1014 "range": {
1015 "start": { "line": 0, "character": 0 },
1016 "end": { "line": 0, "character": 5 }
1017 }
1018 }
1019 });
1020 handle_client_response(3, &value, &pending, &events_tx);
1021
1022 match events_rx.try_recv().expect("expected a Definition event") {
1023 LspEvent::Definition { uri, .. } => {
1024 assert_eq!(uri, "file:///foo/bar.rs");
1025 }
1026 _ => panic!("expected LspEvent::Definition"),
1027 }
1028 }
1029
1030 #[test]
1031 fn test_handle_client_response_unknown_id_ignored() {
1032 let (events_tx, events_rx) = mpsc::channel::<LspEvent>();
1033 let pending = Arc::new(Mutex::new(HashMap::new()));
1034
1035 let value = serde_json::json!({ "id": 99, "result": null });
1036 handle_client_response(99, &value, &pending, &events_tx);
1037 assert!(
1038 events_rx.try_recv().is_err(),
1039 "unknown IDs must not emit events"
1040 );
1041 }
1042
1043 #[test]
1048 fn test_handle_server_notification_progress_done() {
1049 let (events_tx, events_rx) = mpsc::channel::<LspEvent>();
1050 let params = serde_json::json!({
1051 "token": "my-token",
1052 "value": {
1053 "kind": "end",
1054 "title": "Indexing",
1055 "message": "done"
1056 }
1057 });
1058
1059 handle_server_notification(
1060 METHOD_PROGRESS,
1061 ¶ms,
1062 &events_tx,
1063 "lua-ls",
1064 );
1065
1066 match events_rx.try_recv().expect("expected a Progress event") {
1067 LspEvent::Progress { token, done, server_key, .. } => {
1068 assert_eq!(token, "my-token");
1069 assert!(done);
1070 assert_eq!(server_key, "lua-ls");
1071 }
1072 _ => panic!("expected LspEvent::Progress"),
1073 }
1074 }
1075
1076 #[test]
1077 fn test_handle_server_notification_progress_not_done() {
1078 let (events_tx, events_rx) = mpsc::channel::<LspEvent>();
1079 let params = serde_json::json!({
1080 "token": "tok",
1081 "value": { "kind": "report", "title": "Building" }
1082 });
1083
1084 handle_server_notification(
1085 METHOD_PROGRESS,
1086 ¶ms,
1087 &events_tx,
1088 "rust-analyzer",
1089 );
1090
1091 match events_rx.try_recv().expect("expected a Progress event") {
1092 LspEvent::Progress { done, .. } => assert!(!done),
1093 _ => panic!("expected LspEvent::Progress"),
1094 }
1095 }
1096
1097 #[test]
1098 fn test_handle_server_notification_unknown_method_ignored() {
1099 let (events_tx, events_rx) = mpsc::channel::<LspEvent>();
1100 let params = serde_json::json!({});
1101 handle_server_notification(
1102 "$/somethingElse",
1103 ¶ms,
1104 &events_tx,
1105 "server",
1106 );
1107 assert!(events_rx.try_recv().is_err());
1108 }
1109}