aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_flycheck
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-04-01 09:21:17 +0100
committerGitHub <[email protected]>2020-04-01 09:21:17 +0100
commitfae6cecf5434a865043ec566a6417e9bb28c3a4c (patch)
tree61d8ea097f59043803b9b66d7bba5f52ffddfc50 /crates/ra_flycheck
parentf77fc158fc6502cede48c94bbabb040c77b38c08 (diff)
parentc86d8d40c2f17735e81b6d5d43b49950cbc9d86f (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.rs163
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.
4use cargo_metadata::Message; 4mod conv;
5use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender}; 5
6use lsp_types::{
7 CodeAction, CodeActionOrCommand, Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin,
8 WorkDoneProgressEnd, WorkDoneProgressReport,
9};
10use std::{ 6use 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
18mod conv; 14use cargo_metadata::Message;
15use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender};
16use lsp_types::{
17 CodeAction, CodeActionOrCommand, Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin,
18 WorkDoneProgressEnd, WorkDoneProgressReport,
19};
19 20
20use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic}; 21use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic};
21 22
@@ -78,8 +79,15 @@ pub enum CheckCommand {
78struct CheckWatcherThread { 79struct 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
85impl CheckWatcherThread { 93impl 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.
224struct WatchThread {
225 // XXX: drop order is significant
226 message_recv: Receiver<CheckEvent>,
227 _handle: Option<jod_thread::JoinHandle<()>>,
228}
229
230enum CheckEvent { 275enum 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
320impl 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}