1use anyhow::{anyhow, Result};
2use log::info;
3use serde_json::{json, Value};
4use std::{
5 collections::{hash_map::DefaultHasher, HashMap, HashSet},
6 hash::{Hash, Hasher},
7 path::PathBuf,
8 process::{ExitStatus, Stdio},
9 sync::Arc,
10 time::Duration,
11};
12use tokio::{
13 io::BufWriter,
14 process::{Child, Command},
15 sync::{oneshot, watch, Mutex},
16 task::JoinHandle,
17};
18
19use crate::{
20 config::{
21 DOCUMENT_OPEN_DELAY_MILLIS, GRACEFUL_SHUTDOWN_TIMEOUT_SECS, LSP_REQUEST_TIMEOUT_SECS,
22 },
23 protocol::lsp::LSPRequest,
24 uri,
25};
26
27use super::connection::{send_message, Connection, Diagnostics, Flycheck, Outgoing, Pending};
28
29pub struct RustAnalyzerClient {
30 pub(super) process: Option<Child>,
31 pub(super) request_id: Arc<Mutex<u64>>,
32 pub(super) workspace_root: PathBuf,
33 pub(super) stdin: Option<Outgoing<tokio::process::ChildStdin>>,
34 pub(super) pending_requests: Pending,
35 pub(super) initialized: bool,
36 pub(super) open_documents: Arc<Mutex<HashMap<String, OpenDocument>>>,
38 pub(super) diagnostics: Diagnostics,
39 pub(super) quiescent: watch::Sender<bool>,
42 pub(super) flycheck: watch::Sender<Flycheck>,
44 pub(super) gave_up_on_checks: bool,
49 pub(super) saved_documents: HashSet<String>,
51 pub(super) reader: Option<JoinHandle<()>>,
53 pub(super) settings: Value,
56}
57
58impl RustAnalyzerClient {
59 pub fn new(workspace_root: PathBuf, settings: Value) -> Self {
60 let workspace_root = uri::absolute(&workspace_root);
61
62 Self {
63 process: None,
64 request_id: Arc::new(Mutex::new(1)),
65 workspace_root,
66 stdin: None,
67 pending_requests: Arc::new(Mutex::new(HashMap::new())),
68 initialized: false,
69 open_documents: Arc::new(Mutex::new(HashMap::new())),
70 diagnostics: Arc::new(Mutex::new(HashMap::new())),
71 quiescent: watch::channel(false).0,
72 flycheck: watch::channel(Flycheck::default()).0,
73 gave_up_on_checks: false,
74 saved_documents: HashSet::new(),
75 reader: None,
76 settings,
77 }
78 }
79
80 pub async fn start(&mut self) -> Result<()> {
81 info!(
82 "Starting rust-analyzer process in workspace: {}",
83 self.workspace_root.display()
84 );
85 info!("rust-analyzer settings: {}", self.settings);
88
89 self.diagnostics.lock().await.clear();
91
92 let rust_analyzer_path = find_rust_analyzer()?;
94 info!("Using rust-analyzer at: {}", rust_analyzer_path.display());
95
96 let mut cmd = Command::new(rust_analyzer_path);
97 cmd.current_dir(&self.workspace_root)
98 .stdin(Stdio::piped())
99 .stdout(Stdio::piped())
100 .stderr(Stdio::piped())
101 .kill_on_drop(true);
103
104 if let Ok(cache_home) = std::env::var("XDG_CACHE_HOME") {
106 cmd.env("XDG_CACHE_HOME", cache_home);
107 }
108 if let Ok(target_dir) = std::env::var("CARGO_TARGET_DIR") {
109 cmd.env("CARGO_TARGET_DIR", target_dir);
110 }
111 if let Ok(tmpdir) = std::env::var("TMPDIR") {
112 cmd.env("TMPDIR", tmpdir);
113 }
114
115 let mut child = cmd
116 .spawn()
117 .map_err(|e| anyhow!("Failed to start rust-analyzer: {}", e))?;
118
119 let stdin = child
120 .stdin
121 .take()
122 .ok_or_else(|| anyhow!("Failed to get stdin"))?;
123 let stdout = child
124 .stdout
125 .take()
126 .ok_or_else(|| anyhow!("Failed to get stdout"))?;
127 let stderr = child
128 .stderr
129 .take()
130 .ok_or_else(|| anyhow!("Failed to get stderr"))?;
131
132 let stdin = Arc::new(Mutex::new(BufWriter::new(stdin)));
133 self.stdin = Some(Arc::clone(&stdin));
134
135 self.pending_requests = Arc::new(Mutex::new(HashMap::new()));
140 self.reader = Some(super::connection::start_handlers(
141 stdout,
142 stderr,
143 Connection {
144 pending_requests: Arc::clone(&self.pending_requests),
145 diagnostics: Arc::clone(&self.diagnostics),
146 quiescent: self.quiescent.clone(),
147 flycheck: self.flycheck.clone(),
148 outgoing: stdin,
149 settings: self.settings.clone(),
150 },
151 ));
152
153 self.process = Some(child);
154
155 self.initialize().await?;
157 self.initialized = true;
158
159 let config_params = json!({ "settings": { "rust-analyzer": self.settings } });
162 let _ = self
163 .send_notification("workspace/didChangeConfiguration", Some(config_params))
164 .await;
165
166 info!("rust-analyzer client started and initialized");
167 Ok(())
168 }
169
170 pub(super) async fn send_notification(
171 &mut self,
172 method: &str,
173 params: Option<Value>,
174 ) -> Result<()> {
175 let notification = json!({
176 "jsonrpc": "2.0",
177 "method": method,
178 "params": params.unwrap_or(json!({}))
179 });
180
181 info!("Sending LSP notification: {}", method);
182
183 let Some(stdin) = &self.stdin else {
184 return Err(anyhow!("No stdin available"));
185 };
186
187 send_message(stdin, ¬ification).await
188 }
189
190 pub(super) async fn send_request(
196 &mut self,
197 method: &str,
198 params: Option<Value>,
199 ) -> Result<Value> {
200 for attempt in 1..REQUEST_ATTEMPTS {
201 let answer = self.send_request_once(method, params.clone()).await;
202 let Err(e) = &answer else {
203 return answer;
204 };
205 if !superseded(e) {
206 return answer;
207 }
208
209 info!(
210 "Asking rust-analyzer for {} again ({}): {}",
211 method, attempt, e
212 );
213 }
214
215 self.send_request_once(method, params).await
216 }
217
218 async fn send_request_once(&mut self, method: &str, params: Option<Value>) -> Result<Value> {
219 let mut request_id_lock = self.request_id.lock().await;
220 let id = *request_id_lock;
221 *request_id_lock += 1;
222 drop(request_id_lock);
223
224 let request = LSPRequest {
225 jsonrpc: "2.0".to_string(),
226 id,
227 method: method.to_string(),
228 params: params.clone(),
229 };
230
231 let request = serde_json::to_value(request)?;
232
233 info!("Sending LSP request: {} with params: {:?}", method, params);
234
235 let (tx, rx) = oneshot::channel();
239 let pending_requests = self.pending_requests.clone();
240 pending_requests.lock().await.insert(id, tx);
241
242 let Some(stdin) = &self.stdin else {
243 pending_requests.lock().await.remove(&id);
244 return Err(anyhow!("No stdin available"));
245 };
246
247 if let Err(e) = send_message(stdin, &request).await {
248 pending_requests.lock().await.remove(&id);
249 return Err(e);
250 }
251
252 match tokio::time::timeout(Duration::from_secs(LSP_REQUEST_TIMEOUT_SECS), rx).await {
255 Ok(Ok(answer)) => answer.map_err(|message| anyhow!("{message}")),
256 Ok(Err(_)) => Err(anyhow!("rust-analyzer exited before responding")),
257 Err(_) => {
258 pending_requests.lock().await.remove(&id);
260 Err(anyhow!("Request timeout"))
261 }
262 }
263 }
264
265 async fn initialize(&mut self) -> Result<()> {
266 let init_params = json!({
267 "processId": std::process::id(),
268 "rootUri": uri::path_to_uri(&self.workspace_root)?,
269 "initializationOptions": self.settings,
270 "capabilities": {
271 "textDocument": {
272 "hover": {
273 "contentFormat": ["markdown", "plaintext"]
274 },
275 "completion": {
276 "completionItem": {
277 "snippetSupport": true
278 }
279 },
280 "definition": {
281 "linkSupport": true
282 },
283 "references": {},
284 "documentSymbol": {},
285 "codeAction": {
286 "codeActionLiteralSupport": {
287 "codeActionKind": {
288 "valueSet": [
289 "quickfix",
290 "refactor",
291 "refactor.extract",
292 "refactor.inline",
293 "refactor.rewrite",
294 "source",
295 "source.organizeImports"
296 ]
297 }
298 },
299 "resolveSupport": {
300 "properties": ["edit"]
301 }
302 },
303 "publishDiagnostics": {
304 "relatedInformation": true,
305 "tagSupport": {
306 "valueSet": [1, 2]
307 }
308 },
309 "formatting": {},
310 "rename": {
311 "dynamicRegistration": false,
312 "prepareSupport": true
313 }
314 },
315 "workspace": {
316 "didChangeConfiguration": {
317 "dynamicRegistration": false
318 },
319 "workspaceEdit": {
322 "documentChanges": true,
323 "resourceOperations": ["create", "rename", "delete"],
324 "failureHandling": "abort"
325 }
326 },
327 "general": {
330 "positionEncodings": ["utf-16"]
331 },
332 "window": {
335 "workDoneProgress": true
336 },
337 "experimental": {
340 "serverStatusNotification": true
341 }
342 }
343 });
344
345 self.send_request("initialize", Some(init_params)).await?;
346 self.send_notification("initialized", Some(json!({})))
347 .await?;
348
349 self.send_request("rust-analyzer/reloadWorkspace", None)
351 .await
352 .ok();
353
354 Ok(())
355 }
356
357 pub async fn open_document(&mut self, uri: &str, content: &str) -> Result<()> {
365 let key = uri::normalize(uri);
366 let content_hash = hash(content);
367 let known = self
368 .open_documents
369 .lock()
370 .await
371 .get(&key)
372 .map(|document| (document.version, document.content_hash));
373
374 match known {
375 None => {
376 info!("Opening document: {}", uri);
377 let params = json!({
378 "textDocument": {
379 "uri": uri,
380 "languageId": "rust",
381 "version": FIRST_DOCUMENT_VERSION,
382 "text": content
383 }
384 });
385 self.send_notification("textDocument/didOpen", Some(params))
386 .await?;
387 self.open_documents.lock().await.insert(
388 key.clone(),
389 OpenDocument {
390 version: FIRST_DOCUMENT_VERSION,
391 content_hash,
392 },
393 );
394 }
395 Some((_, known_hash)) if known_hash == content_hash => {
396 info!("Document already open and unchanged: {}", uri);
397 }
398 Some((version, _)) => {
399 let version = version + 1;
403 info!("Document changed, sending version {} of {}", version, uri);
404 let params = json!({
405 "textDocument": {
406 "uri": uri,
407 "version": version
408 },
409 "contentChanges": [{ "text": content }]
410 });
411 self.send_notification("textDocument/didChange", Some(params))
412 .await?;
413 self.open_documents.lock().await.insert(
414 key.clone(),
415 OpenDocument {
416 version,
417 content_hash,
418 },
419 );
420
421 self.diagnostics.lock().await.remove(&key);
425 self.saved_documents.remove(&key);
426 }
427 }
428
429 if self.saved_documents.contains(&key) {
437 return Ok(());
438 }
439 if !*self.quiescent.borrow() {
440 info!("rust-analyzer is busy, holding back didSave for {}", uri);
441 return Ok(());
442 }
443
444 self.diagnostics.lock().await.remove(&key);
447 let save_params = json!({
448 "textDocument": {
449 "uri": uri
450 }
451 });
452 self.send_notification("textDocument/didSave", Some(save_params))
453 .await?;
454 self.saved_documents.insert(key);
455
456 tokio::time::sleep(Duration::from_millis(DOCUMENT_OPEN_DELAY_MILLIS)).await;
458
459 Ok(())
460 }
461
462 pub async fn close_document(&mut self, uri: &str) -> Result<()> {
467 let key = uri::normalize(uri);
468 if self.open_documents.lock().await.remove(&key).is_none() {
469 return Ok(());
470 }
471
472 info!("Closing document: {}", uri);
473 let params = json!({ "textDocument": { "uri": uri } });
474 self.send_notification("textDocument/didClose", Some(params))
475 .await?;
476
477 self.saved_documents.remove(&key);
478 self.diagnostics.lock().await.remove(&key);
479
480 Ok(())
481 }
482
483 pub async fn open_document_uris(&self) -> Vec<String> {
485 self.open_documents.lock().await.keys().cloned().collect()
486 }
487
488 pub async fn shutdown(&mut self) -> Result<()> {
490 if self.initialized {
491 let handshake = async {
493 let _ = self.send_request("shutdown", None).await;
494 let _ = self.send_notification("exit", None).await;
495 };
496 let timeout = Duration::from_secs(GRACEFUL_SHUTDOWN_TIMEOUT_SECS);
497 if tokio::time::timeout(timeout, handshake).await.is_err() {
498 info!("Graceful shutdown timed out");
499 }
500 }
501
502 self.force_kill().await;
503 Ok(())
504 }
505
506 pub async fn force_kill(&mut self) {
511 if let Some(mut process) = self.process.take() {
512 let _ = process.kill().await;
514 let _ = process.wait().await;
515 }
516
517 self.open_documents.lock().await.clear();
519 self.saved_documents.clear();
520 self.diagnostics.lock().await.clear();
521 self.flycheck.send_replace(Flycheck::default());
522 self.gave_up_on_checks = false;
523 self.initialized = false;
524 }
525
526 pub fn is_gone(&self) -> bool {
528 self.reader.as_ref().is_some_and(JoinHandle::is_finished)
529 }
530
531 pub fn exit_status(&mut self) -> Option<ExitStatus> {
533 self.process.as_mut()?.try_wait().ok().flatten()
534 }
535}
536
537pub(super) struct OpenDocument {
540 version: u64,
543 content_hash: u64,
547}
548
549const REQUEST_ATTEMPTS: u32 = 3;
551
552fn superseded(error: &anyhow::Error) -> bool {
554 error
555 .to_string()
556 .to_lowercase()
557 .contains("content modified")
558}
559
560const FIRST_DOCUMENT_VERSION: u64 = 1;
562
563fn hash(content: &str) -> u64 {
564 let mut hasher = DefaultHasher::new();
565 content.hash(&mut hasher);
566 hasher.finish()
567}
568
569fn find_rust_analyzer() -> Result<PathBuf> {
570 which::which("rust-analyzer").or_else(|_| {
571 let home = std::env::var("HOME").unwrap_or_else(|_| String::from("~"));
573 let cargo_bin = PathBuf::from(home).join(".cargo/bin/rust-analyzer");
574 if cargo_bin.exists() {
575 Ok(cargo_bin)
576 } else {
577 which::which("rust-analyzer")
578 }
579 })
580 .map_err(|e| {
581 anyhow!(
582 "Failed to find rust-analyzer in PATH or ~/.cargo/bin: {}. Please ensure rust-analyzer is installed.",
583 e
584 )
585 })
586}
587
588#[cfg(test)]
589mod tests {
590 use super::*;
591
592 #[cfg(unix)]
595 use tokio::io::AsyncReadExt;
596
597 #[cfg(unix)]
598 const URI: &str = "file:///tmp/lib.rs";
599
600 #[cfg(unix)]
602 const FLYCHECK: &str = "rust-analyzer/flycheck/0";
603
604 #[cfg(unix)]
605 #[tokio::test]
606 async fn did_save_is_held_back_while_rust_analyzer_is_busy() {
607 let (mut client, mut child) = client_with_fake_stdin();
608
609 open(&mut client).await;
610 open(&mut client).await;
611
612 let sent = written(&mut client, &mut child).await;
613 assert_eq!(sent.matches("textDocument/didOpen").count(), 1, "{sent}");
614 assert_eq!(sent.matches("textDocument/didSave").count(), 0, "{sent}");
615 }
616
617 #[cfg(unix)]
618 #[tokio::test]
619 async fn held_back_did_save_is_sent_once_rust_analyzer_is_quiescent() {
620 let (mut client, mut child) = client_with_fake_stdin();
621
622 open(&mut client).await;
623 client.quiescent.send_replace(true);
624 open(&mut client).await;
625 open(&mut client).await;
626
627 let sent = written(&mut client, &mut child).await;
628 assert_eq!(sent.matches("textDocument/didOpen").count(), 1, "{sent}");
629 assert_eq!(sent.matches("textDocument/didSave").count(), 1, "{sent}");
630 }
631
632 #[cfg(unix)]
633 #[tokio::test]
634 async fn did_save_follows_did_open_while_rust_analyzer_is_quiescent() {
635 let (mut client, mut child) = client_with_fake_stdin();
636 client.quiescent.send_replace(true);
637
638 open(&mut client).await;
639 open(&mut client).await;
640
641 let sent = written(&mut client, &mut child).await;
642 assert_eq!(sent.matches("textDocument/didOpen").count(), 1, "{sent}");
643 assert_eq!(sent.matches("textDocument/didSave").count(), 1, "{sent}");
644 }
645
646 #[cfg(unix)]
647 #[tokio::test]
648 async fn an_edited_document_is_sent_as_a_change() {
649 let (mut client, mut child) = client_with_fake_stdin();
650 client.quiescent.send_replace(true);
651
652 client.open_document(URI, "fn main() {}").await.unwrap();
653 client
654 .open_document(URI, "fn main() { let x = 1; }")
655 .await
656 .unwrap();
657
658 let sent = written(&mut client, &mut child).await;
659 assert_eq!(sent.matches("textDocument/didOpen").count(), 1, "{sent}");
660 assert_eq!(sent.matches("textDocument/didChange").count(), 1, "{sent}");
661 assert!(sent.contains(r#"let x = 1;"#), "{sent}");
662 assert!(sent.contains(r#""version":2"#), "{sent}");
664 }
665
666 #[cfg(unix)]
667 #[tokio::test]
668 async fn a_document_that_did_not_change_is_not_sent_again() {
669 let (mut client, mut child) = client_with_fake_stdin();
672 client.quiescent.send_replace(true);
673
674 open(&mut client).await;
675 open(&mut client).await;
676
677 let sent = written(&mut client, &mut child).await;
678 assert_eq!(sent.matches("textDocument/didOpen").count(), 1, "{sent}");
679 assert_eq!(sent.matches("textDocument/didChange").count(), 0, "{sent}");
680 }
681
682 #[cfg(unix)]
683 #[tokio::test]
684 async fn every_edit_gets_the_next_version() {
685 let (mut client, mut child) = client_with_fake_stdin();
686 client.quiescent.send_replace(true);
687
688 for content in ["fn main() {}", "fn main() { 1; }", "fn main() { 2; }"] {
689 client.open_document(URI, content).await.unwrap();
690 }
691
692 let sent = written(&mut client, &mut child).await;
693 assert!(sent.contains(r#""version":1"#), "{sent}");
694 assert!(sent.contains(r#""version":2"#), "{sent}");
695 assert!(sent.contains(r#""version":3"#), "{sent}");
696 }
697
698 #[cfg(unix)]
699 #[tokio::test]
700 async fn an_edit_drops_what_was_reported_about_the_old_content() {
701 let (mut client, mut child) = client_with_fake_stdin();
702 client.quiescent.send_replace(true);
703
704 client.open_document(URI, "fn main() {}").await.unwrap();
705 client
706 .diagnostics
707 .lock()
708 .await
709 .insert(URI.to_string(), vec![json!({ "message": "stale" })]);
710 client
711 .open_document(URI, "fn main() { let x = 1; }")
712 .await
713 .unwrap();
714
715 assert!(!client.diagnostics.lock().await.contains_key(URI));
716 let sent = written(&mut client, &mut child).await;
718 assert_eq!(sent.matches("textDocument/didSave").count(), 2, "{sent}");
719 }
720
721 #[cfg(unix)]
722 #[tokio::test(start_paused = true)]
723 async fn diagnostics_are_asked_for_and_waited_on() {
724 let (mut client, mut child) = client_with_fake_stdin();
725 client.quiescent.send_replace(true);
726
727 let flycheck = client.flycheck.clone();
730 let diagnostics = Arc::clone(&client.diagnostics);
731 tokio::spawn(async move {
732 tokio::time::sleep(Duration::from_millis(50)).await;
733 flycheck.send_modify(|flycheck| flycheck.begin(FLYCHECK));
734 diagnostics.lock().await.insert(
735 URI.to_string(),
736 vec![json!({ "message": "mismatched types" })],
737 );
738 flycheck.send_modify(|flycheck| flycheck.end(FLYCHECK));
739 });
740
741 let fresh = client.fresh_diagnostics(URI).await.unwrap();
742
743 assert!(fresh.complete);
744 assert_eq!(fresh.items[0]["message"], "mismatched types");
745 let sent = written(&mut client, &mut child).await;
746 assert!(sent.contains("rust-analyzer/runFlycheck"), "{sent}");
747 assert!(sent.contains("textDocument/diagnostic"), "{sent}");
749 }
750
751 #[cfg(unix)]
752 #[tokio::test(start_paused = true)]
753 async fn diagnostics_come_back_marked_when_no_check_runs() {
754 let (mut client, _child) = client_with_fake_stdin();
757 client.quiescent.send_replace(true);
758
759 let fresh = client.fresh_diagnostics(URI).await.unwrap();
760
761 assert!(!fresh.complete);
762 assert_eq!(fresh.items, json!([]));
763 assert!(client.gave_up_on_checks);
765 }
766
767 #[cfg(unix)]
768 #[tokio::test(start_paused = true)]
769 async fn a_check_reported_late_puts_the_waiting_back() {
770 let (mut client, _child) = client_with_fake_stdin();
773 client.quiescent.send_replace(true);
774 client.fresh_diagnostics(URI).await.unwrap();
775 assert!(client.gave_up_on_checks);
776
777 client.flycheck.send_modify(|flycheck| {
778 flycheck.begin(FLYCHECK);
779 flycheck.end(FLYCHECK);
780 });
781
782 let flycheck = client.flycheck.clone();
784 tokio::spawn(async move {
785 tokio::time::sleep(Duration::from_millis(50)).await;
786 flycheck.send_modify(|flycheck| flycheck.begin(FLYCHECK));
787 flycheck.send_modify(|flycheck| flycheck.end(FLYCHECK));
788 });
789
790 let fresh = client.fresh_diagnostics(URI).await.unwrap();
791
792 assert!(fresh.complete);
793 }
794
795 #[cfg(unix)]
796 #[tokio::test(start_paused = true)]
797 async fn diagnostics_from_a_workspace_still_loading_are_marked() {
798 let (mut client, _child) = client_with_fake_stdin();
801 let flycheck = client.flycheck.clone();
802 tokio::spawn(async move {
803 tokio::time::sleep(Duration::from_millis(50)).await;
804 flycheck.send_modify(|flycheck| flycheck.begin(FLYCHECK));
805 flycheck.send_modify(|flycheck| flycheck.end(FLYCHECK));
806 });
807
808 let fresh = client.fresh_diagnostics(URI).await.unwrap();
809
810 assert!(!fresh.complete);
811 }
812
813 #[cfg(unix)]
814 #[tokio::test]
815 async fn exit_status_reflects_whether_rust_analyzer_is_alive() {
816 let mut client = RustAnalyzerClient::new(PathBuf::from("."), json!({}));
817 let mut child = Command::new("sh")
819 .args(["-c", "read _; exit 3"])
820 .stdin(Stdio::piped())
821 .spawn()
822 .unwrap();
823 let stdin = child.stdin.take();
824 client.process = Some(child);
825
826 assert!(client.exit_status().is_none());
827
828 drop(stdin);
829 client.process.as_mut().unwrap().wait().await.unwrap();
830 assert_eq!(
831 client.exit_status().and_then(|status| status.code()),
832 Some(3)
833 );
834 }
835
836 #[tokio::test]
837 async fn is_gone_once_rust_analyzer_closes_its_stdout() {
838 let mut client = RustAnalyzerClient::new(PathBuf::from("."), json!({}));
839 let (stdout, rust_analyzer) = tokio::io::duplex(64);
840 client.reader = Some(super::super::connection::start_handlers(
841 stdout,
842 tokio::io::empty(),
843 Connection {
844 pending_requests: Arc::clone(&client.pending_requests),
845 diagnostics: Arc::clone(&client.diagnostics),
846 quiescent: client.quiescent.clone(),
847 flycheck: client.flycheck.clone(),
848 outgoing: Arc::new(Mutex::new(BufWriter::new(tokio::io::sink()))),
849 settings: json!({}),
850 },
851 ));
852 tokio::task::yield_now().await;
853 assert!(!client.is_gone());
854
855 drop(rust_analyzer);
856 tokio::time::timeout(Duration::from_secs(5), client.reader.as_mut().unwrap())
857 .await
858 .expect("reader must finish once stdout closes")
859 .unwrap();
860 assert!(client.is_gone());
861 }
862
863 #[tokio::test]
864 async fn workspace_diagnostics_fails_once_rust_analyzer_is_gone() {
865 let mut client = RustAnalyzerClient::new(PathBuf::from("."), json!({}));
866 let mut reader = tokio::spawn(async {});
867 (&mut reader).await.unwrap();
868 client.reader = Some(reader);
869
870 assert!(client.workspace_diagnostics().await.is_err());
872 }
873
874 #[cfg(unix)]
878 fn client_with_fake_stdin() -> (RustAnalyzerClient, Child) {
879 let mut child = Command::new("cat")
880 .stdin(Stdio::piped())
881 .stdout(Stdio::piped())
882 .spawn()
883 .unwrap();
884 let mut client = RustAnalyzerClient::new(PathBuf::from("."), json!({}));
885 client.stdin = Some(Arc::new(Mutex::new(BufWriter::new(
886 child.stdin.take().unwrap(),
887 ))));
888 (client, child)
889 }
890
891 #[cfg(unix)]
892 async fn open(client: &mut RustAnalyzerClient) {
893 client.open_document(URI, "fn main() {}").await.unwrap();
894 }
895
896 #[cfg(unix)]
898 async fn written(client: &mut RustAnalyzerClient, child: &mut Child) -> String {
899 client.stdin.take();
900 let mut output = String::new();
901 child
902 .stdout
903 .take()
904 .unwrap()
905 .read_to_string(&mut output)
906 .await
907 .unwrap();
908 child.wait().await.unwrap();
909 output
910 }
911}