@@ -21,7 +21,7 @@ use pet_core::{
2121 Configuration , Locator , RefreshStatePersistence , RefreshStateSyncScope ,
2222} ;
2323use pet_env_var_path:: get_search_paths_from_env_variables;
24- use pet_fs:: glob:: expand_glob_patterns;
24+ use pet_fs:: glob:: { expand_glob_pattern , expand_glob_patterns, is_recursive_glob_pattern } ;
2525use pet_fs:: path:: norm_case;
2626use pet_jsonrpc:: {
2727 send_error, send_reply,
@@ -567,6 +567,47 @@ pub struct ConfigureOptions {
567567/// The client has a 30-second timeout for configure requests.
568568const GLOB_EXPANSION_WARN_THRESHOLD : Duration = Duration :: from_secs ( 5 ) ;
569569
570+ fn expand_configure_directory_patterns ( kind : & str , patterns : Vec < PathBuf > ) -> Vec < PathBuf > {
571+ patterns
572+ . into_iter ( )
573+ . flat_map ( |pattern| {
574+ let start = Instant :: now ( ) ;
575+ let expanded = expand_glob_pattern ( & pattern. to_string_lossy ( ) ) ;
576+ let elapsed = start. elapsed ( ) ;
577+ trace ! (
578+ "Expanded {} pattern '{}' in {:?}" ,
579+ kind,
580+ pattern. display( ) ,
581+ elapsed
582+ ) ;
583+ if elapsed >= GLOB_EXPANSION_WARN_THRESHOLD {
584+ warn ! (
585+ "Expanding {} pattern '{}' took {:?}, this may cause client timeouts" ,
586+ kind,
587+ pattern. display( ) ,
588+ elapsed
589+ ) ;
590+ }
591+ expanded
592+ } )
593+ . filter ( |path| path. is_dir ( ) )
594+ . collect ( )
595+ }
596+
597+ fn recursive_environment_patterns ( patterns : & [ PathBuf ] ) -> impl Iterator < Item = & PathBuf > {
598+ patterns
599+ . iter ( )
600+ . filter ( |pattern| is_recursive_glob_pattern ( & pattern. to_string_lossy ( ) ) )
601+ }
602+
603+ fn warn_for_recursive_environment_patterns ( patterns : & [ PathBuf ] ) {
604+ for pattern in recursive_environment_patterns ( patterns) {
605+ warn ! (
606+ "Recursive environmentDirectories pattern '{}' can make configure slow; prefer non-recursive container-directory patterns such as '<root>/envs' or '<root>/*/envs'" ,
607+ pattern. display( )
608+ ) ;
609+ }
610+ }
570611pub fn handle_configure ( context : Arc < Context > , id : u32 , params : Value ) {
571612 match serde_json:: from_value :: < ConfigureOptions > ( params. clone ( ) ) {
572613 Ok ( mut configure_options) => {
@@ -575,38 +616,27 @@ pub fn handle_configure(context: Arc<Context>, id: u32, params: Value) {
575616 thread:: spawn ( move || {
576617 let now = Instant :: now ( ) ;
577618
619+ // Warn before any expansion so a slow workspace pattern cannot delay
620+ // the actionable environmentDirectories diagnostic.
621+ if let Some ( patterns) = configure_options. environment_directories . as_deref ( ) {
622+ warn_for_recursive_environment_patterns ( patterns) ;
623+ }
624+
578625 // Expand glob patterns before acquiring the write lock so we
579626 // don't block readers/writers while traversing the filesystem.
580627 let workspace_directories =
581- configure_options. workspace_directories . take ( ) . map ( |dirs| {
582- let start = Instant :: now ( ) ;
583- let result: Vec < PathBuf > = expand_glob_patterns ( & dirs)
584- . into_iter ( )
585- . filter ( |p| p. is_dir ( ) )
586- . collect ( ) ;
587- trace ! (
588- "Expanded workspace directory patterns ({:?}) in {:?}" ,
589- dirs,
590- start. elapsed( )
591- ) ;
592- result
593- } ) ;
628+ configure_options
629+ . workspace_directories
630+ . take ( )
631+ . map ( |patterns| {
632+ expand_configure_directory_patterns ( "workspaceDirectories" , patterns)
633+ } ) ;
594634 let environment_directories =
595635 configure_options
596636 . environment_directories
597637 . take ( )
598- . map ( |dirs| {
599- let start = Instant :: now ( ) ;
600- let result: Vec < PathBuf > = expand_glob_patterns ( & dirs)
601- . into_iter ( )
602- . filter ( |p| p. is_dir ( ) )
603- . collect ( ) ;
604- trace ! (
605- "Expanded environment directory patterns ({:?}) in {:?}" ,
606- dirs,
607- start. elapsed( )
608- ) ;
609- result
638+ . map ( |patterns| {
639+ expand_configure_directory_patterns ( "environmentDirectories" , patterns)
610640 } ) ;
611641 let glob_elapsed = now. elapsed ( ) ;
612642 trace ! ( "Glob expansion completed in {:?}" , glob_elapsed) ;
@@ -1412,6 +1442,42 @@ mod tests {
14121442 use std:: sync:: { mpsc, Barrier , Mutex } ;
14131443 use std:: thread;
14141444
1445+ #[ test]
1446+ fn recursive_environment_pattern_filter_only_returns_recursive_globs ( ) {
1447+ let patterns = vec ! [
1448+ PathBuf :: from( ".venv" ) ,
1449+ PathBuf :: from( "*/.venv" ) ,
1450+ PathBuf :: from( "**/.venv" ) ,
1451+ PathBuf :: from( "/workspace/**/venv" ) ,
1452+ ] ;
1453+
1454+ assert_eq ! (
1455+ recursive_environment_patterns( & patterns)
1456+ . cloned( )
1457+ . collect:: <Vec <_>>( ) ,
1458+ vec![
1459+ PathBuf :: from( "**/.venv" ) ,
1460+ PathBuf :: from( "/workspace/**/venv" )
1461+ ]
1462+ ) ;
1463+ }
1464+
1465+ #[ test]
1466+ fn configure_pattern_expansion_filters_non_directories ( ) {
1467+ let temp = tempfile:: tempdir ( ) . unwrap ( ) ;
1468+ let directory = temp. path ( ) . join ( "env" ) ;
1469+ let file = temp. path ( ) . join ( "python.exe" ) ;
1470+ std:: fs:: create_dir ( & directory) . unwrap ( ) ;
1471+ std:: fs:: write ( & file, "" ) . unwrap ( ) ;
1472+
1473+ assert_eq ! (
1474+ expand_configure_directory_patterns(
1475+ "environmentDirectories" ,
1476+ vec![ temp. path( ) . join( "*" ) ] ,
1477+ ) ,
1478+ vec![ directory]
1479+ ) ;
1480+ }
14151481 #[ derive( Default ) ]
14161482 struct RecordingReporter {
14171483 environments : Mutex < Vec < PythonEnvironment > > ,
0 commit comments