@@ -329,8 +329,6 @@ class NativePythonFinderImpl implements NativePythonFinder {
329329 // Get all extra search paths including legacy settings and new searchPaths
330330 const extraSearchPaths = await getAllExtraSearchPaths ( ) ;
331331
332- traceLog ( 'Final environment directories:' , extraSearchPaths ) ;
333-
334332 const options : ConfigurationOptions = {
335333 workspaceDirectories : this . api . getPythonProjects ( ) . map ( ( item ) => item . uri . fsPath ) ,
336334 environmentDirectories : extraSearchPaths ,
@@ -384,49 +382,6 @@ function getPythonSettingAndUntildify<T>(name: string, scope?: Uri): T | undefin
384382 return value ;
385383}
386384
387- /**
388- * Checks if a search path is a regex pattern.
389- * A path is considered a regex pattern if it contains regex special characters
390- * but is not a Windows path (which can contain backslashes).
391- * @param searchPath The search path to check
392- * @returns true if the path is a regex pattern, false otherwise
393- */
394- function isRegexSearchPattern ( searchPath : string ) : boolean {
395- // Check if it's a regex pattern (contains regex special characters)
396- // Note: Windows paths contain backslashes, so we need to be more careful
397- const regexChars = / [ * ? [ \] { } ( ) ^ $ + | \\ ] / ;
398- const hasBackslash = searchPath . includes ( '\\' ) ;
399- const isWindowsPath = hasBackslash && ( searchPath . match ( / ^ [ A - Z a - z ] : \\ / ) || searchPath . match ( / ^ \\ \\ [ ^ \\ ] + \\ / ) ) ;
400- return regexChars . test ( searchPath ) && ! isWindowsPath ;
401- }
402-
403- /**
404- * Extracts the environment directory from a Python executable path.
405- * This follows the pattern: executable -> bin -> env -> search directory
406- * @param executablePath Path to Python executable
407- * @returns The environment directory path, or undefined if not found
408- */
409- function extractEnvironmentDirectory ( executablePath : string ) : string | undefined {
410- try {
411- // TODO: This logic may need to be adjusted for Windows paths (esp with Conda as doesn't use Scripts folder?)
412- const environmentDir = path . dirname ( path . dirname ( path . dirname ( executablePath ) ) ) ;
413- if ( environmentDir && environmentDir !== path . dirname ( environmentDir ) ) {
414- traceLog ( 'Extracted environment directory:' , environmentDir , 'from executable:' , executablePath ) ;
415- return environmentDir ;
416- } else {
417- traceLog (
418- 'Warning: identified executable python at' ,
419- executablePath ,
420- 'not configured in correct folder structure, skipping' ,
421- ) ;
422- return undefined ;
423- }
424- } catch ( error ) {
425- traceLog ( 'Error extracting environment directory from:' , executablePath , 'Error:' , error ) ;
426- return undefined ;
427- }
428- }
429-
430385/**
431386 * Gets all extra environment search paths from various configuration sources.
432387 * Combines legacy python settings (with migration), globalSearchPaths, and workspaceSearchPaths.
@@ -443,74 +398,37 @@ async function getAllExtraSearchPaths(): Promise<string[]> {
443398 const customVenvDirs = getCustomVirtualEnvDirsLegacy ( ) ;
444399 searchDirectories . push ( ...customVenvDirs ) ;
445400 traceLog ( 'Added legacy custom venv directories (not covered by globalSearchPaths):' , customVenvDirs ) ;
446- } else {
447- traceLog ( 'Skipping legacy custom venv directories - they are covered by globalSearchPaths' ) ;
448401 }
449402
450- // Get globalSearchPaths (absolute paths, no regex)
403+ // Get globalSearchPaths
451404 const globalSearchPaths = getGlobalSearchPaths ( ) ;
452- traceLog ( 'Retrieved globalSearchPaths:' , globalSearchPaths ) ;
453- for ( const globalPath of globalSearchPaths ) {
454- try {
455- if ( ! globalPath || globalPath . trim ( ) === '' ) {
456- continue ;
457- }
458- const trimmedPath = globalPath . trim ( ) ;
459- traceLog ( 'Processing global search path:' , trimmedPath ) ;
460- // Simply add the trimmed global path
461- searchDirectories . push ( trimmedPath ) ;
462- } catch ( error ) {
463- traceLog ( 'Error processing global search path:' , globalPath , 'Error:' , error ) ;
464- }
465- }
405+ searchDirectories . push ( ...globalSearchPaths ) ;
466406
467- // Get workspaceSearchPaths (can include regex patterns)
407+ // Get workspaceSearchPaths
468408 const workspaceSearchPaths = getWorkspaceSearchPaths ( ) ;
469- traceLog ( 'Retrieved workspaceSearchPaths:' , workspaceSearchPaths ) ;
470- for ( const searchPath of workspaceSearchPaths ) {
471- try {
472- if ( ! searchPath || searchPath . trim ( ) === '' ) {
473- continue ;
474- }
475409
476- const trimmedPath = searchPath . trim ( ) ;
477- const isRegexPattern = isRegexSearchPattern ( trimmedPath ) ;
478-
479- if ( isRegexPattern ) {
480- // Search for Python executables using the regex pattern
481- // Look for common Python executable names within the pattern
482- const pythonExecutablePatterns = isWindows ( )
483- ? [ `${ trimmedPath } /**/python.exe` , `${ trimmedPath } /**/python3.exe` ]
484- : [ `${ trimmedPath } /**/python` , `${ trimmedPath } /**/python3` ] ;
410+ // Resolve relative paths against workspace folders
411+ for ( const searchPath of workspaceSearchPaths ) {
412+ if ( ! searchPath || searchPath . trim ( ) === '' ) {
413+ continue ;
414+ }
485415
486- traceLog ( 'Searching for Python executables with patterns:' , pythonExecutablePatterns ) ;
487- for ( const pattern of pythonExecutablePatterns ) {
488- try {
489- const foundFiles = await workspace . findFiles ( pattern , null ) ;
490- traceLog (
491- 'Python executable search found' ,
492- foundFiles . length ,
493- 'files matching pattern:' ,
494- pattern ,
495- ) ;
416+ const trimmedPath = searchPath . trim ( ) ;
496417
497- for ( const file of foundFiles ) {
498- // given the executable path, extract and save the environment directory
499- const environmentDir = extractEnvironmentDirectory ( file . fsPath ) ;
500- if ( environmentDir ) {
501- searchDirectories . push ( environmentDir ) ;
502- }
503- }
504- } catch ( error ) {
505- traceLog ( 'Error searching for Python executables with pattern:' , pattern , 'Error:' , error ) ;
506- }
418+ if ( path . isAbsolute ( trimmedPath ) ) {
419+ // Absolute path - use as is
420+ searchDirectories . push ( trimmedPath ) ;
421+ } else {
422+ // Relative path - resolve against all workspace folders
423+ const workspaceFolders = workspace . workspaceFolders ;
424+ if ( workspaceFolders ) {
425+ for ( const workspaceFolder of workspaceFolders ) {
426+ const resolvedPath = path . resolve ( workspaceFolder . uri . fsPath , trimmedPath ) ;
427+ searchDirectories . push ( resolvedPath ) ;
507428 }
508429 } else {
509- // If it's not a regex, treat it as a normal directory path and just add it
510- searchDirectories . push ( trimmedPath ) ;
430+ traceLog ( 'Warning: No workspace folders found for relative path:' , trimmedPath ) ;
511431 }
512- } catch ( error ) {
513- traceLog ( 'Error processing workspace search path:' , searchPath , 'Error:' , error ) ;
514432 }
515433 }
516434
@@ -535,7 +453,6 @@ function getGlobalSearchPaths(): string[] {
535453 const inspection = envConfig . inspect < string [ ] > ( 'globalSearchPaths' ) ;
536454
537455 const globalPaths = inspection ?. globalValue || [ ] ;
538- traceLog ( 'Retrieved globalSearchPaths:' , globalPaths ) ;
539456 return untildifyArray ( globalPaths ) ;
540457 } catch ( error ) {
541458 traceLog ( 'Error getting globalSearchPaths:' , error ) ;
@@ -544,27 +461,29 @@ function getGlobalSearchPaths(): string[] {
544461}
545462
546463/**
547- * Gets workspaceSearchPaths setting with workspace precedence.
548- * Gets the most specific workspace-level setting available.
464+ * Gets the most specific workspace-level setting available for workspaceSearchPaths.
549465 */
550466function getWorkspaceSearchPaths ( ) : string [ ] {
551467 try {
552468 const envConfig = getConfiguration ( 'python-env' ) ;
553469 const inspection = envConfig . inspect < string [ ] > ( 'workspaceSearchPaths' ) ;
554470
471+ if ( inspection ?. globalValue ) {
472+ traceLog (
473+ 'Error: python-env.workspaceSearchPaths is set at the user/global level, but this setting can only be set at the workspace or workspace folder level.' ,
474+ ) ;
475+ }
476+
555477 // For workspace settings, prefer workspaceFolder > workspace
556478 if ( inspection ?. workspaceFolderValue ) {
557- traceLog ( 'Using workspaceFolder level workspaceSearchPaths setting' ) ;
558479 return inspection . workspaceFolderValue ;
559480 }
560481
561482 if ( inspection ?. workspaceValue ) {
562- traceLog ( 'Using workspace level workspaceSearchPaths setting' ) ;
563483 return inspection . workspaceValue ;
564484 }
565485
566486 // Default empty array (don't use global value for workspace settings)
567- traceLog ( 'No workspaceSearchPaths setting found at workspace level, using empty array' ) ;
568487 return [ ] ;
569488 } catch ( error ) {
570489 traceLog ( 'Error getting workspaceSearchPaths:' , error ) ;
@@ -607,12 +526,9 @@ async function handleLegacyPythonSettingsMigration(): Promise<boolean> {
607526
608527 if ( globalLegacyPaths . length === 0 ) {
609528 // No legacy settings exist, so they're "covered" (nothing to worry about)
610- traceLog ( 'No legacy python settings found' ) ;
611529 return true ;
612530 }
613531
614- traceLog ( 'Found legacy python settings - global paths:' , globalLegacyPaths ) ;
615-
616532 // Check if legacy paths are already in globalSearchPaths
617533 const globalSearchPathsInspection = envConfig . inspect < string [ ] > ( 'globalSearchPaths' ) ;
618534 const currentGlobalSearchPaths = globalSearchPathsInspection ?. globalValue || [ ] ;
@@ -630,7 +546,10 @@ async function handleLegacyPythonSettingsMigration(): Promise<boolean> {
630546 // Need to migrate - add legacy paths to globalSearchPaths
631547 const combinedGlobalPaths = Array . from ( new Set ( [ ...currentGlobalSearchPaths , ...globalLegacyPaths ] ) ) ;
632548 await envConfig . update ( 'globalSearchPaths' , combinedGlobalPaths , true ) ; // true = global/user level
633- traceLog ( 'Migrated legacy global python settings to globalSearchPaths. Combined paths:' , combinedGlobalPaths ) ;
549+ traceLog (
550+ 'Migrated legacy global python settings to globalSearchPaths. globalSearchPaths setting is now:' ,
551+ combinedGlobalPaths ,
552+ ) ;
634553
635554 // Show notification to user about migration
636555 if ( ! migrationNotificationShown ) {
0 commit comments