diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-04-01 09:21:17 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-04-01 09:21:17 +0100 |
commit | fae6cecf5434a865043ec566a6417e9bb28c3a4c (patch) | |
tree | 61d8ea097f59043803b9b66d7bba5f52ffddfc50 /crates/ra_flycheck | |
parent | f77fc158fc6502cede48c94bbabb040c77b38c08 (diff) | |
parent | c86d8d40c2f17735e81b6d5d43b49950cbc9d86f (diff) |
Merge #3799
3799: Streamline flycheck implementation r=matklad a=matklad
bors r+
🤖
Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ra_flycheck')
-rw-r--r-- | crates/ra_flycheck/src/lib.rs | 163 |
1 files changed, 76 insertions, 87 deletions
diff --git a/crates/ra_flycheck/src/lib.rs b/crates/ra_flycheck/src/lib.rs index 38940a77b..f6f9171ad 100644 --- a/crates/ra_flycheck/src/lib.rs +++ b/crates/ra_flycheck/src/lib.rs | |||
@@ -1,12 +1,8 @@ | |||
1 | //! cargo_check provides the functionality needed to run `cargo check` or | 1 | //! cargo_check provides the functionality needed to run `cargo check` or |
2 | //! another compatible command (f.x. clippy) in a background thread and provide | 2 | //! another compatible command (f.x. clippy) in a background thread and provide |
3 | //! LSP diagnostics based on the output of the command. | 3 | //! LSP diagnostics based on the output of the command. |
4 | use cargo_metadata::Message; | 4 | mod conv; |
5 | use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender}; | 5 | |
6 | use lsp_types::{ | ||
7 | CodeAction, CodeActionOrCommand, Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin, | ||
8 | WorkDoneProgressEnd, WorkDoneProgressReport, | ||
9 | }; | ||
10 | use std::{ | 6 | use std::{ |
11 | error, fmt, | 7 | error, fmt, |
12 | io::{BufRead, BufReader}, | 8 | io::{BufRead, BufReader}, |
@@ -15,7 +11,12 @@ use std::{ | |||
15 | time::Instant, | 11 | time::Instant, |
16 | }; | 12 | }; |
17 | 13 | ||
18 | mod conv; | 14 | use cargo_metadata::Message; |
15 | use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender}; | ||
16 | use lsp_types::{ | ||
17 | CodeAction, CodeActionOrCommand, Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin, | ||
18 | WorkDoneProgressEnd, WorkDoneProgressReport, | ||
19 | }; | ||
19 | 20 | ||
20 | use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic}; | 21 | use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic}; |
21 | 22 | ||
@@ -78,8 +79,15 @@ pub enum CheckCommand { | |||
78 | struct CheckWatcherThread { | 79 | struct CheckWatcherThread { |
79 | options: CheckConfig, | 80 | options: CheckConfig, |
80 | workspace_root: PathBuf, | 81 | workspace_root: PathBuf, |
81 | watcher: WatchThread, | ||
82 | last_update_req: Option<Instant>, | 82 | last_update_req: Option<Instant>, |
83 | // XXX: drop order is significant | ||
84 | message_recv: Receiver<CheckEvent>, | ||
85 | /// WatchThread exists to wrap around the communication needed to be able to | ||
86 | /// run `cargo check` without blocking. Currently the Rust standard library | ||
87 | /// doesn't provide a way to read sub-process output without blocking, so we | ||
88 | /// have to wrap sub-processes output handling in a thread and pass messages | ||
89 | /// back over a channel. | ||
90 | check_process: Option<jod_thread::JoinHandle<()>>, | ||
83 | } | 91 | } |
84 | 92 | ||
85 | impl CheckWatcherThread { | 93 | impl CheckWatcherThread { |
@@ -87,8 +95,9 @@ impl CheckWatcherThread { | |||
87 | CheckWatcherThread { | 95 | CheckWatcherThread { |
88 | options, | 96 | options, |
89 | workspace_root, | 97 | workspace_root, |
90 | watcher: WatchThread::dummy(), | ||
91 | last_update_req: None, | 98 | last_update_req: None, |
99 | message_recv: never(), | ||
100 | check_process: None, | ||
92 | } | 101 | } |
93 | } | 102 | } |
94 | 103 | ||
@@ -105,25 +114,21 @@ impl CheckWatcherThread { | |||
105 | break; | 114 | break; |
106 | }, | 115 | }, |
107 | }, | 116 | }, |
108 | recv(self.watcher.message_recv) -> msg => match msg { | 117 | recv(self.message_recv) -> msg => match msg { |
109 | Ok(msg) => self.handle_message(msg, task_send), | 118 | Ok(msg) => self.handle_message(msg, task_send), |
110 | Err(RecvError) => { | 119 | Err(RecvError) => { |
111 | // Watcher finished, replace it with a never channel to | 120 | // Watcher finished, replace it with a never channel to |
112 | // avoid busy-waiting. | 121 | // avoid busy-waiting. |
113 | std::mem::replace(&mut self.watcher.message_recv, never()); | 122 | self.message_recv = never(); |
123 | self.check_process = None; | ||
114 | }, | 124 | }, |
115 | } | 125 | } |
116 | }; | 126 | }; |
117 | 127 | ||
118 | if self.should_recheck() { | 128 | if self.should_recheck() { |
119 | self.last_update_req.take(); | 129 | self.last_update_req = None; |
120 | task_send.send(CheckTask::ClearDiagnostics).unwrap(); | 130 | task_send.send(CheckTask::ClearDiagnostics).unwrap(); |
121 | 131 | self.restart_check_process(); | |
122 | // Replace with a dummy watcher first so we drop the original and wait for completion | ||
123 | std::mem::replace(&mut self.watcher, WatchThread::dummy()); | ||
124 | |||
125 | // Then create the actual new watcher | ||
126 | self.watcher = WatchThread::new(&self.options, &self.workspace_root); | ||
127 | } | 132 | } |
128 | } | 133 | } |
129 | } | 134 | } |
@@ -206,6 +211,59 @@ impl CheckWatcherThread { | |||
206 | CheckEvent::Msg(Message::Unknown) => {} | 211 | CheckEvent::Msg(Message::Unknown) => {} |
207 | } | 212 | } |
208 | } | 213 | } |
214 | |||
215 | fn restart_check_process(&mut self) { | ||
216 | // First, clear and cancel the old thread | ||
217 | self.message_recv = never(); | ||
218 | self.check_process = None; | ||
219 | if !self.options.enable { | ||
220 | return; | ||
221 | } | ||
222 | |||
223 | let mut args: Vec<String> = vec![ | ||
224 | self.options.command.clone(), | ||
225 | "--workspace".to_string(), | ||
226 | "--message-format=json".to_string(), | ||
227 | "--manifest-path".to_string(), | ||
228 | format!("{}/Cargo.toml", self.workspace_root.display()), | ||
229 | ]; | ||
230 | if self.options.all_targets { | ||
231 | args.push("--all-targets".to_string()); | ||
232 | } | ||
233 | args.extend(self.options.args.iter().cloned()); | ||
234 | |||
235 | let (message_send, message_recv) = unbounded(); | ||
236 | let workspace_root = self.workspace_root.to_owned(); | ||
237 | self.message_recv = message_recv; | ||
238 | self.check_process = Some(jod_thread::spawn(move || { | ||
239 | // If we trigger an error here, we will do so in the loop instead, | ||
240 | // which will break out of the loop, and continue the shutdown | ||
241 | let _ = message_send.send(CheckEvent::Begin); | ||
242 | |||
243 | let res = run_cargo(&args, Some(&workspace_root), &mut |message| { | ||
244 | // Skip certain kinds of messages to only spend time on what's useful | ||
245 | match &message { | ||
246 | Message::CompilerArtifact(artifact) if artifact.fresh => return true, | ||
247 | Message::BuildScriptExecuted(_) => return true, | ||
248 | Message::Unknown => return true, | ||
249 | _ => {} | ||
250 | } | ||
251 | |||
252 | // if the send channel was closed, we want to shutdown | ||
253 | message_send.send(CheckEvent::Msg(message)).is_ok() | ||
254 | }); | ||
255 | |||
256 | if let Err(err) = res { | ||
257 | // FIXME: make the `message_send` to be `Sender<Result<CheckEvent, CargoError>>` | ||
258 | // to display user-caused misconfiguration errors instead of just logging them here | ||
259 | log::error!("Cargo watcher failed {:?}", err); | ||
260 | } | ||
261 | |||
262 | // We can ignore any error here, as we are already in the progress | ||
263 | // of shutting down. | ||
264 | let _ = message_send.send(CheckEvent::End); | ||
265 | })) | ||
266 | } | ||
209 | } | 267 | } |
210 | 268 | ||
211 | #[derive(Debug)] | 269 | #[derive(Debug)] |
@@ -214,19 +272,6 @@ pub struct DiagnosticWithFixes { | |||
214 | fixes: Vec<CodeAction>, | 272 | fixes: Vec<CodeAction>, |
215 | } | 273 | } |
216 | 274 | ||
217 | /// WatchThread exists to wrap around the communication needed to be able to | ||
218 | /// run `cargo check` without blocking. Currently the Rust standard library | ||
219 | /// doesn't provide a way to read sub-process output without blocking, so we | ||
220 | /// have to wrap sub-processes output handling in a thread and pass messages | ||
221 | /// back over a channel. | ||
222 | /// The correct way to dispose of the thread is to drop it, on which the | ||
223 | /// sub-process will be killed, and the thread will be joined. | ||
224 | struct WatchThread { | ||
225 | // XXX: drop order is significant | ||
226 | message_recv: Receiver<CheckEvent>, | ||
227 | _handle: Option<jod_thread::JoinHandle<()>>, | ||
228 | } | ||
229 | |||
230 | enum CheckEvent { | 275 | enum CheckEvent { |
231 | Begin, | 276 | Begin, |
232 | Msg(cargo_metadata::Message), | 277 | Msg(cargo_metadata::Message), |
@@ -316,59 +361,3 @@ fn run_cargo( | |||
316 | 361 | ||
317 | Err(CargoError(err_msg)) | 362 | Err(CargoError(err_msg)) |
318 | } | 363 | } |
319 | |||
320 | impl WatchThread { | ||
321 | fn dummy() -> WatchThread { | ||
322 | WatchThread { message_recv: never(), _handle: None } | ||
323 | } | ||
324 | |||
325 | fn new(options: &CheckConfig, workspace_root: &Path) -> WatchThread { | ||
326 | let mut args: Vec<String> = vec![ | ||
327 | options.command.clone(), | ||
328 | "--workspace".to_string(), | ||
329 | "--message-format=json".to_string(), | ||
330 | "--manifest-path".to_string(), | ||
331 | format!("{}/Cargo.toml", workspace_root.display()), | ||
332 | ]; | ||
333 | if options.all_targets { | ||
334 | args.push("--all-targets".to_string()); | ||
335 | } | ||
336 | args.extend(options.args.iter().cloned()); | ||
337 | |||
338 | let (message_send, message_recv) = unbounded(); | ||
339 | let workspace_root = workspace_root.to_owned(); | ||
340 | let handle = if options.enable { | ||
341 | Some(jod_thread::spawn(move || { | ||
342 | // If we trigger an error here, we will do so in the loop instead, | ||
343 | // which will break out of the loop, and continue the shutdown | ||
344 | let _ = message_send.send(CheckEvent::Begin); | ||
345 | |||
346 | let res = run_cargo(&args, Some(&workspace_root), &mut |message| { | ||
347 | // Skip certain kinds of messages to only spend time on what's useful | ||
348 | match &message { | ||
349 | Message::CompilerArtifact(artifact) if artifact.fresh => return true, | ||
350 | Message::BuildScriptExecuted(_) => return true, | ||
351 | Message::Unknown => return true, | ||
352 | _ => {} | ||
353 | } | ||
354 | |||
355 | // if the send channel was closed, we want to shutdown | ||
356 | message_send.send(CheckEvent::Msg(message)).is_ok() | ||
357 | }); | ||
358 | |||
359 | if let Err(err) = res { | ||
360 | // FIXME: make the `message_send` to be `Sender<Result<CheckEvent, CargoError>>` | ||
361 | // to display user-caused misconfiguration errors instead of just logging them here | ||
362 | log::error!("Cargo watcher failed {:?}", err); | ||
363 | } | ||
364 | |||
365 | // We can ignore any error here, as we are already in the progress | ||
366 | // of shutting down. | ||
367 | let _ = message_send.send(CheckEvent::End); | ||
368 | })) | ||
369 | } else { | ||
370 | None | ||
371 | }; | ||
372 | WatchThread { message_recv, _handle: handle } | ||
373 | } | ||
374 | } | ||