1use anyhow::Result;
2use log::{info, warn};
3use serde_json::{json, Value};
4use std::time::Duration;
5use tokio::sync::watch;
6
7use super::{client::RustAnalyzerClient, connection::Flycheck};
8use crate::{
9 config::{
10 DOCUMENT_OPEN_DELAY_MILLIS, FLYCHECK_REQUEST_ATTEMPTS, FLYCHECK_START_TIMEOUT_SECS,
11 FLYCHECK_TIMEOUT_SECS, WORKSPACE_LOAD_TIMEOUT_SECS,
12 },
13 uri,
14};
15
16const ANALYSIS_SOURCE: &str = "rust-analyzer";
19
20pub struct FreshDiagnostics {
22 pub items: Value,
23 pub complete: bool,
26}
27
28async fn wait_for(
30 progress: &mut watch::Receiver<Flycheck>,
31 timeout: Duration,
32 condition: impl Fn(&Flycheck) -> bool,
33) -> bool {
34 let wait = async {
35 loop {
36 if condition(&progress.borrow_and_update()) {
37 return true;
38 }
39 if progress.changed().await.is_err() {
41 return false;
42 }
43 }
44 };
45
46 tokio::time::timeout(timeout, wait).await.unwrap_or(false)
47}
48
49impl RustAnalyzerClient {
50 pub async fn hover(&mut self, uri: &str, line: u32, character: u32) -> Result<Value> {
51 let params = json!({
52 "textDocument": { "uri": uri },
53 "position": { "line": line, "character": character }
54 });
55
56 self.send_request("textDocument/hover", Some(params)).await
57 }
58
59 pub async fn definition(&mut self, uri: &str, line: u32, character: u32) -> Result<Value> {
60 let params = json!({
61 "textDocument": { "uri": uri },
62 "position": { "line": line, "character": character }
63 });
64
65 self.send_request("textDocument/definition", Some(params))
66 .await
67 }
68
69 pub async fn references(&mut self, uri: &str, line: u32, character: u32) -> Result<Value> {
70 let params = json!({
71 "textDocument": { "uri": uri },
72 "position": { "line": line, "character": character },
73 "context": { "includeDeclaration": true }
74 });
75
76 self.send_request("textDocument/references", Some(params))
77 .await
78 }
79
80 pub async fn completion(&mut self, uri: &str, line: u32, character: u32) -> Result<Value> {
81 let params = json!({
82 "textDocument": { "uri": uri },
83 "position": { "line": line, "character": character }
84 });
85
86 self.send_request("textDocument/completion", Some(params))
87 .await
88 }
89
90 pub async fn rename(
92 &mut self,
93 uri: &str,
94 line: u32,
95 character: u32,
96 new_name: &str,
97 ) -> Result<Value> {
98 let params = json!({
99 "textDocument": { "uri": uri },
100 "position": { "line": line, "character": character },
101 "newName": new_name
102 });
103
104 self.send_request("textDocument/rename", Some(params)).await
105 }
106
107 pub async fn prepare_rename(&mut self, uri: &str, line: u32, character: u32) -> Result<Value> {
113 let params = json!({
114 "textDocument": { "uri": uri },
115 "position": { "line": line, "character": character }
116 });
117
118 self.send_request("textDocument/prepareRename", Some(params))
119 .await
120 }
121
122 pub async fn document_symbols(&mut self, uri: &str) -> Result<Value> {
123 let params = json!({
124 "textDocument": { "uri": uri }
125 });
126
127 self.send_request("textDocument/documentSymbol", Some(params))
128 .await
129 }
130
131 pub async fn formatting(&mut self, uri: &str) -> Result<Value> {
132 let params = json!({
133 "textDocument": { "uri": uri },
134 "options": {
135 "tabSize": 4,
136 "insertSpaces": true
137 }
138 });
139
140 self.send_request("textDocument/formatting", Some(params))
141 .await
142 }
143
144 pub async fn diagnostics(&mut self, uri: &str) -> Result<Value> {
145 let key = uri::normalize(uri);
147 let diag_lock = self.diagnostics.lock().await;
148 info!("Looking for diagnostics for URI: {}", key);
149 info!(
150 "Available URIs with diagnostics: {:?}",
151 diag_lock.keys().collect::<Vec<_>>()
152 );
153 if let Some(diagnostics) = diag_lock.get(&key) {
154 info!("Found {} stored diagnostics for {}", diagnostics.len(), uri);
155 return Ok(json!(diagnostics));
156 }
157 drop(diag_lock);
158
159 info!("No stored diagnostics for {}, trying pull model", uri);
160 let params = json!({
162 "textDocument": { "uri": uri }
163 });
164
165 let response = self
166 .send_request("textDocument/diagnostic", Some(params))
167 .await?;
168
169 if let Some(items) = response.get("items") {
171 Ok(items.clone())
172 } else {
173 Ok(json!([]))
174 }
175 }
176
177 pub async fn fresh_diagnostics(&mut self, uri: &str) -> Result<FreshDiagnostics> {
184 let loaded = self
188 .wait_until_loaded(Duration::from_secs(WORKSPACE_LOAD_TIMEOUT_SECS))
189 .await;
190 if !loaded {
191 warn!("rust-analyzer is still loading the workspace; reporting on what it has");
192 }
193
194 let before = self.flycheck.borrow().clone();
195 let mut progress = self.flycheck.subscribe();
196
197 self.diagnostics.lock().await.clear();
200
201 let params = json!({ "textDocument": { "uri": uri } });
202 self.send_notification("rust-analyzer/runFlycheck", Some(params.clone()))
203 .await?;
204
205 if self.gave_up_on_checks && before.never_ran_one() {
210 return Ok(FreshDiagnostics {
211 items: self.current_diagnostics(uri).await?,
212 complete: false,
213 });
214 }
215
216 let mut started = false;
222 for attempt in 1..=FLYCHECK_REQUEST_ATTEMPTS {
223 started = wait_for(
224 &mut progress,
225 Duration::from_secs(FLYCHECK_START_TIMEOUT_SECS),
226 |flycheck| flycheck.started_since(&before),
227 )
228 .await;
229 if started || attempt == FLYCHECK_REQUEST_ATTEMPTS {
230 break;
231 }
232
233 info!("Asking for a cargo check of {} again", uri);
234 self.send_notification("rust-analyzer/runFlycheck", Some(params.clone()))
235 .await?;
236 }
237 if !started {
238 info!("No cargo check started for {}, reporting what we have", uri);
239 if self.flycheck.borrow().never_ran_one() {
243 warn!("rust-analyzer has yet to report a cargo check; not waiting for one");
244 self.gave_up_on_checks = true;
245 }
246
247 return Ok(FreshDiagnostics {
248 items: self.current_diagnostics(uri).await?,
249 complete: false,
250 });
251 }
252
253 let finished = wait_for(
254 &mut progress,
255 Duration::from_secs(FLYCHECK_TIMEOUT_SECS),
256 |flycheck| flycheck.caught_up_with(&before),
257 )
258 .await;
259 if !finished {
260 warn!("cargo check for {} is still running, reporting early", uri);
261 }
262
263 tokio::time::sleep(Duration::from_millis(DOCUMENT_OPEN_DELAY_MILLIS)).await;
266
267 Ok(FreshDiagnostics {
268 items: self.current_diagnostics(uri).await?,
269 complete: loaded && finished,
270 })
271 }
272
273 pub async fn wait_until_loaded(&self, timeout: Duration) -> bool {
278 let mut quiescent = self.quiescent.subscribe();
279 let wait = async {
280 loop {
281 if *quiescent.borrow_and_update() {
282 return true;
283 }
284 if quiescent.changed().await.is_err() {
285 return false;
286 }
287 }
288 };
289
290 tokio::time::timeout(timeout, wait).await.unwrap_or(false)
291 }
292
293 async fn current_diagnostics(&mut self, uri: &str) -> Result<Value> {
303 let published = self.published_diagnostics(uri).await;
304
305 let params = json!({ "textDocument": { "uri": uri } });
306 let pulled = match self
307 .send_request("textDocument/diagnostic", Some(params))
308 .await
309 {
310 Ok(pulled) => pulled,
311 Err(e) => {
315 warn!("Could not ask rust-analyzer about {}: {}", uri, e);
316 return Ok(json!(published));
317 }
318 };
319 let Some(analysed) = pulled.get("items").and_then(|items| items.as_array()) else {
320 return Ok(json!(published));
321 };
322
323 let from_cargo = published
324 .iter()
325 .filter(|diagnostic| diagnostic["source"] != ANALYSIS_SOURCE)
326 .cloned();
327
328 Ok(json!(analysed
329 .iter()
330 .cloned()
331 .chain(from_cargo)
332 .collect::<Vec<_>>()))
333 }
334
335 async fn published_diagnostics(&self, uri: &str) -> Vec<Value> {
342 self.diagnostics
343 .lock()
344 .await
345 .get(&uri::normalize(uri))
346 .cloned()
347 .unwrap_or_default()
348 }
349
350 pub async fn workspace_diagnostics(&mut self) -> Result<Value> {
351 let params = json!({
353 "identifier": "rust-analyzer",
354 "previousResultId": null
355 });
356
357 match self
358 .send_request("workspace/diagnostic", Some(params))
359 .await
360 {
361 Ok(response) => Ok(response),
362 Err(e) if self.is_gone() => Err(e),
364 Err(_) => {
365 let mut all_diagnostics = json!({});
367 let open_docs: Vec<String> =
368 self.open_documents.lock().await.keys().cloned().collect();
369
370 for doc_uri in open_docs.iter() {
371 if let Ok(diag) = self.diagnostics(doc_uri).await {
372 all_diagnostics[doc_uri] = diag;
373 }
374 }
375
376 Ok(all_diagnostics)
377 }
378 }
379 }
380
381 pub async fn code_actions(
382 &mut self,
383 uri: &str,
384 start_line: u32,
385 start_char: u32,
386 end_line: u32,
387 end_char: u32,
388 ) -> Result<Value> {
389 let diagnostics = self.diagnostics(uri).await.unwrap_or(json!([]));
391
392 let filtered_diagnostics = filter_diagnostics_in_range(&diagnostics, start_line, end_line);
394
395 let params = json!({
396 "textDocument": { "uri": uri },
397 "range": {
398 "start": { "line": start_line, "character": start_char },
399 "end": { "line": end_line, "character": end_char }
400 },
401 "context": {
402 "diagnostics": filtered_diagnostics,
403 "only": ["quickfix", "refactor", "refactor.extract", "refactor.inline", "refactor.rewrite", "source"]
404 }
405 });
406
407 self.send_request("textDocument/codeAction", Some(params))
408 .await
409 }
410}
411
412fn filter_diagnostics_in_range(diagnostics: &Value, start_line: u32, end_line: u32) -> Value {
413 let Some(diag_array) = diagnostics.as_array() else {
414 return json!([]);
415 };
416
417 let filtered: Vec<Value> = diag_array
418 .iter()
419 .filter(|d| {
420 let Some(range) = d.get("range") else {
421 return false;
422 };
423 let Some(start) = range.get("start") else {
424 return false;
425 };
426 let Some(end) = range.get("end") else {
427 return false;
428 };
429
430 let diag_start_line = start.get("line").and_then(|l| l.as_u64()).unwrap_or(0) as u32;
431 let diag_end_line = end.get("line").and_then(|l| l.as_u64()).unwrap_or(0) as u32;
432
433 diag_start_line <= end_line && diag_end_line >= start_line
435 })
436 .cloned()
437 .collect();
438
439 json!(filtered)
440}