diff options
Diffstat (limited to 'crates/flycheck/src')
-rw-r--r-- | crates/flycheck/src/lib.rs | 109 |
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 | ||
13 | use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; | 13 | use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; |
@@ -74,9 +74,6 @@ impl FlycheckHandle { | |||
74 | 74 | ||
75 | #[derive(Debug)] | 75 | #[derive(Debug)] |
76 | pub enum Message { | 76 | pub 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)] |
88 | pub enum Progress { | 85 | pub enum Progress { |
89 | Being, | 86 | DidStart, |
90 | DidCheckCrate(String), | 87 | DidCheckCrate(String), |
91 | End, | 88 | DidFinish, |
89 | DidCancel, | ||
92 | } | 90 | } |
93 | 91 | ||
94 | struct Restart; | 92 | struct 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 | ||
110 | enum Event { | 107 | enum Event { |
111 | Restart(Restart), | 108 | Restart(Restart), |
112 | CheckEvent(Option<CheckEvent>), | 109 | CheckEvent(Option<cargo_metadata::Message>), |
113 | } | 110 | } |
114 | 111 | ||
115 | impl FlycheckActor { | 112 | impl 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 | ||
264 | enum CheckEvent { | ||
265 | Begin, | ||
266 | Msg(cargo_metadata::Message), | ||
267 | End, | ||
268 | } | ||
269 | |||
270 | fn run_cargo( | 235 | fn 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, |