Improve Python environment selection with auto-discovery - #16
Conversation
- Add VS Code Python extension integration to use already-selected interpreter - Auto-discover conda environments, workspace venvs, and system Python - Replace file browser with QuickPick dropdown showing all discovered environments - Add status bar item showing current Python environment (click to change) - Validate dependencies before accepting environment selection - Update documentation (README, CHANGELOG, CONTRIBUTING) Closes #15 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove explicit activationEvents from package.json (VS Code auto-generates from contributes) - Move CLAUDE.md to .claude/ directory - Update package-lock.json from WSL npm install Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add npm run vsce:package to create .vsix file - Add npm run vsce:install to package and install locally - Add @vscode/vsce as dev dependency - Update CONTRIBUTING.md with new workflow documentation - Add netcdf-viewer.vsix to .gitignore Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add onStartupFinished activation event so status bar shows immediately - Change status bar icon to beaker with "NC:" prefix for clarity - Fix environment name display to show conda env name instead of "bin" - Skip "bin" and "Scripts" folders when extracting environment name Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This pull request significantly improves the Python environment selection experience by implementing automatic discovery of Python installations and integrating with the VS Code Python extension. The changes replace the manual file browser approach with an intelligent QuickPick dropdown that shows discovered environments with friendly names.
Changes:
- Added automatic Python environment discovery (VS Code Python extension, conda, workspace venvs, system Python)
- Implemented QuickPick interface for environment selection with validation
- Added status bar indicator showing current Python environment
Reviewed changes
Copilot reviewed 7 out of 9 changed files in this pull request and generated 17 comments.
Show a summary per file
| File | Description |
|---|---|
| src/python/environmentDiscovery.ts | New module implementing Python environment discovery logic for various sources |
| src/extension.ts | Integrated QuickPick selector, status bar item, and configuration listener for Python environment |
| package.json | Removed redundant activationEvents, added vsce scripts and dependency |
| README.md | Updated documentation to describe automatic environment detection and new selection UI |
| CONTRIBUTING.md | Added new module to project structure documentation and new packaging commands |
| CHANGELOG.md | Documented new features and improvements |
| .gitignore | Added build artifact exclusion |
| CLAUDE.md | Removed (appears to be internal documentation) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Try the newer API first (Python extension 2023.4+) | ||
| const execDetails = pythonExt.exports?.settings?.getExecutionDetails?.( | ||
| vscode.workspace.workspaceFolders?.[0]?.uri | ||
| ); | ||
| if (execDetails?.execCommand?.[0]) { | ||
| const pythonPath = execDetails.execCommand[0]; | ||
| const version = await getPythonVersion(pythonPath); | ||
| return { | ||
| name: `VS Code Python${version ? ` (${version})` : ''}`, | ||
| path: pythonPath, | ||
| source: 'vscode-python', | ||
| version, | ||
| }; | ||
| } | ||
|
|
||
| // Fallback to older API | ||
| const pythonPath = pythonExt.exports?.settings?.getExecutionDetails?.()?.execCommand?.[0]; |
There was a problem hiding this comment.
The VS Code Python extension API is accessed without proper type definitions, using optional chaining throughout. While this handles the case where the API doesn't exist, it's fragile. The comment mentions "Python extension 2023.4+" but there's no documentation of what happens with newer versions. Consider documenting the expected API structure or using the official Python extension API types if available.
| /** | ||
| * Represents a discovered Python environment | ||
| */ | ||
| export interface PythonEnvironment { | ||
| name: string; |
There was a problem hiding this comment.
The new Python environment discovery functionality lacks test coverage. Given that the existing test file has comprehensive tests for other features like NetCDFTreeProvider, consider adding tests for the environment discovery functions, especially for edge cases like missing conda, invalid paths, and the VS Code Python extension integration.
| const workspaceFolders = vscode.workspace.workspaceFolders; | ||
|
|
||
| if (!workspaceFolders) { | ||
| return environments; |
There was a problem hiding this comment.
The venv directory list includes '.env' which is commonly used for environment variable files (e.g., by dotenv), not virtual environments. This could lead to false positives. Consider removing '.env' from the list unless there's a specific use case for it.
| return environments; | |
| const venvDirs = ['.venv', 'venv', 'env']; |
| } | ||
|
|
||
| // Check if it's a venv (look for common venv folder names in the path) |
There was a problem hiding this comment.
In the getEnvironmentDisplayName function, the check for venv indicators uses path.sep + indicator + path.sep, which won't match if the venv directory is at the end of the path (which it typically is, e.g., /path/to/.venv/bin/python). The current code would fail to identify this as a venv. Consider checking if the path contains the indicator followed by path separators leading to 'bin' or 'Scripts'.
| } | |
| // Check if it's a venv (look for common venv folder names in the path) | |
| const pathSegments = pythonPath.split(path.sep); | |
| for (const indicator of venvIndicators) { | |
| const index = pathSegments.lastIndexOf(indicator); | |
| if (index !== -1) { | |
| const nextSegment = pathSegments[index + 1]; | |
| // Accept if the indicator is the last segment, or is followed by bin/Scripts | |
| if (!nextSegment || nextSegment === 'bin' || nextSegment === 'Scripts') { | |
| return `venv: ${indicator}`; | |
| } |
| validateInput: (value) => { | ||
| if (!value.trim()) { | ||
| return 'Please enter a path'; | ||
| } | ||
| return undefined; | ||
| }, |
There was a problem hiding this comment.
The validation input function only checks if the value is empty, but doesn't validate that the path exists or is executable. A user could enter an invalid path that passes validation but fails later. Consider adding basic path validation such as checking if the file exists and is executable, or at least checking if it looks like a valid path.
| /** | ||
| * Gets the Python version for a given interpreter path | ||
| */ | ||
| export async function getPythonVersion(pythonPath: string): Promise<string | undefined> { |
There was a problem hiding this comment.
The Python version regex pattern /Python (\d+\.\d+\.\d+)/ expects a three-part version number (e.g., "3.9.7"), but some Python installations may output version strings in different formats (e.g., "Python 3.9.7+" for modified builds, or "Python 3.12.0rc1" for release candidates). Consider making the regex more flexible to handle these variations.
| export async function getPythonVersion(pythonPath: string): Promise<string | undefined> { | |
| const match = output.match(/Python (\d+(?:\.\d+){1,2})(?:\D|$)/); |
| export interface PythonEnvironment { | ||
| name: string; | ||
| path: string; | ||
| source: 'vscode-python' | 'conda' | 'venv' | 'system' | 'manual'; |
There was a problem hiding this comment.
The PythonEnvironment interface includes a 'manual' source type, but this source is never actually used in the code. Environments selected manually via browse or text input don't create a PythonEnvironment object with source='manual'. Consider either using this source type when manual selection occurs, or removing it from the interface to avoid confusion.
| source: 'vscode-python' | 'conda' | 'venv' | 'system' | 'manual'; | |
| source: 'vscode-python' | 'conda' | 'venv' | 'system'; |
|
|
||
| /** | ||
| * Helper function to run a command and return stdout | ||
| */ |
There was a problem hiding this comment.
The timeout of 10000ms (10 seconds) for command execution may be too short for slow systems or environments with many conda environments. Consider increasing this timeout or making it configurable, especially for the conda discovery which might take longer to enumerate all environments.
| */ | |
| const envTimeout = process.env.PYTHON_ENV_DISCOVERY_TIMEOUT_MS; | |
| let timeout = 30000; // Default to 30 seconds to better support slow systems or many environments | |
| if (envTimeout) { | |
| const parsed = Number(envTimeout); | |
| if (!Number.isNaN(parsed) && parsed > 0) { | |
| timeout = parsed; | |
| } | |
| } | |
| execFile(command, args, { timeout }, (error, stdout, stderr) => { |
| ? path.join(venvPath, 'Scripts', 'python.exe') | ||
| : path.join(venvPath, 'bin', 'python'); | ||
|
|
||
| if (fs.existsSync(pythonPath)) { | ||
| const version = await getPythonVersion(pythonPath); | ||
| environments.push({ | ||
| name: `venv: ${venvDir}${version ? ` (${version})` : ''}`, | ||
| path: pythonPath, | ||
| source: 'venv', |
There was a problem hiding this comment.
The fs.existsSync calls are synchronous and will block the event loop. Since this code discovers multiple environments, it could perform many synchronous filesystem checks. Consider using the asynchronous fs.promises.access() or fs.promises.stat() instead to avoid blocking, especially when checking multiple venv directories across multiple workspace folders.
| ? path.join(venvPath, 'Scripts', 'python.exe') | |
| : path.join(venvPath, 'bin', 'python'); | |
| if (fs.existsSync(pythonPath)) { | |
| const version = await getPythonVersion(pythonPath); | |
| environments.push({ | |
| name: `venv: ${venvDir}${version ? ` (${version})` : ''}`, | |
| path: pythonPath, | |
| source: 'venv', | |
| try { | |
| await fs.promises.access(pythonPath); | |
| } catch { | |
| // If the Python executable does not exist or is not accessible, skip this venv | |
| continue; | |
| } | |
| const version = await getPythonVersion(pythonPath); | |
| environments.push({ | |
| name: `venv: ${venvDir}${version ? ` (${version})` : ''}`, | |
| path: pythonPath, | |
| source: 'venv', | |
| version, | |
| }); |
| const items: (vscode.QuickPickItem & { env?: PythonEnvironment })[] = environments.map((env) => ({ | ||
| label: env.name, | ||
| description: env.path, | ||
| detail: env.source === 'vscode-python' ? '$(star) Recommended - from VS Code Python extension' : undefined, | ||
| env, | ||
| })); | ||
|
|
||
| // Add option to browse manually | ||
| items.push({ | ||
| label: '$(folder) Browse...', | ||
| description: 'Select Python executable manually', | ||
| alwaysShow: true, | ||
| }); | ||
|
|
||
| // Add option to enter path manually | ||
| items.push({ | ||
| label: '$(edit) Enter path...', | ||
| description: 'Type a custom Python path', | ||
| alwaysShow: true, | ||
| }); |
There was a problem hiding this comment.
The QuickPick items for manual selection use icon codes like $(folder) and $(edit), but there's no consistent icon used for the discovered environments. Consider adding icons to the discovered environment items as well (e.g., $(symbol-misc) for VS Code Python, $(package) for conda, $(folder-library) for venv) to improve visual consistency and make the list easier to scan.
- Remove unused 'manual' source type from PythonEnvironment interface - Remove '.env' from venvDirs (commonly used for env variable files) - Use async fs.promises.access instead of sync fs.existsSync - Use fs.realpathSync for symlink resolution in duplicate detection - Make Python version regex more flexible for rc/+ versions - Increase command timeout from 10s to 30s for slow systems - Include stderr in error messages for better debugging - Fix venv indicator matching to verify bin/Scripts structure - Add debouncing to status bar updates on config changes - Add icons to discovered environment QuickPick items - Show helpful placeholder when no environments found Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Summary
Changes
New Files
src/python/environmentDiscovery.ts- Python environment detection and discovery moduleModified Files
src/extension.ts- QuickPick selector, status bar item, config listenerREADME.md- Updated Python configuration documentationCHANGELOG.md- Added feature entriesCONTRIBUTING.md- Updated project structurepackage.json- Removed redundant activationEventsTest Plan
Closes #15
🤖 Generated with Claude Code