aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_cargo_watch/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_cargo_watch/src/lib.rs')
-rw-r--r--crates/ra_cargo_watch/src/lib.rs153
1 files changed, 55 insertions, 98 deletions
diff --git a/crates/ra_cargo_watch/src/lib.rs b/crates/ra_cargo_watch/src/lib.rs
index 7f4c9280c..f07c34549 100644
--- a/crates/ra_cargo_watch/src/lib.rs
+++ b/crates/ra_cargo_watch/src/lib.rs
@@ -4,22 +4,20 @@
4use cargo_metadata::Message; 4use cargo_metadata::Message;
5use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender}; 5use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender};
6use lsp_types::{ 6use lsp_types::{
7 Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressEnd, 7 CodeAction, CodeActionOrCommand, Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin,
8 WorkDoneProgressReport, 8 WorkDoneProgressEnd, WorkDoneProgressReport,
9}; 9};
10use parking_lot::RwLock;
11use std::{ 10use std::{
12 collections::HashMap, 11 io::{BufRead, BufReader},
13 path::PathBuf, 12 path::PathBuf,
14 process::{Command, Stdio}, 13 process::{Command, Stdio},
15 sync::Arc,
16 thread::JoinHandle, 14 thread::JoinHandle,
17 time::Instant, 15 time::Instant,
18}; 16};
19 17
20mod conv; 18mod conv;
21 19
22use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic, SuggestedFix}; 20use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic};
23 21
24pub use crate::conv::url_from_path_with_drive_lowercasing; 22pub use crate::conv::url_from_path_with_drive_lowercasing;
25 23
@@ -38,7 +36,6 @@ pub struct CheckOptions {
38#[derive(Debug)] 36#[derive(Debug)]
39pub struct CheckWatcher { 37pub struct CheckWatcher {
40 pub task_recv: Receiver<CheckTask>, 38 pub task_recv: Receiver<CheckTask>,
41 pub state: Arc<RwLock<CheckState>>,
42 cmd_send: Option<Sender<CheckCommand>>, 39 cmd_send: Option<Sender<CheckCommand>>,
43 handle: Option<JoinHandle<()>>, 40 handle: Option<JoinHandle<()>>,
44} 41}
@@ -46,7 +43,6 @@ pub struct CheckWatcher {
46impl CheckWatcher { 43impl CheckWatcher {
47 pub fn new(options: &CheckOptions, workspace_root: PathBuf) -> CheckWatcher { 44 pub fn new(options: &CheckOptions, workspace_root: PathBuf) -> CheckWatcher {
48 let options = options.clone(); 45 let options = options.clone();
49 let state = Arc::new(RwLock::new(CheckState::new()));
50 46
51 let (task_send, task_recv) = unbounded::<CheckTask>(); 47 let (task_send, task_recv) = unbounded::<CheckTask>();
52 let (cmd_send, cmd_recv) = unbounded::<CheckCommand>(); 48 let (cmd_send, cmd_recv) = unbounded::<CheckCommand>();
@@ -54,13 +50,12 @@ impl CheckWatcher {
54 let mut check = CheckWatcherThread::new(options, workspace_root); 50 let mut check = CheckWatcherThread::new(options, workspace_root);
55 check.run(&task_send, &cmd_recv); 51 check.run(&task_send, &cmd_recv);
56 }); 52 });
57 CheckWatcher { task_recv, cmd_send: Some(cmd_send), handle: Some(handle), state } 53 CheckWatcher { task_recv, cmd_send: Some(cmd_send), handle: Some(handle) }
58 } 54 }
59 55
60 /// Returns a CheckWatcher that doesn't actually do anything 56 /// Returns a CheckWatcher that doesn't actually do anything
61 pub fn dummy() -> CheckWatcher { 57 pub fn dummy() -> CheckWatcher {
62 let state = Arc::new(RwLock::new(CheckState::new())); 58 CheckWatcher { task_recv: never(), cmd_send: None, handle: None }
63 CheckWatcher { task_recv: never(), cmd_send: None, handle: None, state }
64 } 59 }
65 60
66 /// Schedule a re-start of the cargo check worker. 61 /// Schedule a re-start of the cargo check worker.
@@ -88,83 +83,12 @@ impl std::ops::Drop for CheckWatcher {
88} 83}
89 84
90#[derive(Debug)] 85#[derive(Debug)]
91pub struct CheckState {
92 diagnostic_collection: HashMap<Url, Vec<Diagnostic>>,
93 suggested_fix_collection: HashMap<Url, Vec<SuggestedFix>>,
94}
95
96impl CheckState {
97 fn new() -> CheckState {
98 CheckState {
99 diagnostic_collection: HashMap::new(),
100 suggested_fix_collection: HashMap::new(),
101 }
102 }
103
104 /// Clear the cached diagnostics, and schedule updating diagnostics by the
105 /// server, to clear stale results.
106 pub fn clear(&mut self) -> Vec<Url> {
107 let cleared_files: Vec<Url> = self.diagnostic_collection.keys().cloned().collect();
108 self.diagnostic_collection.clear();
109 self.suggested_fix_collection.clear();
110 cleared_files
111 }
112
113 pub fn diagnostics_for(&self, uri: &Url) -> Option<&[Diagnostic]> {
114 self.diagnostic_collection.get(uri).map(|d| d.as_slice())
115 }
116
117 pub fn fixes_for(&self, uri: &Url) -> Option<&[SuggestedFix]> {
118 self.suggested_fix_collection.get(uri).map(|d| d.as_slice())
119 }
120
121 pub fn add_diagnostic_with_fixes(&mut self, file_uri: Url, diagnostic: DiagnosticWithFixes) {
122 for fix in diagnostic.suggested_fixes {
123 self.add_suggested_fix_for_diagnostic(fix, &diagnostic.diagnostic);
124 }
125 self.add_diagnostic(file_uri, diagnostic.diagnostic);
126 }
127
128 fn add_diagnostic(&mut self, file_uri: Url, diagnostic: Diagnostic) {
129 let diagnostics = self.diagnostic_collection.entry(file_uri).or_default();
130
131 // If we're building multiple targets it's possible we've already seen this diagnostic
132 let is_duplicate = diagnostics.iter().any(|d| are_diagnostics_equal(d, &diagnostic));
133 if is_duplicate {
134 return;
135 }
136
137 diagnostics.push(diagnostic);
138 }
139
140 fn add_suggested_fix_for_diagnostic(
141 &mut self,
142 mut suggested_fix: SuggestedFix,
143 diagnostic: &Diagnostic,
144 ) {
145 let file_uri = suggested_fix.location.uri.clone();
146 let file_suggestions = self.suggested_fix_collection.entry(file_uri).or_default();
147
148 let existing_suggestion: Option<&mut SuggestedFix> =
149 file_suggestions.iter_mut().find(|s| s == &&suggested_fix);
150 if let Some(existing_suggestion) = existing_suggestion {
151 // The existing suggestion also applies to this new diagnostic
152 existing_suggestion.diagnostics.push(diagnostic.clone());
153 } else {
154 // We haven't seen this suggestion before
155 suggested_fix.diagnostics.push(diagnostic.clone());
156 file_suggestions.push(suggested_fix);
157 }
158 }
159}
160
161#[derive(Debug)]
162pub enum CheckTask { 86pub enum CheckTask {
163 /// Request a clearing of all cached diagnostics from the check watcher 87 /// Request a clearing of all cached diagnostics from the check watcher
164 ClearDiagnostics, 88 ClearDiagnostics,
165 89
166 /// Request adding a diagnostic with fixes included to a file 90 /// Request adding a diagnostic with fixes included to a file
167 AddDiagnostic(Url, DiagnosticWithFixes), 91 AddDiagnostic { url: Url, diagnostic: Diagnostic, fixes: Vec<CodeActionOrCommand> },
168 92
169 /// Request check progress notification to client 93 /// Request check progress notification to client
170 Status(WorkDoneProgress), 94 Status(WorkDoneProgress),
@@ -216,8 +140,10 @@ impl CheckWatcherThread {
216 self.last_update_req.take(); 140 self.last_update_req.take();
217 task_send.send(CheckTask::ClearDiagnostics).unwrap(); 141 task_send.send(CheckTask::ClearDiagnostics).unwrap();
218 142
219 // By replacing the watcher, we drop the previous one which 143 // Replace with a dummy watcher first so we drop the original and wait for completion
220 // causes it to shut down automatically. 144 std::mem::replace(&mut self.watcher, WatchThread::dummy());
145
146 // Then create the actual new watcher
221 self.watcher = WatchThread::new(&self.options, &self.workspace_root); 147 self.watcher = WatchThread::new(&self.options, &self.workspace_root);
222 } 148 }
223 } 149 }
@@ -277,10 +203,17 @@ impl CheckWatcherThread {
277 None => return, 203 None => return,
278 }; 204 };
279 205
280 let MappedRustDiagnostic { location, diagnostic, suggested_fixes } = map_result; 206 let MappedRustDiagnostic { location, diagnostic, fixes } = map_result;
207 let fixes = fixes
208 .into_iter()
209 .map(|fix| {
210 CodeAction { diagnostics: Some(vec![diagnostic.clone()]), ..fix }.into()
211 })
212 .collect();
281 213
282 let diagnostic = DiagnosticWithFixes { diagnostic, suggested_fixes }; 214 task_send
283 task_send.send(CheckTask::AddDiagnostic(location.uri, diagnostic)).unwrap(); 215 .send(CheckTask::AddDiagnostic { url: location.uri, diagnostic, fixes })
216 .unwrap();
284 } 217 }
285 218
286 CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {} 219 CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {}
@@ -292,7 +225,7 @@ impl CheckWatcherThread {
292#[derive(Debug)] 225#[derive(Debug)]
293pub struct DiagnosticWithFixes { 226pub struct DiagnosticWithFixes {
294 diagnostic: Diagnostic, 227 diagnostic: Diagnostic,
295 suggested_fixes: Vec<SuggestedFix>, 228 fixes: Vec<CodeAction>,
296} 229}
297 230
298/// WatchThread exists to wrap around the communication needed to be able to 231/// WatchThread exists to wrap around the communication needed to be able to
@@ -341,6 +274,7 @@ impl WatchThread {
341 .args(&args) 274 .args(&args)
342 .stdout(Stdio::piped()) 275 .stdout(Stdio::piped())
343 .stderr(Stdio::null()) 276 .stderr(Stdio::null())
277 .stdin(Stdio::null())
344 .spawn() 278 .spawn()
345 .expect("couldn't launch cargo"); 279 .expect("couldn't launch cargo");
346 280
@@ -348,15 +282,45 @@ impl WatchThread {
348 // which will break out of the loop, and continue the shutdown 282 // which will break out of the loop, and continue the shutdown
349 let _ = message_send.send(CheckEvent::Begin); 283 let _ = message_send.send(CheckEvent::Begin);
350 284
351 for message in cargo_metadata::parse_messages(command.stdout.take().unwrap()) { 285 // We manually read a line at a time, instead of using serde's
286 // stream deserializers, because the deserializer cannot recover
287 // from an error, resulting in it getting stuck, because we try to
288 // be resillient against failures.
289 //
290 // Because cargo only outputs one JSON object per line, we can
291 // simply skip a line if it doesn't parse, which just ignores any
292 // erroneus output.
293 let stdout = BufReader::new(command.stdout.take().unwrap());
294 for line in stdout.lines() {
295 let line = match line {
296 Ok(line) => line,
297 Err(err) => {
298 log::error!("Couldn't read line from cargo: {}", err);
299 continue;
300 }
301 };
302
303 let message = serde_json::from_str::<cargo_metadata::Message>(&line);
352 let message = match message { 304 let message = match message {
353 Ok(message) => message, 305 Ok(message) => message,
354 Err(err) => { 306 Err(err) => {
355 log::error!("Invalid json from cargo check, ignoring: {}", err); 307 log::error!(
308 "Invalid json from cargo check, ignoring ({}): {:?} ",
309 err,
310 line
311 );
356 continue; 312 continue;
357 } 313 }
358 }; 314 };
359 315
316 // Skip certain kinds of messages to only spend time on what's useful
317 match &message {
318 Message::CompilerArtifact(artifact) if artifact.fresh => continue,
319 Message::BuildScriptExecuted(_) => continue,
320 Message::Unknown => continue,
321 _ => {}
322 }
323
360 match message_send.send(CheckEvent::Msg(message)) { 324 match message_send.send(CheckEvent::Msg(message)) {
361 Ok(()) => {} 325 Ok(()) => {}
362 Err(_err) => { 326 Err(_err) => {
@@ -396,10 +360,3 @@ impl std::ops::Drop for WatchThread {
396 } 360 }
397 } 361 }
398} 362}
399
400fn are_diagnostics_equal(left: &Diagnostic, right: &Diagnostic) -> bool {
401 left.source == right.source
402 && left.severity == right.severity
403 && left.range == right.range
404 && left.message == right.message
405}