@@ -326,23 +326,14 @@ class NativePythonFinderImpl implements NativePythonFinder {
326326 * Must be invoked when ever there are changes to any data related to the configuration details.
327327 */
328328 private async configure ( ) {
329- // Get custom virtual environment directories from legacy python settings
330- const customVenvDirs = getCustomVirtualEnvDirs ( ) ;
331-
332- // Get additional search paths from the new searchPaths setting
329+ // Get all extra search paths including legacy settings and new searchPaths
333330 const extraSearchPaths = await getAllExtraSearchPaths ( ) ;
334331
335- // Combine and deduplicate all environment directories
336- const allEnvironmentDirectories = [ ...customVenvDirs , ...extraSearchPaths ] ;
337- const environmentDirectories = Array . from ( new Set ( allEnvironmentDirectories ) ) ;
338-
339- traceLog ( 'Legacy custom venv directories:' , customVenvDirs ) ;
340- traceLog ( 'Extra search paths from settings:' , extraSearchPaths ) ;
341- traceLog ( 'Final combined environment directories:' , environmentDirectories ) ;
332+ traceLog ( 'Final environment directories:' , extraSearchPaths ) ;
342333
343334 const options : ConfigurationOptions = {
344335 workspaceDirectories : this . api . getPythonProjects ( ) . map ( ( item ) => item . uri . fsPath ) ,
345- environmentDirectories,
336+ environmentDirectories : extraSearchPaths ,
346337 condaExecutable : getPythonSettingAndUntildify < string > ( 'condaPath' ) ,
347338 poetryExecutable : getPythonSettingAndUntildify < string > ( 'poetryPath' ) ,
348339 cacheDirectory : this . cacheDirectory ?. fsPath ,
@@ -394,35 +385,40 @@ function getPythonSettingAndUntildify<T>(name: string, scope?: Uri): T | undefin
394385}
395386
396387/**
397- * Extracts the great-grandparent directory from a Python executable path.
388+ * Extracts the environment directory from a Python executable path.
398389 * This follows the pattern: executable -> bin -> env -> search directory
399390 * @param executablePath Path to Python executable
400- * @returns The great-grandparent directory path, or undefined if not found
391+ * @returns The environment directory path, or undefined if not found
401392 */
402- function extractGreatGrandparentDirectory ( executablePath : string ) : string | undefined {
393+ function extractEnvironmentDirectory ( executablePath : string ) : string | undefined {
403394 try {
404- const grandGrandParent = path . dirname ( path . dirname ( path . dirname ( executablePath ) ) ) ;
405- if ( grandGrandParent && grandGrandParent !== path . dirname ( grandGrandParent ) ) {
406- traceLog ( 'Extracted great-grandparent directory:' , grandGrandParent , 'from executable:' , executablePath ) ;
407- return grandGrandParent ;
395+ const environmentDir = path . dirname ( path . dirname ( path . dirname ( executablePath ) ) ) ;
396+ if ( environmentDir && environmentDir !== path . dirname ( environmentDir ) ) {
397+ traceLog ( 'Extracted environment directory:' , environmentDir , 'from executable:' , executablePath ) ;
398+ return environmentDir ;
408399 } else {
409- traceLog ( 'Warning: Could not find valid great-grandparent directory for executable:' , executablePath ) ;
400+ traceLog ( 'Warning: identified executable python at' , executablePath , 'not configured in correct folder structure, skipping' ) ;
410401 return undefined ;
411402 }
412403 } catch ( error ) {
413- traceLog ( 'Error extracting great-grandparent directory from:' , executablePath , 'Error:' , error ) ;
404+ traceLog ( 'Error extracting environment directory from:' , executablePath , 'Error:' , error ) ;
414405 return undefined ;
415406 }
416407}
417408
418409/**
419410 * Gets all extra environment search paths from various configuration sources.
420- * Combines python.venvFolders (for migration) and python-env.searchPaths settings.
411+ * Combines python.venvFolders (for migration), python-env.searchPaths settings, and legacy custom venv dirs .
421412 * @returns Array of search directory paths
422413 */
423414async function getAllExtraSearchPaths ( ) : Promise < string [ ] > {
424415 const searchDirectories : string [ ] = [ ] ;
425416
417+ // Get custom virtual environment directories from legacy python settings
418+ const customVenvDirs = getCustomVirtualEnvDirs ( ) ;
419+ searchDirectories . push ( ...customVenvDirs ) ;
420+ traceLog ( 'Added legacy custom venv directories:' , customVenvDirs ) ;
421+
426422 // Handle migration from python.venvFolders to python-env.searchPaths
427423 await handleVenvFoldersMigration ( ) ;
428424
@@ -440,35 +436,32 @@ async function getAllExtraSearchPaths(): Promise<string[]> {
440436
441437 // Check if it's a regex pattern (contains regex special characters)
442438 // Note: Windows paths contain backslashes, so we need to be more careful
443- const regexChars = / [ * ? [ \] { } ( ) ^ $ + | ] / ;
439+ const regexChars = / [ * ? [ \] { } ( ) ^ $ + | \\ ] / ;
444440 const hasBackslash = trimmedPath . includes ( '\\' ) ;
445441 const isWindowsPath = hasBackslash && ( trimmedPath . match ( / ^ [ A - Z a - z ] : \\ / ) || trimmedPath . match ( / ^ \\ \\ [ ^ \\ ] + \\ / ) ) ;
446- const isRegexPattern = regexChars . test ( trimmedPath ) || ( hasBackslash && ! isWindowsPath ) ;
442+ const isRegexPattern = regexChars . test ( trimmedPath ) && ! isWindowsPath ;
447443
448444 if ( isRegexPattern ) {
449445 traceLog ( 'Processing regex pattern for Python environment discovery:' , trimmedPath ) ;
450446 traceLog ( 'Warning: Using regex patterns in searchPaths may cause performance issues due to file system scanning' ) ;
451447
452- // Use workspace.findFiles to search for python executables
453- const foundFiles = await workspace . findFiles ( trimmedPath , null ) ;
454- traceLog ( 'Regex pattern search found' , foundFiles . length , 'files matching pattern:' , trimmedPath ) ;
448+ // Modify the regex to search for directories that might contain Python executables
449+ // Instead of searching for executables directly, we append python pattern to find parent directories
450+ const directoryPattern = trimmedPath . endsWith ( 'python*' ) ? trimmedPath . replace ( / p y t h o n \* $ / , '' ) : trimmedPath ;
451+
452+ // Use workspace.findFiles to search for directories or python-related files
453+ const foundFiles = await workspace . findFiles ( directoryPattern + '**/python*' , null ) ;
454+ traceLog ( 'Regex pattern search found' , foundFiles . length , 'files matching pattern:' , directoryPattern + '**/python*' ) ;
455455
456456 for ( const file of foundFiles ) {
457457 const filePath = file . fsPath ;
458458 traceLog ( 'Evaluating file from regex search:' , filePath ) ;
459459
460- // Check if it's likely a python executable
461- if ( filePath . toLowerCase ( ) . includes ( 'python' ) || path . basename ( filePath ) . startsWith ( 'python' ) ) {
462- traceLog ( 'File appears to be a Python executable:' , filePath ) ;
463-
464- // Get great-grandparent folder (file -> bin -> env -> this)
465- const greatGrandParent = extractGreatGrandparentDirectory ( filePath ) ;
466- if ( greatGrandParent ) {
467- searchDirectories . push ( greatGrandParent ) ;
468- traceLog ( 'Added search directory from regex match:' , greatGrandParent ) ;
469- }
470- } else {
471- traceLog ( 'File does not appear to be a Python executable, skipping:' , filePath ) ;
460+ // Get environment folder (file -> bin -> env -> this)
461+ const environmentDir = extractEnvironmentDirectory ( filePath ) ;
462+ if ( environmentDir ) {
463+ searchDirectories . push ( environmentDir ) ;
464+ traceLog ( 'Added search directory from regex match:' , environmentDir ) ;
472465 }
473466 }
474467
@@ -480,9 +473,13 @@ async function getAllExtraSearchPaths(): Promise<string[]> {
480473 searchDirectories . push ( trimmedPath ) ;
481474 traceLog ( 'Added directory as search path:' , trimmedPath ) ;
482475 }
483- // If path doesn't exist, try to check if it could be a directory that might exist later
476+ // If path doesn't exist, add it anyway as it might exist in the future
477+ // This handles cases where:
478+ // - Virtual environments might be created later
479+ // - Network drives that might not be mounted yet
480+ // - Symlinks to directories that might be created
484481 else {
485- traceLog ( 'Path does not exist currently, treating as potential directory for future resolution:' , trimmedPath ) ;
482+ traceLog ( 'Path does not exist currently, adding for future resolution when environments may be created :' , trimmedPath ) ;
486483 searchDirectories . push ( trimmedPath ) ;
487484 }
488485 } catch ( error ) {
@@ -503,32 +500,22 @@ async function getAllExtraSearchPaths(): Promise<string[]> {
503500 */
504501function getSearchPathsWithPrecedence ( ) : string [ ] {
505502 try {
506- // Get the workspace folders for proper precedence checking
507- const workspaceFolders = workspace . workspaceFolders ;
508-
509- // Try workspaceFolder level first (most specific)
510- if ( workspaceFolders && workspaceFolders . length > 0 ) {
511- for ( const folder of workspaceFolders ) {
512- const config = getConfiguration ( 'python-env' , folder . uri ) ;
513- const inspection = config . inspect < string [ ] > ( 'searchPaths' ) ;
514-
515- if ( inspection ?. workspaceFolderValue ) {
516- traceLog ( 'Using workspaceFolder level searchPaths setting from:' , folder . uri . fsPath ) ;
517- return untildifyArray ( inspection . workspaceFolderValue ) ;
518- }
519- }
520- }
521-
522- // Try workspace level next
503+ // Use VS Code configuration inspection to handle precedence automatically
523504 const config = getConfiguration ( 'python-env' ) ;
524505 const inspection = config . inspect < string [ ] > ( 'searchPaths' ) ;
525506
507+ // VS Code automatically handles precedence: workspaceFolder -> workspace -> user
508+ // We check each level in order and return the first one found
509+ if ( inspection ?. workspaceFolderValue ) {
510+ traceLog ( 'Using workspaceFolder level searchPaths setting' ) ;
511+ return untildifyArray ( inspection . workspaceFolderValue ) ;
512+ }
513+
526514 if ( inspection ?. workspaceValue ) {
527515 traceLog ( 'Using workspace level searchPaths setting' ) ;
528516 return untildifyArray ( inspection . workspaceValue ) ;
529517 }
530518
531- // Fall back to user level
532519 if ( inspection ?. globalValue ) {
533520 traceLog ( 'Using user level searchPaths setting' ) ;
534521 return untildifyArray ( inspection . globalValue ) ;
@@ -564,7 +551,6 @@ async function handleVenvFoldersMigration(): Promise<void> {
564551 const venvFolders = venvFoldersInspection ?. globalValue ;
565552
566553 if ( ! venvFolders || venvFolders . length === 0 ) {
567- traceLog ( 'No python.venvFolders setting found, skipping migration' ) ;
568554 return ;
569555 }
570556
0 commit comments