diff options
author | Kevin DeLorey <[email protected]> | 2020-02-09 16:25:47 +0000 |
---|---|---|
committer | Kevin DeLorey <[email protected]> | 2020-02-09 16:37:43 +0000 |
commit | a957c473fdb79880c39b73dc9e0c923093cf16ac (patch) | |
tree | f998b548f530ce604651e0e6af314ed2ec74b3b5 /crates/ra_cargo_watch/src/lib.rs | |
parent | 22caf982b99c54058e2e9200aeea0e61cada284a (diff) | |
parent | 1b9b13b4b4a75b5531c3f046ce6bf72d681f2732 (diff) |
Merge branch 'master' into kdelorey/complete-trait-impl
Diffstat (limited to 'crates/ra_cargo_watch/src/lib.rs')
-rw-r--r-- | crates/ra_cargo_watch/src/lib.rs | 153 |
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 @@ | |||
4 | use cargo_metadata::Message; | 4 | use cargo_metadata::Message; |
5 | use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender}; | 5 | use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender}; |
6 | use lsp_types::{ | 6 | use lsp_types::{ |
7 | Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressEnd, | 7 | CodeAction, CodeActionOrCommand, Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin, |
8 | WorkDoneProgressReport, | 8 | WorkDoneProgressEnd, WorkDoneProgressReport, |
9 | }; | 9 | }; |
10 | use parking_lot::RwLock; | ||
11 | use std::{ | 10 | use 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 | ||
20 | mod conv; | 18 | mod conv; |
21 | 19 | ||
22 | use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic, SuggestedFix}; | 20 | use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic}; |
23 | 21 | ||
24 | pub use crate::conv::url_from_path_with_drive_lowercasing; | 22 | pub use crate::conv::url_from_path_with_drive_lowercasing; |
25 | 23 | ||
@@ -38,7 +36,6 @@ pub struct CheckOptions { | |||
38 | #[derive(Debug)] | 36 | #[derive(Debug)] |
39 | pub struct CheckWatcher { | 37 | pub 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 { | |||
46 | impl CheckWatcher { | 43 | impl 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)] |
91 | pub struct CheckState { | ||
92 | diagnostic_collection: HashMap<Url, Vec<Diagnostic>>, | ||
93 | suggested_fix_collection: HashMap<Url, Vec<SuggestedFix>>, | ||
94 | } | ||
95 | |||
96 | impl 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)] | ||
162 | pub enum CheckTask { | 86 | pub 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)] |
293 | pub struct DiagnosticWithFixes { | 226 | pub 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 | |||
400 | fn 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 | } | ||