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