Skip to content

Commit 7a3f5a9

Browse files
Copiloteleanorjboyd
andcommitted
Address PR feedback - improve function naming, warning messages, and code organization
Co-authored-by: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com>
1 parent c885a3b commit 7a3f5a9

2 files changed

Lines changed: 63 additions & 70 deletions

File tree

src/managers/common/nativePythonFinder.ts

Lines changed: 46 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
423414
async 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-Za-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(/python\*$/, '') : 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
*/
504501
function 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

src/test/managers/nativePythonFinder.unit.test.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,34 +62,38 @@ suite('NativePythonFinder SearchPaths Tests', () => {
6262

6363
test('should handle Windows paths specially', () => {
6464
const windowsPath = 'C:\\Users\\user\\envs';
65-
const regexChars = /[*?[\]{}()^$+|]/;
65+
const regexChars = /[*?[\]{}()^$+|\\]/; // Added backslash to match implementation
6666

6767
// Windows paths contain backslashes which are regex characters
6868
// Our implementation should handle this case by checking for valid Windows path patterns
6969
assert.ok(regexChars.test(windowsPath), 'Windows paths contain regex chars');
7070

71-
// Test that we can identify valid Windows paths
72-
const isWindowsPath = windowsPath.match(/^[A-Za-z]:\\/) || windowsPath.match(/^\\\\[^\\]+\\/);
71+
// Test that we can identify valid Windows paths and NOT treat them as regex
72+
const hasBackslash = windowsPath.includes('\\');
73+
const isWindowsPath = hasBackslash && (windowsPath.match(/^[A-Za-z]:\\/) || windowsPath.match(/^\\\\[^\\]+\\/));
74+
const isRegexPattern = regexChars.test(windowsPath) && !isWindowsPath;
75+
7376
assert.ok(isWindowsPath, 'Should recognize Windows path pattern');
77+
assert.ok(!isRegexPattern, 'Should not treat Windows path as regex pattern');
7478
});
7579
});
7680

77-
suite('Great-grandparent path extraction', () => {
78-
test('should extract correct great-grandparent from executable path', () => {
81+
suite('Environment directory path extraction', () => {
82+
test('should extract correct environment directory from executable path', () => {
7983
const executablePath = '/home/user/.virtualenvs/myenv/bin/python';
8084
const expected = '/home/user/.virtualenvs';
8185

8286
// Test path manipulation logic
83-
const greatGrandParent = path.dirname(path.dirname(path.dirname(executablePath)));
84-
assert.strictEqual(greatGrandParent, expected);
87+
const environmentDir = path.dirname(path.dirname(path.dirname(executablePath)));
88+
assert.strictEqual(environmentDir, expected);
8589
});
8690

8791
test('should handle deep nested paths', () => {
8892
const executablePath = '/very/deep/nested/path/to/env/bin/python';
8993
const expected = '/very/deep/nested/path/to';
9094

91-
const greatGrandParent = path.dirname(path.dirname(path.dirname(executablePath)));
92-
assert.strictEqual(greatGrandParent, expected);
95+
const environmentDir = path.dirname(path.dirname(path.dirname(executablePath)));
96+
assert.strictEqual(environmentDir, expected);
9397
});
9498

9599
test('should handle shallow paths gracefully', () => {
@@ -214,8 +218,11 @@ suite('NativePythonFinder SearchPaths Tests', () => {
214218
const emptyArray2: string[] = [];
215219
const nonEmptyArray = ['path1'];
216220

221+
// Empty arrays should be equal (every element matches)
217222
assert.ok(emptyArray1.every((val, index) => val === emptyArray2[index]));
218-
assert.ok(!emptyArray1.every((val, index) => val === nonEmptyArray[index]));
223+
224+
// Empty array should not match non-empty array (lengths differ)
225+
assert.ok(emptyArray1.length !== nonEmptyArray.length);
219226
});
220227
});
221228

0 commit comments

Comments
 (0)