aboutsummaryrefslogtreecommitdiff
path: root/crates/flycheck/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/flycheck/src')
-rw-r--r--crates/flycheck/src/lib.rs109
1 files changed, 37 insertions, 72 deletions
diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs
index 9e8205ae7..92ec4f92e 100644
--- a/crates/flycheck/src/lib.rs
+++ b/crates/flycheck/src/lib.rs
@@ -7,7 +7,7 @@ use std::{
7 io::{self, BufReader}, 7 io::{self, BufReader},
8 path::PathBuf, 8 path::PathBuf,
9 process::{Command, Stdio}, 9 process::{Command, Stdio},
10 time::Instant, 10 time::Duration,
11}; 11};
12 12
13use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; 13use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
@@ -74,9 +74,6 @@ impl FlycheckHandle {
74 74
75#[derive(Debug)] 75#[derive(Debug)]
76pub enum Message { 76pub enum Message {
77 /// Request a clearing of all cached diagnostics from the check watcher
78 ClearDiagnostics,
79
80 /// Request adding a diagnostic with fixes included to a file 77 /// Request adding a diagnostic with fixes included to a file
81 AddDiagnostic { workspace_root: PathBuf, diagnostic: Diagnostic }, 78 AddDiagnostic { workspace_root: PathBuf, diagnostic: Diagnostic },
82 79
@@ -86,9 +83,10 @@ pub enum Message {
86 83
87#[derive(Debug)] 84#[derive(Debug)]
88pub enum Progress { 85pub enum Progress {
89 Being, 86 DidStart,
90 DidCheckCrate(String), 87 DidCheckCrate(String),
91 End, 88 DidFinish,
89 DidCancel,
92} 90}
93 91
94struct Restart; 92struct Restart;
@@ -97,19 +95,18 @@ struct FlycheckActor {
97 sender: Box<dyn Fn(Message) + Send>, 95 sender: Box<dyn Fn(Message) + Send>,
98 config: FlycheckConfig, 96 config: FlycheckConfig,
99 workspace_root: PathBuf, 97 workspace_root: PathBuf,
100 last_update_req: Option<Instant>,
101 /// WatchThread exists to wrap around the communication needed to be able to 98 /// WatchThread exists to wrap around the communication needed to be able to
102 /// run `cargo check` without blocking. Currently the Rust standard library 99 /// run `cargo check` without blocking. Currently the Rust standard library
103 /// doesn't provide a way to read sub-process output without blocking, so we 100 /// doesn't provide a way to read sub-process output without blocking, so we
104 /// have to wrap sub-processes output handling in a thread and pass messages 101 /// have to wrap sub-processes output handling in a thread and pass messages
105 /// back over a channel. 102 /// back over a channel.
106 // XXX: drop order is significant 103 // XXX: drop order is significant
107 check_process: Option<(Receiver<CheckEvent>, jod_thread::JoinHandle)>, 104 check_process: Option<(Receiver<cargo_metadata::Message>, jod_thread::JoinHandle)>,
108} 105}
109 106
110enum Event { 107enum Event {
111 Restart(Restart), 108 Restart(Restart),
112 CheckEvent(Option<CheckEvent>), 109 CheckEvent(Option<cargo_metadata::Message>),
113} 110}
114 111
115impl FlycheckActor { 112impl FlycheckActor {
@@ -118,78 +115,58 @@ impl FlycheckActor {
118 config: FlycheckConfig, 115 config: FlycheckConfig,
119 workspace_root: PathBuf, 116 workspace_root: PathBuf,
120 ) -> FlycheckActor { 117 ) -> FlycheckActor {
121 FlycheckActor { sender, config, workspace_root, last_update_req: None, check_process: None } 118 FlycheckActor { sender, config, workspace_root, check_process: None }
119 }
120 fn next_event(&self, inbox: &Receiver<Restart>) -> Option<Event> {
121 let check_chan = self.check_process.as_ref().map(|(chan, _thread)| chan);
122 select! {
123 recv(inbox) -> msg => msg.ok().map(Event::Restart),
124 recv(check_chan.unwrap_or(&never())) -> msg => Some(Event::CheckEvent(msg.ok())),
125 }
122 } 126 }
123
124 fn run(&mut self, inbox: Receiver<Restart>) { 127 fn run(&mut self, inbox: Receiver<Restart>) {
125 // If we rerun the thread, we need to discard the previous check results first
126 self.send(Message::ClearDiagnostics);
127 self.send(Message::Progress(Progress::End));
128
129 while let Some(event) = self.next_event(&inbox) { 128 while let Some(event) = self.next_event(&inbox) {
130 match event { 129 match event {
131 Event::Restart(Restart) => self.last_update_req = Some(Instant::now()), 130 Event::Restart(Restart) => {
131 while let Ok(Restart) = inbox.recv_timeout(Duration::from_millis(50)) {}
132 self.cancel_check_process();
133 self.check_process = Some(self.start_check_process());
134 self.send(Message::Progress(Progress::DidStart));
135 }
132 Event::CheckEvent(None) => { 136 Event::CheckEvent(None) => {
133 // Watcher finished, replace it with a never channel to 137 // Watcher finished, replace it with a never channel to
134 // avoid busy-waiting. 138 // avoid busy-waiting.
135 self.check_process = None; 139 assert!(self.check_process.take().is_some());
140 self.send(Message::Progress(Progress::DidFinish));
136 } 141 }
137 Event::CheckEvent(Some(event)) => match event { 142 Event::CheckEvent(Some(message)) => match message {
138 CheckEvent::Begin => { 143 cargo_metadata::Message::CompilerArtifact(msg) => {
139 self.send(Message::Progress(Progress::Being));
140 }
141
142 CheckEvent::End => {
143 self.send(Message::Progress(Progress::End));
144 }
145
146 CheckEvent::Msg(cargo_metadata::Message::CompilerArtifact(msg)) => {
147 self.send(Message::Progress(Progress::DidCheckCrate(msg.target.name))); 144 self.send(Message::Progress(Progress::DidCheckCrate(msg.target.name)));
148 } 145 }
149 146
150 CheckEvent::Msg(cargo_metadata::Message::CompilerMessage(msg)) => { 147 cargo_metadata::Message::CompilerMessage(msg) => {
151 self.send(Message::AddDiagnostic { 148 self.send(Message::AddDiagnostic {
152 workspace_root: self.workspace_root.clone(), 149 workspace_root: self.workspace_root.clone(),
153 diagnostic: msg.message, 150 diagnostic: msg.message,
154 }); 151 });
155 } 152 }
156 153
157 CheckEvent::Msg(cargo_metadata::Message::BuildScriptExecuted(_)) 154 cargo_metadata::Message::BuildScriptExecuted(_)
158 | CheckEvent::Msg(cargo_metadata::Message::BuildFinished(_)) 155 | cargo_metadata::Message::BuildFinished(_)
159 | CheckEvent::Msg(cargo_metadata::Message::TextLine(_)) 156 | cargo_metadata::Message::TextLine(_)
160 | CheckEvent::Msg(cargo_metadata::Message::Unknown) => {} 157 | cargo_metadata::Message::Unknown => {}
161 }, 158 },
162 } 159 }
163 if self.should_recheck() {
164 self.last_update_req = None;
165 self.send(Message::ClearDiagnostics);
166 self.restart_check_process();
167 }
168 }
169 }
170
171 fn next_event(&self, inbox: &Receiver<Restart>) -> Option<Event> {
172 let check_chan = self.check_process.as_ref().map(|(chan, _thread)| chan);
173 select! {
174 recv(inbox) -> msg => msg.ok().map(Event::Restart),
175 recv(check_chan.unwrap_or(&never())) -> msg => Some(Event::CheckEvent(msg.ok())),
176 } 160 }
161 // If we rerun the thread, we need to discard the previous check results first
162 self.cancel_check_process();
177 } 163 }
178 164 fn cancel_check_process(&mut self) {
179 fn should_recheck(&mut self) -> bool { 165 if self.check_process.take().is_some() {
180 if let Some(_last_update_req) = &self.last_update_req { 166 self.send(Message::Progress(Progress::DidCancel));
181 // We currently only request an update on save, as we need up to
182 // date source on disk for cargo check to do it's magic, so we
183 // don't really need to debounce the requests at this point.
184 return true;
185 } 167 }
186 false
187 } 168 }
188 169 fn start_check_process(&self) -> (Receiver<cargo_metadata::Message>, jod_thread::JoinHandle) {
189 fn restart_check_process(&mut self) {
190 // First, clear and cancel the old thread
191 self.check_process = None;
192
193 let mut cmd = match &self.config { 170 let mut cmd = match &self.config {
194 FlycheckConfig::CargoCommand { 171 FlycheckConfig::CargoCommand {
195 command, 172 command,
@@ -226,8 +203,6 @@ impl FlycheckActor {
226 let thread = jod_thread::spawn(move || { 203 let thread = jod_thread::spawn(move || {
227 // If we trigger an error here, we will do so in the loop instead, 204 // If we trigger an error here, we will do so in the loop instead,
228 // which will break out of the loop, and continue the shutdown 205 // which will break out of the loop, and continue the shutdown
229 let _ = message_send.send(CheckEvent::Begin);
230
231 let res = run_cargo(cmd, &mut |message| { 206 let res = run_cargo(cmd, &mut |message| {
232 // Skip certain kinds of messages to only spend time on what's useful 207 // Skip certain kinds of messages to only spend time on what's useful
233 match &message { 208 match &message {
@@ -240,7 +215,7 @@ impl FlycheckActor {
240 } 215 }
241 216
242 // if the send channel was closed, we want to shutdown 217 // if the send channel was closed, we want to shutdown
243 message_send.send(CheckEvent::Msg(message)).is_ok() 218 message_send.send(message).is_ok()
244 }); 219 });
245 220
246 if let Err(err) = res { 221 if let Err(err) = res {
@@ -248,12 +223,8 @@ impl FlycheckActor {
248 // to display user-caused misconfiguration errors instead of just logging them here 223 // to display user-caused misconfiguration errors instead of just logging them here
249 log::error!("Cargo watcher failed {:?}", err); 224 log::error!("Cargo watcher failed {:?}", err);
250 } 225 }
251
252 // We can ignore any error here, as we are already in the progress
253 // of shutting down.
254 let _ = message_send.send(CheckEvent::End);
255 }); 226 });
256 self.check_process = Some((message_recv, thread)) 227 (message_recv, thread)
257 } 228 }
258 229
259 fn send(&self, check_task: Message) { 230 fn send(&self, check_task: Message) {
@@ -261,12 +232,6 @@ impl FlycheckActor {
261 } 232 }
262} 233}
263 234
264enum CheckEvent {
265 Begin,
266 Msg(cargo_metadata::Message),
267 End,
268}
269
270fn run_cargo( 235fn run_cargo(
271 mut command: Command, 236 mut command: Command,
272 on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool, 237 on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool,