-
Notifications
You must be signed in to change notification settings - Fork 2
feat: Clickable links, hover tooltips, switch env, auto interpreter #69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,8 +21,11 @@ No build step — the extension runs directly from JS source files. | |
|
|
||
| ``` | ||
| extension.js (activate: check conda, register commands, wire status bar, config listener) | ||
| ├── commands.js (4 command handlers: build, activate, export, delete) | ||
| ├── statusBarItems.js (CustomStatusBarItem class, 4 items, showAll/hideAll) | ||
| ├── commands.js (5 command handlers: build, activate, export, delete, switch) | ||
| ├── statusBarItems.js (CustomStatusBarItem class, 4 command items + env info item, showAll/hideAll) | ||
| ├── interpreter.js (find conda env Python path, set python.defaultInterpreterPath) | ||
| ├── documentLinks.js (clickable package links in YAML → anaconda.org / PyPI) | ||
| ├── codeLens.js (hover tooltips with PyPI package info) | ||
| ├── config.js (reads condaWingman.* settings) | ||
| └── utils.js (sendCommandToTerminal, YAML helpers, activeFileIsYAML) | ||
| ``` | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here are some suggestions based on the provided code patch:
Overall, the changes and additions seem valuable for enhancing the extension's functionality. Pay attention to error handling, maintain consistent coding standards, and consider testing to ensure the reliability and robustness of the extension. |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,11 +53,28 @@ The supported commands are: | |
| ``` | ||
| - **VS Code Command Palette:** `>Conda Wingman: Delete Conda Environment` | ||
|
|
||
| ### Switch Environment | ||
| - **Command:** Pick from all available conda environments and activate the selected one. | ||
| - **VS Code Command Palette:** `>Conda Wingman: Switch Environment` | ||
|
|
||
| ### Clickable Package Links | ||
| Package names in conda environment YAML files are clickable — conda packages link to [anaconda.org](https://anaconda.org), pip sub-dependencies link to [PyPI](https://pypi.org). | ||
|
|
||
| ### Hover Tooltips | ||
| Hover over a package name in a YAML file to see its description and latest version from PyPI. | ||
|
|
||
| ### Active Environment in Status Bar | ||
| The status bar shows the current conda environment name and Python version, detected from the open YAML file. Click it to switch environments. | ||
|
|
||
| ### Auto-Set Python Interpreter | ||
| When a conda environment is detected, the workspace Python interpreter (`python.defaultInterpreterPath`) is automatically set to point to it. | ||
|
|
||
| ## Settings | ||
|
|
||
| | Setting | Default | Description | | ||
| |---|---|---| | ||
| | `condaWingman.showStatusBarItems` | `true` | Show/hide status bar buttons | | ||
| | `condaWingman.autoSetInterpreter` | `true` | Auto-set workspace Python interpreter from conda env | | ||
|
|
||
| ## Release Notes | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Code Review:
Overall, the code patch appears to introduce valuable enhancements. However, attention should be given to potential edge cases, conflicts, and user preferences regarding the auto-setting of the Python interpreter. |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,7 @@ | |
| "name": "conda-wingman", | ||
| "displayName": "Conda Wingman", | ||
| "description": "Status bar buttons and commands for managing Conda environments from YAML files.", | ||
| "version": "1.1.0", | ||
| "version": "1.2.0", | ||
| "publisher": "DJSaunders1997", | ||
| "engines": { | ||
| "vscode": "^1.86.0" | ||
|
|
@@ -31,7 +31,8 @@ | |
| "onCommand:conda-wingman.buildCondaYAML", | ||
| "onCommand:conda-wingman.activateCondaYAML", | ||
| "onCommand:conda-wingman.writeRequirementsFile", | ||
| "onCommand:conda-wingman.deleteCondaEnv" | ||
| "onCommand:conda-wingman.deleteCondaEnv", | ||
| "onCommand:conda-wingman.switchEnvironment" | ||
| ], | ||
| "main": "./src/extension.js", | ||
| "contributes": { | ||
|
|
@@ -51,6 +52,10 @@ | |
| { | ||
| "command": "conda-wingman.deleteCondaEnv", | ||
| "title": "Conda Wingman: Delete Environment" | ||
| }, | ||
| { | ||
| "command": "conda-wingman.switchEnvironment", | ||
| "title": "Conda Wingman: Switch Environment" | ||
| } | ||
| ], | ||
| "configuration": { | ||
|
|
@@ -60,6 +65,11 @@ | |
| "type": "boolean", | ||
| "default": true, | ||
| "description": "Show Conda Wingman buttons in the status bar when a YAML file is open." | ||
| }, | ||
| "condaWingman.autoSetInterpreter": { | ||
| "type": "boolean", | ||
| "default": true, | ||
| "description": "Automatically set the workspace Python interpreter when a conda environment is detected." | ||
| } | ||
| } | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Code Review:Bug Risks:
Improvements:
Overall, the changes appear straightforward, but thorough testing and consideration of potential edge cases and user experience improvements are recommended. |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,156 @@ | ||
| // Provides hover tooltips on package names in conda environment YAML files, | ||
| // showing package description and latest version from PyPI. | ||
|
|
||
| const vscode = require("vscode"); | ||
| const https = require("https"); | ||
| const { findCondaDepPositions } = require("./documentLinks"); | ||
|
|
||
| /** Simple in-memory cache: pkgName -> { version, summary, fetchedAt } */ | ||
| const cache = new Map(); | ||
| const CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes | ||
|
|
||
| /** Invisible decoration type used only to attach hover tooltips to package names */ | ||
| const hoverDecorationType = vscode.window.createTextEditorDecorationType({}); | ||
|
|
||
| /** | ||
| * Fetches package info from the PyPI JSON API. | ||
| * Returns { version, summary } or null on failure. | ||
| */ | ||
| function fetchPypiInfo(pkgName) { | ||
| return new Promise(resolve => { | ||
| const url = `https://pypi.org/pypi/${encodeURIComponent(pkgName)}/json`; | ||
| const req = https.get(url, { timeout: 5000 }, res => { | ||
| if (res.statusCode !== 200) { | ||
| res.resume(); | ||
| resolve(null); | ||
| return; | ||
| } | ||
| let data = ""; | ||
| res.on("data", chunk => { data += chunk; }); | ||
| res.on("end", () => { | ||
| try { | ||
| const json = JSON.parse(data); | ||
| resolve({ | ||
| version: json.info.version, | ||
| summary: json.info.summary || "", | ||
| }); | ||
| } catch { | ||
| resolve(null); | ||
| } | ||
| }); | ||
| }); | ||
| req.on("error", () => resolve(null)); | ||
| req.on("timeout", () => { req.destroy(); resolve(null); }); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Returns cached PyPI info or fetches it, with TTL-based expiry. | ||
| */ | ||
| async function getPypiInfo(pkgName) { | ||
| const cached = cache.get(pkgName); | ||
| if (cached && (Date.now() - cached.fetchedAt) < CACHE_TTL_MS) { | ||
| return cached; | ||
| } | ||
| const info = await fetchPypiInfo(pkgName); | ||
| if (info) { | ||
| cache.set(pkgName, { ...info, fetchedAt: Date.now() }); | ||
| } | ||
| return info; | ||
| } | ||
|
|
||
| /** Track the current update so we can cancel stale runs */ | ||
| let updateCounter = 0; | ||
|
|
||
| /** | ||
| * Updates hover decorations for the given text editor. | ||
| * Only applies to YAML files that look like conda environment files. | ||
| */ | ||
| async function updateDecorations(editor) { | ||
| if (!editor) return; | ||
|
|
||
| const filePath = editor.document.uri.path; | ||
| if (!filePath.endsWith(".yml") && !filePath.endsWith(".yaml")) return; | ||
|
|
||
| const thisUpdate = ++updateCounter; | ||
| const text = editor.document.getText(); | ||
| const positions = findCondaDepPositions(text); | ||
|
|
||
| if (positions.length === 0) { | ||
| editor.setDecorations(hoverDecorationType, []); | ||
| return; | ||
| } | ||
|
|
||
| const results = await Promise.all( | ||
| positions.map(async (pos) => { | ||
| const info = await getPypiInfo(pos.name); | ||
| return { ...pos, info }; | ||
| }) | ||
| ); | ||
|
|
||
| // Check we haven't been superseded by a newer update | ||
| if (thisUpdate !== updateCounter) return; | ||
| if (editor !== vscode.window.activeTextEditor) return; | ||
|
|
||
| const hoverDecorations = []; | ||
|
|
||
| for (const { lineNum, colStart, colEnd, name, isPip, info } of results) { | ||
| if (!info) continue; | ||
|
|
||
| // Build hover tooltip content | ||
| const parts = []; | ||
| if (info.summary) parts.push(info.summary); | ||
| parts.push(`**Latest version:** ${info.version}`); | ||
|
|
||
| // Link to the right package index | ||
| if (isPip) { | ||
| parts.push(`[View on PyPI](https://pypi.org/project/${name}/)`); | ||
| } else { | ||
| parts.push(`[View on Anaconda](https://anaconda.org/conda-forge/${name})`); | ||
| parts.push(`[View on PyPI](https://pypi.org/project/${name}/)`); | ||
| } | ||
|
|
||
| hoverDecorations.push({ | ||
| range: new vscode.Range(lineNum, colStart, lineNum, colEnd), | ||
| hoverMessage: new vscode.MarkdownString(parts.join("\n\n")), | ||
| }); | ||
| } | ||
|
|
||
| editor.setDecorations(hoverDecorationType, hoverDecorations); | ||
| } | ||
|
|
||
| /** Debounce timer for document changes */ | ||
| let debounceTimer; | ||
|
|
||
| /** | ||
| * Registers hover tooltip decorations for conda YAML files. | ||
| * @param {vscode.ExtensionContext} context | ||
| */ | ||
| function registerCodeLens(context) { | ||
| // Decorate the active editor if it's a YAML file | ||
| if (vscode.window.activeTextEditor) { | ||
| updateDecorations(vscode.window.activeTextEditor); | ||
| } | ||
|
|
||
| // Re-decorate when switching editors | ||
| context.subscriptions.push( | ||
| vscode.window.onDidChangeActiveTextEditor(editor => { | ||
| if (editor) { | ||
| updateDecorations(editor); | ||
| } | ||
| }) | ||
| ); | ||
|
|
||
| // Re-decorate on document changes (debounced) | ||
| context.subscriptions.push( | ||
| vscode.workspace.onDidChangeTextDocument(e => { | ||
| const editor = vscode.window.activeTextEditor; | ||
| if (editor && e.document === editor.document) { | ||
| clearTimeout(debounceTimer); | ||
| debounceTimer = setTimeout(() => updateDecorations(editor), 1000); | ||
| } | ||
| }) | ||
| ); | ||
| } | ||
|
|
||
| module.exports = { registerCodeLens }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| const vscode = require("vscode"); | ||
| const path = require("path"); | ||
| const { execSync } = require("child_process"); | ||
|
|
||
| const { | ||
| sendCommandToTerminal, | ||
|
|
@@ -9,6 +10,8 @@ const { | |
| deleteEnvFromYAML, | ||
| createYAMLInputBox, | ||
| } = require("./utils"); | ||
| const { getCondaInterpreterPath, setWorkspacePythonInterpreter } = require("./interpreter"); | ||
| const { getConfig } = require("./config"); | ||
| const { | ||
| activateEnvIcon, | ||
| writeEnvIcon, | ||
|
|
@@ -102,9 +105,61 @@ async function writeRequirementsFile() { | |
| writeEnvIcon.displayDefault(); | ||
| } | ||
|
|
||
| /** | ||
| * Shows a quick pick of all conda environments and activates the selected one. | ||
| * Runs `conda env list --json` to get the list of environments. | ||
| * Also sets the workspace Python interpreter if autoSetInterpreter is enabled. | ||
| */ | ||
| async function switchEnvironment() { | ||
| let envs; | ||
| try { | ||
| const output = execSync("conda env list --json", { encoding: "utf8", timeout: 10000 }); | ||
| const parsed = JSON.parse(output); | ||
| envs = parsed.envs || []; | ||
| } catch { | ||
| vscode.window.showErrorMessage("Failed to list conda environments. Is conda installed?"); | ||
| return; | ||
| } | ||
|
|
||
| if (envs.length === 0) { | ||
| vscode.window.showInformationMessage("No conda environments found."); | ||
| return; | ||
| } | ||
|
|
||
| // Build quick pick items from env paths | ||
| // Each path looks like /home/user/miniconda3/envs/myenv or /home/user/miniconda3 (base) | ||
| const items = envs.map(envPath => { | ||
| const name = path.basename(envPath); | ||
| // The base env is the conda root dir itself, detect it | ||
| const isBase = !envPath.includes(path.join("envs", name)); | ||
| return { | ||
| label: isBase ? "base" : name, | ||
| description: envPath, | ||
| envPath: envPath, | ||
| }; | ||
| }); | ||
|
|
||
| const selected = await vscode.window.showQuickPick(items, { | ||
| placeHolder: "Select a conda environment to activate", | ||
| }); | ||
|
|
||
| if (!selected) return; | ||
|
|
||
| sendCommandToTerminal(`conda activate ${selected.label}`); | ||
|
|
||
| // Auto-set interpreter if enabled | ||
| if (getConfig().autoSetInterpreter) { | ||
| const interpreterPath = getCondaInterpreterPath(selected.label); | ||
| if (interpreterPath) { | ||
| setWorkspacePythonInterpreter(interpreterPath).catch(() => {}); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| module.exports = { | ||
| buildCondaYAML, | ||
| activateCondaYAML, | ||
| writeRequirementsFile, | ||
| deleteCondaEnv, | ||
| switchEnvironment, | ||
| }; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Code Review:
Bug Risks and Improvements:
By addressing these points, you can enhance the robustness, reliability, and maintainability of the codebase. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ function getConfig() { | |
| const cfg = vscode.workspace.getConfiguration('condaWingman'); | ||
| return { | ||
| showStatusBarItems: cfg.get('showStatusBarItems', true), | ||
| autoSetInterpreter: cfg.get('autoSetInterpreter', true), | ||
| }; | ||
| } | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code patch seems to be a simple modification to an existing function in a Visual Studio Code (VSCode) extension related to Conda environments. Here are some observations and suggestions for improvement:
Overall, the code change seems fine if it aligns with the intended functionality. Just pay attention to error handling and code documentation for clarity and maintainability. |
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code additions look good, but here are some suggestions for improvement and potential bug risks:
Input Validation: Ensure that input from the user (such as package names) is validated to prevent security vulnerabilities like injection attacks.
Error Handling: Implement robust error handling mechanisms to gracefully handle unexpected issues, preventing crashes or data loss.
Testing: Add comprehensive unit tests to cover new functionality thoroughly, ensuring that changes work as expected and don't break existing features.
Documentation: Update documentation within the codebase to reflect the new features, describing usage, configurations, and potential limitations.
Performance Optimization: Monitor performance impact of new features, optimizing code if any slowdowns are noticed, especially in hover tooltips that may load external data.
Versioning: Follow consistent versioning practices to maintain clarity on changes made between versions and ensure compatibility with dependency requirements.
Overall, the new features seem beneficial for user experience, but attention to these suggestions can enhance reliability and usability.