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+
106use 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
2021use crate :: conv:: { map_rust_diagnostic_to_lsp, MappedRustDiagnostic } ;
2122
@@ -78,17 +79,25 @@ pub enum CheckCommand {
7879struct 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
8593impl 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-
230275enum 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