11//! This module is responsible for implementing handlers for Language Server 
22//! Protocol. This module specifically handles notifications. 
33
4- use  std:: ops:: { Deref ,  Not  as  _} ; 
4+ use  std:: { 
5+     ops:: { Deref ,  Not  as  _} , 
6+     panic:: UnwindSafe , 
7+ } ; 
58
6- use  ide_db:: base_db:: salsa:: Cancelled ; 
79use  itertools:: Itertools ; 
810use  lsp_types:: { 
911    CancelParams ,  DidChangeConfigurationParams ,  DidChangeTextDocumentParams , 
@@ -16,7 +18,7 @@ use vfs::{AbsPathBuf, ChangeKind, VfsPath};
1618
1719use  crate :: { 
1820    config:: { Config ,  ConfigChange } , 
19-     flycheck:: Target , 
21+     flycheck:: { InvocationStrategy ,   Target } , 
2022    global_state:: { FetchWorkspaceRequest ,  GlobalState } , 
2123    lsp:: { from_proto,  utils:: apply_document_changes} , 
2224    lsp_ext:: { self ,  RunFlycheckParams } , 
@@ -301,131 +303,165 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool {
301303    let  file_id = state. vfs . read ( ) . 0 . file_id ( & vfs_path) ; 
302304    if  let  Some ( ( file_id,  vfs:: FileExcluded :: No ) )  = file_id { 
303305        let  world = state. snapshot ( ) ; 
304-         let  invocation_strategy_once  = state. config . flycheck ( None ) . invocation_strategy_once ( ) ; 
306+         let  invocation_strategy  = state. config . flycheck ( None ) . invocation_strategy ( ) ; 
305307        let  may_flycheck_workspace = state. config . flycheck_workspace ( None ) ; 
306-         let  mut  workspace_check_triggered = false ; 
307-         let  task = move  || -> std:: result:: Result < ( ) ,  Cancelled >  { 
308-             let  saved_file = vfs_path. as_path ( ) . map ( |p| p. to_owned ( ) ) ; 
309-             if  invocation_strategy_once { 
310-                 world. flycheck [ 0 ] . restart_workspace ( saved_file. clone ( ) ) ; 
311-             } 
312308
313-             let  target = TargetSpec :: for_file ( & world,  file_id) ?. and_then ( |it| { 
314-                 let  tgt_kind = it. target_kind ( ) ; 
315-                 let  ( tgt_name,  root,  package)  = match  it { 
316-                     TargetSpec :: Cargo ( c)  => ( c. target ,  c. workspace_root ,  c. package ) , 
317-                     _ => return  None , 
318-                 } ; 
319- 
320-                 let  tgt = match  tgt_kind { 
321-                     project_model:: TargetKind :: Bin  => Target :: Bin ( tgt_name) , 
322-                     project_model:: TargetKind :: Example  => Target :: Example ( tgt_name) , 
323-                     project_model:: TargetKind :: Test  => Target :: Test ( tgt_name) , 
324-                     project_model:: TargetKind :: Bench  => Target :: Benchmark ( tgt_name) , 
325-                     _ => return  Some ( ( None ,  root,  package) ) , 
326-                 } ; 
327- 
328-                 Some ( ( Some ( tgt) ,  root,  package) ) 
329-             } ) ; 
330-             tracing:: debug!( ?target,  "flycheck target" ) ; 
331-             // we have a specific non-library target, attempt to only check that target, nothing 
332-             // else will be affected 
333-             let  mut  package_workspace_idx = None ; 
334-             if  let  Some ( ( target,  root,  package) )  = target { 
335-                 // trigger a package check if we have a non-library target as that can't affect 
336-                 // anything else in the workspace OR if we're not allowed to check the workspace as 
337-                 // the user opted into package checks then 
338-                 let  package_check_allowed = target. is_some ( )  || !may_flycheck_workspace; 
339-                 if  package_check_allowed { 
340-                     let  workspace = world. workspaces . iter ( ) . position ( |ws| match  & ws. kind  { 
341-                         project_model:: ProjectWorkspaceKind :: Cargo  {  cargo,  .. } 
342-                         | project_model:: ProjectWorkspaceKind :: DetachedFile  { 
343-                             cargo :  Some ( ( cargo,  _,  _) ) , 
344-                             ..
345-                         }  => * cargo. workspace_root ( )  == root, 
346-                         _ => false , 
347-                     } ) ; 
348-                     if  let  Some ( idx)  = workspace { 
349-                         package_workspace_idx = Some ( idx) ; 
350-                         world. flycheck [ idx] . restart_for_package ( package,  target) ; 
351-                     } 
309+         let  task:  Box < dyn  FnOnce ( )  -> ide:: Cancellable < ( ) >  + Send  + UnwindSafe >  =
310+             match  invocation_strategy { 
311+                 InvocationStrategy :: Once  => { 
312+                     Box :: new ( move  || { 
313+                         // FIXME: Because triomphe::Arc's auto UnwindSafe impl requires that the inner type 
314+                         // be UnwindSafe, and FlycheckHandle is not UnwindSafe, `word.flycheck` cannot 
315+                         // be captured directly. std::sync::Arc has an UnwindSafe impl that only requires 
316+                         // that the inner type be RefUnwindSafe, so if we were using that one we wouldn't 
317+                         // have this problem. Remove the line below when triomphe::Arc has an UnwindSafe impl 
318+                         // like std::sync::Arc's. 
319+                         let  world = world; 
320+                         stdx:: always!( 
321+                             world. flycheck. len( )  == 1 , 
322+                             "should have exactly one flycheck handle when invocation strategy is once" 
323+                         ) ; 
324+                         let  saved_file = vfs_path. as_path ( ) . map ( ToOwned :: to_owned) ; 
325+                         world. flycheck [ 0 ] . restart_workspace ( saved_file) ; 
326+                         Ok ( ( ) ) 
327+                     } ) 
352328                } 
353-             } 
354- 
355-             if  !may_flycheck_workspace { 
356-                 return  Ok ( ( ) ) ; 
357-             } 
358- 
359-             // Trigger flychecks for all workspaces that depend on the saved file 
360-             // Crates containing or depending on the saved file 
361-             let  crate_ids = world
362-                 . analysis 
363-                 . crates_for ( file_id) ?
364-                 . into_iter ( ) 
365-                 . flat_map ( |id| world. analysis . transitive_rev_deps ( id) ) 
366-                 . flatten ( ) 
367-                 . unique ( ) 
368-                 . collect :: < Vec < _ > > ( ) ; 
369-             tracing:: debug!( ?crate_ids,  "flycheck crate ids" ) ; 
370-             let  crate_root_paths:  Vec < _ >  = crate_ids
371-                 . iter ( ) 
372-                 . filter_map ( |& crate_id| { 
373-                     world
374-                         . analysis 
375-                         . crate_root ( crate_id) 
376-                         . map ( |file_id| { 
377-                             world. file_id_to_file_path ( file_id) . as_path ( ) . map ( ToOwned :: to_owned) 
378-                         } ) 
379-                         . transpose ( ) 
380-                 } ) 
381-                 . collect :: < ide:: Cancellable < _ > > ( ) ?; 
382-             let  crate_root_paths:  Vec < _ >  = crate_root_paths. iter ( ) . map ( Deref :: deref) . collect ( ) ; 
383-             tracing:: debug!( ?crate_root_paths,  "flycheck crate roots" ) ; 
384- 
385-             // Find all workspaces that have at least one target containing the saved file 
386-             let  workspace_ids = world
387-                 . workspaces 
388-                 . iter ( ) 
389-                 . enumerate ( ) 
390-                 . filter ( |& ( idx,  _) | match  package_workspace_idx { 
391-                     Some ( pkg_idx)  => idx != pkg_idx, 
392-                     None  => true , 
393-                 } ) 
394-                 . filter ( |& ( _,  ws) | match  & ws. kind  { 
395-                     project_model:: ProjectWorkspaceKind :: Cargo  {  cargo,  .. } 
396-                     | project_model:: ProjectWorkspaceKind :: DetachedFile  { 
397-                         cargo :  Some ( ( cargo,  _,  _) ) , 
398-                         ..
399-                     }  => cargo. packages ( ) . any ( |pkg| { 
400-                         cargo[ pkg] 
401-                             . targets 
329+                 InvocationStrategy :: PerWorkspace  => { 
330+                     Box :: new ( move  || { 
331+                         let  target = TargetSpec :: for_file ( & world,  file_id) ?. and_then ( |it| { 
332+                             let  tgt_kind = it. target_kind ( ) ; 
333+                             let  ( tgt_name,  root,  package)  = match  it { 
334+                                 TargetSpec :: Cargo ( c)  => ( c. target ,  c. workspace_root ,  c. package ) , 
335+                                 _ => return  None , 
336+                             } ; 
337+ 
338+                             let  tgt = match  tgt_kind { 
339+                                 project_model:: TargetKind :: Bin  => Target :: Bin ( tgt_name) , 
340+                                 project_model:: TargetKind :: Example  => Target :: Example ( tgt_name) , 
341+                                 project_model:: TargetKind :: Test  => Target :: Test ( tgt_name) , 
342+                                 project_model:: TargetKind :: Bench  => Target :: Benchmark ( tgt_name) , 
343+                                 _ => return  Some ( ( None ,  root,  package) ) , 
344+                             } ; 
345+ 
346+                             Some ( ( Some ( tgt) ,  root,  package) ) 
347+                         } ) ; 
348+                         tracing:: debug!( ?target,  "flycheck target" ) ; 
349+                         // we have a specific non-library target, attempt to only check that target, nothing 
350+                         // else will be affected 
351+                         let  mut  package_workspace_idx = None ; 
352+                         if  let  Some ( ( target,  root,  package) )  = target { 
353+                             // trigger a package check if we have a non-library target as that can't affect 
354+                             // anything else in the workspace OR if we're not allowed to check the workspace as 
355+                             // the user opted into package checks then 
356+                             let  package_check_allowed = target. is_some ( )  || !may_flycheck_workspace; 
357+                             if  package_check_allowed { 
358+                                 package_workspace_idx =
359+                                     world. workspaces . iter ( ) . position ( |ws| match  & ws. kind  { 
360+                                         project_model:: ProjectWorkspaceKind :: Cargo  { 
361+                                             cargo, 
362+                                             ..
363+                                         } 
364+                                         | project_model:: ProjectWorkspaceKind :: DetachedFile  { 
365+                                             cargo :  Some ( ( cargo,  _,  _) ) , 
366+                                             ..
367+                                         }  => * cargo. workspace_root ( )  == root, 
368+                                         _ => false , 
369+                                     } ) ; 
370+                                 if  let  Some ( idx)  = package_workspace_idx { 
371+                                     world. flycheck [ idx] . restart_for_package ( package,  target) ; 
372+                                 } 
373+                             } 
374+                         } 
375+ 
376+                         if  !may_flycheck_workspace { 
377+                             return  Ok ( ( ) ) ; 
378+                         } 
379+ 
380+                         // Trigger flychecks for all workspaces that depend on the saved file 
381+                         // Crates containing or depending on the saved file 
382+                         let  crate_ids:  Vec < _ >  = world
383+                             . analysis 
384+                             . crates_for ( file_id) ?
385+                             . into_iter ( ) 
386+                             . flat_map ( |id| world. analysis . transitive_rev_deps ( id) ) 
387+                             . flatten ( ) 
388+                             . unique ( ) 
389+                             . collect ( ) ; 
390+                         tracing:: debug!( ?crate_ids,  "flycheck crate ids" ) ; 
391+                         let  crate_root_paths:  Vec < _ >  = crate_ids
402392                            . iter ( ) 
403-                             . any ( |& it| crate_root_paths. contains ( & cargo[ it] . root . as_path ( ) ) ) 
404-                     } ) , 
405-                     project_model:: ProjectWorkspaceKind :: Json ( project)  => project
406-                         . crates ( ) 
407-                         . any ( |( _,  krate) | crate_root_paths. contains ( & krate. root_module . as_path ( ) ) ) , 
408-                     project_model:: ProjectWorkspaceKind :: DetachedFile  {  .. }  => false , 
409-                 } ) ; 
410- 
411-             // Find and trigger corresponding flychecks 
412-             ' flychecks:  for  flycheck in  world. flycheck . iter ( )  { 
413-                 for  ( id,  _)  in  workspace_ids. clone ( )  { 
414-                     if  id == flycheck. id ( )  { 
415-                         workspace_check_triggered = true ; 
416-                         flycheck. restart_workspace ( saved_file. clone ( ) ) ; 
417-                         continue  ' flychecks; 
418-                     } 
419-                 } 
420-             } 
421-             // No specific flycheck was triggered, so let's trigger all of them. 
422-             if  !workspace_check_triggered && package_workspace_idx. is_none ( )  { 
423-                 for  flycheck in  world. flycheck . iter ( )  { 
424-                     flycheck. restart_workspace ( saved_file. clone ( ) ) ; 
393+                             . filter_map ( |& crate_id| { 
394+                                 world
395+                                     . analysis 
396+                                     . crate_root ( crate_id) 
397+                                     . map ( |file_id| { 
398+                                         world
399+                                             . file_id_to_file_path ( file_id) 
400+                                             . as_path ( ) 
401+                                             . map ( ToOwned :: to_owned) 
402+                                     } ) 
403+                                     . transpose ( ) 
404+                             } ) 
405+                             . collect :: < ide:: Cancellable < _ > > ( ) ?; 
406+                         let  crate_root_paths:  Vec < _ >  =
407+                             crate_root_paths. iter ( ) . map ( Deref :: deref) . collect ( ) ; 
408+                         tracing:: debug!( ?crate_root_paths,  "flycheck crate roots" ) ; 
409+ 
410+                         // Find all workspaces that have at least one target containing the saved file 
411+                         let  workspace_ids =
412+                             world. workspaces . iter ( ) . enumerate ( ) . filter ( |& ( idx,  ws) | { 
413+                                 let  ws_contains_file = match  & ws. kind  { 
414+                                     project_model:: ProjectWorkspaceKind :: Cargo  { 
415+                                         cargo,  ..
416+                                     } 
417+                                     | project_model:: ProjectWorkspaceKind :: DetachedFile  { 
418+                                         cargo :  Some ( ( cargo,  _,  _) ) , 
419+                                         ..
420+                                     }  => cargo. packages ( ) . any ( |pkg| { 
421+                                         cargo[ pkg] . targets . iter ( ) . any ( |& it| { 
422+                                             crate_root_paths. contains ( & cargo[ it] . root . as_path ( ) ) 
423+                                         } ) 
424+                                     } ) , 
425+                                     project_model:: ProjectWorkspaceKind :: Json ( project)  => { 
426+                                         project. crates ( ) . any ( |( _,  krate) | { 
427+                                             crate_root_paths. contains ( & krate. root_module . as_path ( ) ) 
428+                                         } ) 
429+                                     } 
430+                                     project_model:: ProjectWorkspaceKind :: DetachedFile  { 
431+                                         ..
432+                                     }  => false , 
433+                                 } ; 
434+                                 let  is_pkg_ws = match  package_workspace_idx { 
435+                                     Some ( pkg_idx)  => pkg_idx == idx, 
436+                                     None  => false , 
437+                                 } ; 
438+                                 ws_contains_file && !is_pkg_ws
439+                             } ) ; 
440+ 
441+                         let  saved_file = vfs_path. as_path ( ) . map ( ToOwned :: to_owned) ; 
442+                         let  mut  workspace_check_triggered = false ; 
443+                         // Find and trigger corresponding flychecks 
444+                         ' flychecks:  for  flycheck in  world. flycheck . iter ( )  { 
445+                             for  ( id,  _)  in  workspace_ids. clone ( )  { 
446+                                 if  id == flycheck. id ( )  { 
447+                                     workspace_check_triggered = true ; 
448+                                     flycheck. restart_workspace ( saved_file. clone ( ) ) ; 
449+                                     continue  ' flychecks; 
450+                                 } 
451+                             } 
452+                         } 
453+ 
454+                         // No specific flycheck was triggered, so let's trigger all of them. 
455+                         if  !workspace_check_triggered && package_workspace_idx. is_none ( )  { 
456+                             for  flycheck in  world. flycheck . iter ( )  { 
457+                                 flycheck. restart_workspace ( saved_file. clone ( ) ) ; 
458+                             } 
459+                         } 
460+                         Ok ( ( ) ) 
461+                     } ) 
425462                } 
426-             } 
427-             Ok ( ( ) ) 
428-         } ; 
463+             } ; 
464+ 
429465        state. task_pool . handle . spawn_with_sender ( stdx:: thread:: ThreadIntent :: Worker ,  move  |_| { 
430466            if  let  Err ( e)  = std:: panic:: catch_unwind ( task)  { 
431467                tracing:: error!( "flycheck task panicked: {e:?}" ) 
0 commit comments