Skip to content

Commit fae6cec

Browse files
bors[bot]matklad
andauthored
Merge #3799
3799: Streamline flycheck implementation r=matklad a=matklad bors r+ 🤖 Co-authored-by: Aleksey Kladov <[email protected]>
2 parents f77fc15 + c86d8d4 commit fae6cec

File tree

1 file changed

+76
-87
lines changed

1 file changed

+76
-87
lines changed

crates/ra_flycheck/src/lib.rs

Lines changed: 76 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
//! cargo_check provides the functionality needed to run `cargo check` or
22
//! another compatible command (f.x. clippy) in a background thread and provide
33
//! LSP diagnostics based on the output of the command.
4-
use cargo_metadata::Message;
5-
use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender};
6-
use lsp_types::{
7-
CodeAction, CodeActionOrCommand, Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin,
8-
WorkDoneProgressEnd, WorkDoneProgressReport,
9-
};
4+
mod conv;
5+
106
use std::{
117
error, fmt,
128
io::{BufRead, BufReader},
@@ -15,7 +11,12 @@ use std::{
1511
time::Instant,
1612
};
1713

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+
};
1920

2021
use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic};
2122

@@ -78,17 +79,25 @@ pub enum CheckCommand {
7879
struct CheckWatcherThread {
7980
options: CheckConfig,
8081
workspace_root: PathBuf,
81-
watcher: WatchThread,
8282
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<()>>,
8391
}
8492

8593
impl CheckWatcherThread {
8694
fn new(options: CheckConfig, workspace_root: PathBuf) -> CheckWatcherThread {
8795
CheckWatcherThread {
8896
options,
8997
workspace_root,
90-
watcher: WatchThread::dummy(),
9198
last_update_req: None,
99+
message_recv: never(),
100+
check_process: None,
92101
}
93102
}
94103

@@ -105,25 +114,21 @@ impl CheckWatcherThread {
105114
break;
106115
},
107116
},
108-
recv(self.watcher.message_recv) -> msg => match msg {
117+
recv(self.message_recv) -> msg => match msg {
109118
Ok(msg) => self.handle_message(msg, task_send),
110119
Err(RecvError) => {
111120
// Watcher finished, replace it with a never channel to
112121
// avoid busy-waiting.
113-
std::mem::replace(&mut self.watcher.message_recv, never());
122+
self.message_recv = never();
123+
self.check_process = None;
114124
},
115125
}
116126
};
117127

118128
if self.should_recheck() {
119-
self.last_update_req.take();
129+
self.last_update_req = None;
120130
task_send.send(CheckTask::ClearDiagnostics).unwrap();
121-
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);
131+
self.restart_check_process();
127132
}
128133
}
129134
}
@@ -206,6 +211,59 @@ impl CheckWatcherThread {
206211
CheckEvent::Msg(Message::Unknown) => {}
207212
}
208213
}
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+
}
209267
}
210268

211269
#[derive(Debug)]
@@ -214,19 +272,6 @@ pub struct DiagnosticWithFixes {
214272
fixes: Vec<CodeAction>,
215273
}
216274

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-
230275
enum CheckEvent {
231276
Begin,
232277
Msg(cargo_metadata::Message),
@@ -316,59 +361,3 @@ fn run_cargo(
316361

317362
Err(CargoError(err_msg))
318363
}
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-
}

0 commit comments

Comments
 (0)