aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-03-31 23:39:50 +0100
committerAleksey Kladov <[email protected]>2020-04-01 08:42:23 +0100
commitc86d8d40c2f17735e81b6d5d43b49950cbc9d86f (patch)
tree61d8ea097f59043803b9b66d7bba5f52ffddfc50
parentb46fd3863203d662531c735389b4008d8a715561 (diff)
Streamline flycheck implementation
-rw-r--r--crates/ra_flycheck/src/lib.rs148
1 files changed, 68 insertions, 80 deletions
diff --git a/crates/ra_flycheck/src/lib.rs b/crates/ra_flycheck/src/lib.rs
index 4b3fbc908..f6f9171ad 100644
--- a/crates/ra_flycheck/src/lib.rs
+++ b/crates/ra_flycheck/src/lib.rs
@@ -79,8 +79,15 @@ pub enum CheckCommand {
79struct CheckWatcherThread { 79struct CheckWatcherThread {
80 options: CheckConfig, 80 options: CheckConfig,
81 workspace_root: PathBuf, 81 workspace_root: PathBuf,
82 watcher: WatchThread,
83 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<()>>,
84} 91}
85 92
86impl CheckWatcherThread { 93impl CheckWatcherThread {
@@ -88,8 +95,9 @@ impl CheckWatcherThread {
88 CheckWatcherThread { 95 CheckWatcherThread {
89 options, 96 options,
90 workspace_root, 97 workspace_root,
91 watcher: WatchThread::dummy(),
92 last_update_req: None, 98 last_update_req: None,
99 message_recv: never(),
100 check_process: None,
93 } 101 }
94 } 102 }
95 103
@@ -106,25 +114,21 @@ impl CheckWatcherThread {
106 break; 114 break;
107 }, 115 },
108 }, 116 },
109 recv(self.watcher.message_recv) -> msg => match msg { 117 recv(self.message_recv) -> msg => match msg {
110 Ok(msg) => self.handle_message(msg, task_send), 118 Ok(msg) => self.handle_message(msg, task_send),
111 Err(RecvError) => { 119 Err(RecvError) => {
112 // Watcher finished, replace it with a never channel to 120 // Watcher finished, replace it with a never channel to
113 // avoid busy-waiting. 121 // avoid busy-waiting.
114 std::mem::replace(&mut self.watcher.message_recv, never()); 122 self.message_recv = never();
123 self.check_process = None;
115 }, 124 },
116 } 125 }
117 }; 126 };
118 127
119 if self.should_recheck() { 128 if self.should_recheck() {
120 self.last_update_req.take(); 129 self.last_update_req = None;
121 task_send.send(CheckTask::ClearDiagnostics).unwrap(); 130 task_send.send(CheckTask::ClearDiagnostics).unwrap();
122 131 self.restart_check_process();
123 // Replace with a dummy watcher first so we drop the original and wait for completion
124 std::mem::replace(&mut self.watcher, WatchThread::dummy());
125
126 // Then create the actual new watcher
127 self.watcher = WatchThread::new(&self.options, &self.workspace_root);
128 } 132 }
129 } 133 }
130 } 134 }
@@ -207,6 +211,59 @@ impl CheckWatcherThread {
207 CheckEvent::Msg(Message::Unknown) => {} 211 CheckEvent::Msg(Message::Unknown) => {}
208 } 212 }
209 } 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 }
210} 267}
211 268
212#[derive(Debug)] 269#[derive(Debug)]
@@ -215,19 +272,6 @@ pub struct DiagnosticWithFixes {
215 fixes: Vec<CodeAction>, 272 fixes: Vec<CodeAction>,
216} 273}
217 274
218/// WatchThread exists to wrap around the communication needed to be able to
219/// run `cargo check` without blocking. Currently the Rust standard library
220/// doesn't provide a way to read sub-process output without blocking, so we
221/// have to wrap sub-processes output handling in a thread and pass messages
222/// back over a channel.
223/// The correct way to dispose of the thread is to drop it, on which the
224/// sub-process will be killed, and the thread will be joined.
225struct WatchThread {
226 // XXX: drop order is significant
227 message_recv: Receiver<CheckEvent>,
228 _handle: Option<jod_thread::JoinHandle<()>>,
229}
230
231enum CheckEvent { 275enum CheckEvent {
232 Begin, 276 Begin,
233 Msg(cargo_metadata::Message), 277 Msg(cargo_metadata::Message),
@@ -317,59 +361,3 @@ fn run_cargo(
317 361
318 Err(CargoError(err_msg)) 362 Err(CargoError(err_msg))
319} 363}
320
321impl WatchThread {
322 fn dummy() -> WatchThread {
323 WatchThread { message_recv: never(), _handle: None }
324 }
325
326 fn new(options: &CheckConfig, workspace_root: &Path) -> WatchThread {
327 let mut args: Vec<String> = vec![
328 options.command.clone(),
329 "--workspace".to_string(),
330 "--message-format=json".to_string(),
331 "--manifest-path".to_string(),
332 format!("{}/Cargo.toml", workspace_root.display()),
333 ];
334 if options.all_targets {
335 args.push("--all-targets".to_string());
336 }
337 args.extend(options.args.iter().cloned());
338
339 let (message_send, message_recv) = unbounded();
340 let workspace_root = workspace_root.to_owned();
341 let handle = if options.enable {
342 Some(jod_thread::spawn(move || {
343 // If we trigger an error here, we will do so in the loop instead,
344 // which will break out of the loop, and continue the shutdown
345 let _ = message_send.send(CheckEvent::Begin);
346
347 let res = run_cargo(&args, Some(&workspace_root), &mut |message| {
348 // Skip certain kinds of messages to only spend time on what's useful
349 match &message {
350 Message::CompilerArtifact(artifact) if artifact.fresh => return true,
351 Message::BuildScriptExecuted(_) => return true,
352 Message::Unknown => return true,
353 _ => {}
354 }
355
356 // if the send channel was closed, we want to shutdown
357 message_send.send(CheckEvent::Msg(message)).is_ok()
358 });
359
360 if let Err(err) = res {
361 // FIXME: make the `message_send` to be `Sender<Result<CheckEvent, CargoError>>`
362 // to display user-caused misconfiguration errors instead of just logging them here
363 log::error!("Cargo watcher failed {:?}", err);
364 }
365
366 // We can ignore any error here, as we are already in the progress
367 // of shutting down.
368 let _ = message_send.send(CheckEvent::End);
369 }))
370 } else {
371 None
372 };
373 WatchThread { message_recv, _handle: handle }
374 }
375}