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