From 27fd645dc2e6286c3340aac18079f2cfa374cc06 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Sep 2025 18:51:34 +0000 Subject: [PATCH 1/8] Initial plan From 0859b4d195a6c781215e5ca229d0379a6a67195a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Sep 2025 18:59:43 +0000 Subject: [PATCH 2/8] Add uv detection and displayName update functionality Co-authored-by: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> --- src/managers/builtin/venvManager.ts | 10 +- .../managers/builtin/venvManager.unit.test.ts | 94 +++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 src/test/managers/builtin/venvManager.unit.test.ts diff --git a/src/managers/builtin/venvManager.ts b/src/managers/builtin/venvManager.ts index cf5027a9..e90897bc 100644 --- a/src/managers/builtin/venvManager.ts +++ b/src/managers/builtin/venvManager.ts @@ -52,6 +52,7 @@ import { setVenvForWorkspace, setVenvForWorkspaces, } from './venvUtils'; +import { isUvInstalled } from './helpers'; export class VenvManager implements EnvironmentManager { private collection: PythonEnvironment[] = []; @@ -66,7 +67,7 @@ export class VenvManager implements EnvironmentManager { public readonly onDidChangeEnvironments = this._onDidChangeEnvironments.event; readonly name: string; - readonly displayName: string; + displayName: string; // Made mutable to update with uv detection readonly preferredPackageManagerId: string; readonly description?: string | undefined; readonly tooltip?: string | MarkdownString | undefined; @@ -97,6 +98,13 @@ export class VenvManager implements EnvironmentManager { this._initialized = createDeferred(); try { + // Check if uv is available and update display name accordingly + const uvAvailable = await isUvInstalled(this.log); + if (uvAvailable) { + this.displayName = 'venv [uv]'; + this.log?.info('uv detected - updating venv manager display name'); + } + await this.internalRefresh(undefined, false, VenvManagerStrings.venvInitialize); } finally { this._initialized.resolve(); diff --git a/src/test/managers/builtin/venvManager.unit.test.ts b/src/test/managers/builtin/venvManager.unit.test.ts new file mode 100644 index 00000000..46ef4e97 --- /dev/null +++ b/src/test/managers/builtin/venvManager.unit.test.ts @@ -0,0 +1,94 @@ +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import { LogOutputChannel } from 'vscode'; +import { VenvManager } from '../../../managers/builtin/venvManager'; +import * as helpers from '../../../managers/builtin/helpers'; + +suite('VenvManager uv labeling tests', () => { + let sandbox: sinon.SinonSandbox; + + setup(() => { + sandbox = sinon.createSandbox(); + }); + + teardown(() => { + sandbox.restore(); + }); + + test('should display "venv" when uv is not available', async () => { + // Mock isUvInstalled to return false + sandbox.stub(helpers, 'isUvInstalled').resolves(false); + + // Create mocks for required dependencies + const mockNativeFinder = {} as any; + const mockApi = {} as any; + const mockBaseManager = {} as any; + const mockLog = {} as LogOutputChannel; + + const mockInternalRefresh = sandbox.stub(VenvManager.prototype, 'internalRefresh' as any).resolves(); + + const manager = new VenvManager(mockNativeFinder, mockApi, mockBaseManager, mockLog); + + // Verify initial state + assert.strictEqual(manager.displayName, 'venv'); + + await manager.initialize(); + + // Verify displayName remains unchanged when uv is not available + assert.strictEqual(manager.displayName, 'venv'); + assert.ok(mockInternalRefresh.calledOnce); + }); + + test('should display "venv [uv]" when uv is available', async () => { + // Mock isUvInstalled to return true + sandbox.stub(helpers, 'isUvInstalled').resolves(true); + + // Create mocks for required dependencies + const mockNativeFinder = {} as any; + const mockApi = {} as any; + const mockBaseManager = {} as any; + const mockLog = { + info: sandbox.stub() + } as any; + + const mockInternalRefresh = sandbox.stub(VenvManager.prototype, 'internalRefresh' as any).resolves(); + + const manager = new VenvManager(mockNativeFinder, mockApi, mockBaseManager, mockLog); + + // Verify initial state + assert.strictEqual(manager.displayName, 'venv'); + + await manager.initialize(); + + // Verify displayName is updated when uv is available + assert.strictEqual(manager.displayName, 'venv [uv]'); + assert.ok(mockInternalRefresh.calledOnce); + assert.ok(mockLog.info.calledWith('uv detected - updating venv manager display name')); + }); + + test('should only initialize once and preserve displayName', async () => { + // Mock isUvInstalled to return true + sandbox.stub(helpers, 'isUvInstalled').resolves(true); + + // Create mocks for required dependencies + const mockNativeFinder = {} as any; + const mockApi = {} as any; + const mockBaseManager = {} as any; + const mockLog = { + info: sandbox.stub() + } as any; + + const mockInternalRefresh = sandbox.stub(VenvManager.prototype, 'internalRefresh' as any).resolves(); + + const manager = new VenvManager(mockNativeFinder, mockApi, mockBaseManager, mockLog); + + // Initialize multiple times + await manager.initialize(); + await manager.initialize(); + + // Verify displayName is set correctly and internal refresh is called only once + assert.strictEqual(manager.displayName, 'venv [uv]'); + assert.ok(mockInternalRefresh.calledOnce); + assert.ok(mockLog.info.calledOnce); + }); +}); \ No newline at end of file From 388eb78270a4c680a5684b58b633028198092f39 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Sep 2025 19:03:23 +0000 Subject: [PATCH 3/8] Improve test code quality by removing any types Co-authored-by: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> --- .../managers/builtin/venvManager.unit.test.ts | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/test/managers/builtin/venvManager.unit.test.ts b/src/test/managers/builtin/venvManager.unit.test.ts index 46ef4e97..315eafc7 100644 --- a/src/test/managers/builtin/venvManager.unit.test.ts +++ b/src/test/managers/builtin/venvManager.unit.test.ts @@ -3,6 +3,9 @@ import * as sinon from 'sinon'; import { LogOutputChannel } from 'vscode'; import { VenvManager } from '../../../managers/builtin/venvManager'; import * as helpers from '../../../managers/builtin/helpers'; +import { PythonEnvironmentApi } from '../../../api'; +import { NativePythonFinder } from '../../../managers/common/nativePythonFinder'; +import { EnvironmentManager } from '../../../api'; suite('VenvManager uv labeling tests', () => { let sandbox: sinon.SinonSandbox; @@ -20,12 +23,12 @@ suite('VenvManager uv labeling tests', () => { sandbox.stub(helpers, 'isUvInstalled').resolves(false); // Create mocks for required dependencies - const mockNativeFinder = {} as any; - const mockApi = {} as any; - const mockBaseManager = {} as any; + const mockNativeFinder = {} as NativePythonFinder; + const mockApi = {} as PythonEnvironmentApi; + const mockBaseManager = {} as EnvironmentManager; const mockLog = {} as LogOutputChannel; - const mockInternalRefresh = sandbox.stub(VenvManager.prototype, 'internalRefresh' as any).resolves(); + const mockInternalRefresh = sandbox.stub(VenvManager.prototype, 'internalRefresh' as keyof VenvManager).resolves(); const manager = new VenvManager(mockNativeFinder, mockApi, mockBaseManager, mockLog); @@ -44,14 +47,14 @@ suite('VenvManager uv labeling tests', () => { sandbox.stub(helpers, 'isUvInstalled').resolves(true); // Create mocks for required dependencies - const mockNativeFinder = {} as any; - const mockApi = {} as any; - const mockBaseManager = {} as any; + const mockNativeFinder = {} as NativePythonFinder; + const mockApi = {} as PythonEnvironmentApi; + const mockBaseManager = {} as EnvironmentManager; const mockLog = { info: sandbox.stub() - } as any; + } as unknown as LogOutputChannel; - const mockInternalRefresh = sandbox.stub(VenvManager.prototype, 'internalRefresh' as any).resolves(); + const mockInternalRefresh = sandbox.stub(VenvManager.prototype, 'internalRefresh' as keyof VenvManager).resolves(); const manager = new VenvManager(mockNativeFinder, mockApi, mockBaseManager, mockLog); @@ -63,7 +66,7 @@ suite('VenvManager uv labeling tests', () => { // Verify displayName is updated when uv is available assert.strictEqual(manager.displayName, 'venv [uv]'); assert.ok(mockInternalRefresh.calledOnce); - assert.ok(mockLog.info.calledWith('uv detected - updating venv manager display name')); + assert.ok((mockLog as unknown as { info: sinon.SinonStub }).info.calledWith('uv detected - updating venv manager display name')); }); test('should only initialize once and preserve displayName', async () => { @@ -71,14 +74,14 @@ suite('VenvManager uv labeling tests', () => { sandbox.stub(helpers, 'isUvInstalled').resolves(true); // Create mocks for required dependencies - const mockNativeFinder = {} as any; - const mockApi = {} as any; - const mockBaseManager = {} as any; + const mockNativeFinder = {} as NativePythonFinder; + const mockApi = {} as PythonEnvironmentApi; + const mockBaseManager = {} as EnvironmentManager; const mockLog = { info: sandbox.stub() - } as any; + } as unknown as LogOutputChannel; - const mockInternalRefresh = sandbox.stub(VenvManager.prototype, 'internalRefresh' as any).resolves(); + const mockInternalRefresh = sandbox.stub(VenvManager.prototype, 'internalRefresh' as keyof VenvManager).resolves(); const manager = new VenvManager(mockNativeFinder, mockApi, mockBaseManager, mockLog); @@ -89,6 +92,6 @@ suite('VenvManager uv labeling tests', () => { // Verify displayName is set correctly and internal refresh is called only once assert.strictEqual(manager.displayName, 'venv [uv]'); assert.ok(mockInternalRefresh.calledOnce); - assert.ok(mockLog.info.calledOnce); + assert.ok((mockLog as unknown as { info: sinon.SinonStub }).info.calledOnce); }); }); \ No newline at end of file From 9746b27525c09cccd3ab8b3af7fe43f98da4d070 Mon Sep 17 00:00:00 2001 From: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Date: Mon, 8 Sep 2025 13:28:27 -0700 Subject: [PATCH 4/8] trigger check at startup --- src/managers/builtin/main.ts | 7 +++++++ src/managers/builtin/venvManager.ts | 16 ++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/managers/builtin/main.ts b/src/managers/builtin/main.ts index d1f4a400..d2e4e980 100644 --- a/src/managers/builtin/main.ts +++ b/src/managers/builtin/main.ts @@ -5,6 +5,7 @@ import { onDidEndTerminalShellExecution } from '../../common/window.apis'; import { createFileSystemWatcher, onDidDeleteFiles } from '../../common/workspace.apis'; import { getPythonApi } from '../../features/pythonApi'; import { NativePythonFinder } from '../common/nativePythonFinder'; +import { isUvInstalled } from './helpers'; import { PipPackageManager } from './pipManager'; import { isPipInstallCommand } from './pipUtils'; import { SysPythonManager } from './sysPythonManager'; @@ -20,6 +21,12 @@ export async function registerSystemPythonFeatures( const venvManager = new VenvManager(nativeFinder, api, envManager, log); const pkgManager = new PipPackageManager(api, log, venvManager); + const uvAvailable = await isUvInstalled(log); + if (uvAvailable) { + venvManager.setDisplayName('venv [uv]'); + log.info('uv detected - updating venv manager display name'); + } + disposables.push( api.registerPackageManager(pkgManager), api.registerEnvironmentManager(envManager), diff --git a/src/managers/builtin/venvManager.ts b/src/managers/builtin/venvManager.ts index e90897bc..0296bbe5 100644 --- a/src/managers/builtin/venvManager.ts +++ b/src/managers/builtin/venvManager.ts @@ -52,7 +52,6 @@ import { setVenvForWorkspace, setVenvForWorkspaces, } from './venvUtils'; -import { isUvInstalled } from './helpers'; export class VenvManager implements EnvironmentManager { private collection: PythonEnvironment[] = []; @@ -98,13 +97,6 @@ export class VenvManager implements EnvironmentManager { this._initialized = createDeferred(); try { - // Check if uv is available and update display name accordingly - const uvAvailable = await isUvInstalled(this.log); - if (uvAvailable) { - this.displayName = 'venv [uv]'; - this.log?.info('uv detected - updating venv manager display name'); - } - await this.internalRefresh(undefined, false, VenvManagerStrings.venvInitialize); } finally { this._initialized.resolve(); @@ -127,6 +119,14 @@ export class VenvManager implements EnvironmentManager { }; } + /** + * Updates the display name for this environment manager. + * @param name The new display name to set. + */ + public setDisplayName(name: string): void { + this.displayName = name; + } + async create( scope: CreateEnvironmentScope, options: CreateEnvironmentOptions | undefined, From a232ff55fce040035fe219d4813aa85efd971b61 Mon Sep 17 00:00:00 2001 From: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Date: Mon, 8 Sep 2025 13:29:57 -0700 Subject: [PATCH 5/8] remove test --- .../managers/builtin/venvManager.unit.test.ts | 97 ------------------- 1 file changed, 97 deletions(-) delete mode 100644 src/test/managers/builtin/venvManager.unit.test.ts diff --git a/src/test/managers/builtin/venvManager.unit.test.ts b/src/test/managers/builtin/venvManager.unit.test.ts deleted file mode 100644 index 315eafc7..00000000 --- a/src/test/managers/builtin/venvManager.unit.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -import * as assert from 'assert'; -import * as sinon from 'sinon'; -import { LogOutputChannel } from 'vscode'; -import { VenvManager } from '../../../managers/builtin/venvManager'; -import * as helpers from '../../../managers/builtin/helpers'; -import { PythonEnvironmentApi } from '../../../api'; -import { NativePythonFinder } from '../../../managers/common/nativePythonFinder'; -import { EnvironmentManager } from '../../../api'; - -suite('VenvManager uv labeling tests', () => { - let sandbox: sinon.SinonSandbox; - - setup(() => { - sandbox = sinon.createSandbox(); - }); - - teardown(() => { - sandbox.restore(); - }); - - test('should display "venv" when uv is not available', async () => { - // Mock isUvInstalled to return false - sandbox.stub(helpers, 'isUvInstalled').resolves(false); - - // Create mocks for required dependencies - const mockNativeFinder = {} as NativePythonFinder; - const mockApi = {} as PythonEnvironmentApi; - const mockBaseManager = {} as EnvironmentManager; - const mockLog = {} as LogOutputChannel; - - const mockInternalRefresh = sandbox.stub(VenvManager.prototype, 'internalRefresh' as keyof VenvManager).resolves(); - - const manager = new VenvManager(mockNativeFinder, mockApi, mockBaseManager, mockLog); - - // Verify initial state - assert.strictEqual(manager.displayName, 'venv'); - - await manager.initialize(); - - // Verify displayName remains unchanged when uv is not available - assert.strictEqual(manager.displayName, 'venv'); - assert.ok(mockInternalRefresh.calledOnce); - }); - - test('should display "venv [uv]" when uv is available', async () => { - // Mock isUvInstalled to return true - sandbox.stub(helpers, 'isUvInstalled').resolves(true); - - // Create mocks for required dependencies - const mockNativeFinder = {} as NativePythonFinder; - const mockApi = {} as PythonEnvironmentApi; - const mockBaseManager = {} as EnvironmentManager; - const mockLog = { - info: sandbox.stub() - } as unknown as LogOutputChannel; - - const mockInternalRefresh = sandbox.stub(VenvManager.prototype, 'internalRefresh' as keyof VenvManager).resolves(); - - const manager = new VenvManager(mockNativeFinder, mockApi, mockBaseManager, mockLog); - - // Verify initial state - assert.strictEqual(manager.displayName, 'venv'); - - await manager.initialize(); - - // Verify displayName is updated when uv is available - assert.strictEqual(manager.displayName, 'venv [uv]'); - assert.ok(mockInternalRefresh.calledOnce); - assert.ok((mockLog as unknown as { info: sinon.SinonStub }).info.calledWith('uv detected - updating venv manager display name')); - }); - - test('should only initialize once and preserve displayName', async () => { - // Mock isUvInstalled to return true - sandbox.stub(helpers, 'isUvInstalled').resolves(true); - - // Create mocks for required dependencies - const mockNativeFinder = {} as NativePythonFinder; - const mockApi = {} as PythonEnvironmentApi; - const mockBaseManager = {} as EnvironmentManager; - const mockLog = { - info: sandbox.stub() - } as unknown as LogOutputChannel; - - const mockInternalRefresh = sandbox.stub(VenvManager.prototype, 'internalRefresh' as keyof VenvManager).resolves(); - - const manager = new VenvManager(mockNativeFinder, mockApi, mockBaseManager, mockLog); - - // Initialize multiple times - await manager.initialize(); - await manager.initialize(); - - // Verify displayName is set correctly and internal refresh is called only once - assert.strictEqual(manager.displayName, 'venv [uv]'); - assert.ok(mockInternalRefresh.calledOnce); - assert.ok((mockLog as unknown as { info: sinon.SinonStub }).info.calledOnce); - }); -}); \ No newline at end of file From 44f84dd647877afa3f24b31d3d807e0de9063ca5 Mon Sep 17 00:00:00 2001 From: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Date: Mon, 8 Sep 2025 13:52:29 -0700 Subject: [PATCH 6/8] add uv to more strings when being used --- src/common/localize.ts | 84 +++++++++++++++++++++-------- src/managers/builtin/main.ts | 3 ++ src/managers/builtin/pipUtils.ts | 6 +-- src/managers/builtin/venvManager.ts | 14 ++--- src/managers/builtin/venvUtils.ts | 66 ++++++++++++++--------- 5 files changed, 116 insertions(+), 57 deletions(-) diff --git a/src/common/localize.ts b/src/common/localize.ts index 1674a397..e001b6ef 100644 --- a/src/common/localize.ts +++ b/src/common/localize.ts @@ -77,32 +77,74 @@ export namespace ProjectViews { export const noPackages = l10n.t('No packages found'); } -export namespace VenvManagerStrings { - export const venvManagerDescription = l10n.t('Manages virtual environments created using `venv`'); - export const venvInitialize = l10n.t('Initializing virtual environments'); - export const venvRefreshing = l10n.t('Refreshing virtual environments'); - export const venvGlobalFolder = l10n.t('Select a folder to create a global virtual environment'); - export const venvGlobalFoldersSetting = l10n.t('Venv Folders Setting'); +export class VenvManagerStringsNoUv { + static venvManagerDescription = l10n.t('Manages virtual environments created using `venv`'); + static venvInitialize = l10n.t('Initializing virtual environments'); + static venvRefreshing = l10n.t('Refreshing virtual environments'); + static venvGlobalFolder = l10n.t('Select a folder to create a global virtual environment'); + static venvGlobalFoldersSetting = l10n.t('Venv Folders Setting'); + + static venvErrorNoBasePython = l10n.t('No base Python found'); + static venvErrorNoPython3 = l10n.t('Did not find any base Python 3'); + + static venvName = l10n.t('Enter a name for the virtual environment'); + static venvNameErrorEmpty = l10n.t('Name cannot be empty'); + static venvNameErrorExists = l10n.t('A folder with the same name already exists'); + static venvCreateFailed = l10n.t('Failed to create virtual environment'); + + static venvRemoving = l10n.t('Removing virtual environment'); + static venvRemoveFailed = l10n.t('Failed to remove virtual environment'); + + static installEditable = l10n.t('Install project as editable'); + static searchingDependencies = l10n.t('Searching for dependencies'); + + static selectQuickOrCustomize = l10n.t('Select environment creation mode'); + static quickCreate = l10n.t('Quick Create'); + static quickCreateDescription = l10n.t('Create a virtual environment in the workspace root'); + static customize = l10n.t('Custom'); + static customizeDescription = l10n.t('Choose python version, location, packages, name, etc.'); +} - export const venvErrorNoBasePython = l10n.t('No base Python found'); - export const venvErrorNoPython3 = l10n.t('Did not find any base Python 3'); +export class VenvManagerStringsWithUv { + static venvManagerDescription = l10n.t('Manages virtual environments created using `venv [uv]`'); + static venvInitialize = l10n.t('Initializing virtual environments'); + static venvRefreshing = l10n.t('Refreshing virtual environments'); + static venvGlobalFolder = l10n.t('Select a folder to create a global virtual environment'); + static venvGlobalFoldersSetting = l10n.t('Venv Folders Setting'); - export const venvName = l10n.t('Enter a name for the virtual environment'); - export const venvNameErrorEmpty = l10n.t('Name cannot be empty'); - export const venvNameErrorExists = l10n.t('A folder with the same name already exists'); - export const venvCreateFailed = l10n.t('Failed to create virtual environment'); + static venvErrorNoBasePython = l10n.t('No base Python found'); + static venvErrorNoPython3 = l10n.t('Did not find any base Python 3'); - export const venvRemoving = l10n.t('Removing virtual environment'); - export const venvRemoveFailed = l10n.t('Failed to remove virtual environment'); + static venvName = l10n.t('Enter a name for the virtual environment'); + static venvNameErrorEmpty = l10n.t('Name cannot be empty'); + static venvNameErrorExists = l10n.t('A folder with the same name already exists'); + static venvCreateFailed = l10n.t('Failed to create virtual environment'); - export const installEditable = l10n.t('Install project as editable'); - export const searchingDependencies = l10n.t('Searching for dependencies'); + static venvRemoving = l10n.t('Removing virtual environment'); + static venvRemoveFailed = l10n.t('Failed to remove virtual environment'); - export const selectQuickOrCustomize = l10n.t('Select environment creation mode'); - export const quickCreate = l10n.t('Quick Create'); - export const quickCreateDescription = l10n.t('Create a virtual environment in the workspace root'); - export const customize = l10n.t('Custom'); - export const customizeDescription = l10n.t('Choose python version, location, packages, name, etc.'); + static installEditable = l10n.t('Install project as editable'); + static searchingDependencies = l10n.t('Searching for dependencies'); + + static selectQuickOrCustomize = l10n.t('Select environment creation mode'); + static quickCreate = l10n.t('Quick Create'); + static quickCreateDescription = l10n.t( + 'Create a virtual environment in the workspace root using uv for fast installs', + ); + static customize = l10n.t('Custom'); + static customizeDescription = l10n.t( + 'Choose python version, location, packages, name, etc. (uses uv for installs)', + ); +} + +/** + * VenvManagerStrings is assigned to either VenvManagerStringsNoUv or VenvManagerStringsWithUv + * depending on which environment manager is active. This variable can be reassigned at runtime. + */ +export let VenvManagerStrings: typeof VenvManagerStringsNoUv | typeof VenvManagerStringsWithUv = VenvManagerStringsNoUv; + +export function setVenvManagerStrings(val: typeof VenvManagerStringsNoUv | typeof VenvManagerStringsWithUv) { + VenvManagerStrings = val; } export namespace SysManagerStrings { diff --git a/src/managers/builtin/main.ts b/src/managers/builtin/main.ts index d2e4e980..33d817d4 100644 --- a/src/managers/builtin/main.ts +++ b/src/managers/builtin/main.ts @@ -1,5 +1,6 @@ import { Disposable, LogOutputChannel } from 'vscode'; import { PythonEnvironmentApi } from '../../api'; +import { setVenvManagerStrings, VenvManagerStringsWithUv } from '../../common/localize'; import { createSimpleDebounce } from '../../common/utils/debounce'; import { onDidEndTerminalShellExecution } from '../../common/window.apis'; import { createFileSystemWatcher, onDidDeleteFiles } from '../../common/workspace.apis'; @@ -25,6 +26,8 @@ export async function registerSystemPythonFeatures( if (uvAvailable) { venvManager.setDisplayName('venv [uv]'); log.info('uv detected - updating venv manager display name'); + // Switch all venv-related UI strings to uv versions + setVenvManagerStrings(VenvManagerStringsWithUv); } disposables.push( diff --git a/src/managers/builtin/pipUtils.ts b/src/managers/builtin/pipUtils.ts index ef3ef88b..31bb5f22 100644 --- a/src/managers/builtin/pipUtils.ts +++ b/src/managers/builtin/pipUtils.ts @@ -4,7 +4,7 @@ import * as path from 'path'; import { LogOutputChannel, ProgressLocation, QuickInputButtons, QuickPickItem, Uri } from 'vscode'; import { PackageManagementOptions, PythonEnvironment, PythonEnvironmentApi, PythonProject } from '../../api'; import { EXTENSION_ROOT_DIR } from '../../common/constants'; -import { PackageManagement, Pickers, VenvManagerStrings } from '../../common/localize'; +import { PackageManagement, Pickers, VenvManagerStringsNoUv } from '../../common/localize'; import { traceInfo } from '../../common/logging'; import { showQuickPickWithButtons, withProgress } from '../../common/window.apis'; import { findFiles } from '../../common/workspace.apis'; @@ -36,7 +36,7 @@ function getTomlInstallable(toml: tomljs.JsonMap, tomlPath: Uri): Installable[] extras.push({ name, displayName: name, - description: VenvManagerStrings.installEditable, + description: VenvManagerStringsNoUv.installEditable, group: 'TOML', args: ['-e', projectDir], uri: tomlPath, @@ -177,7 +177,7 @@ export async function getProjectInstallable( await withProgress( { location: ProgressLocation.Notification, - title: VenvManagerStrings.searchingDependencies, + title: VenvManagerStringsNoUv.searchingDependencies, }, async (_progress, token) => { const results: Uri[] = ( diff --git a/src/managers/builtin/venvManager.ts b/src/managers/builtin/venvManager.ts index 0296bbe5..32264534 100644 --- a/src/managers/builtin/venvManager.ts +++ b/src/managers/builtin/venvManager.ts @@ -29,7 +29,7 @@ import { SetEnvironmentScope, } from '../../api'; import { PYTHON_EXTENSION_ID } from '../../common/constants'; -import { VenvManagerStrings } from '../../common/localize'; +import { VenvManagerStringsNoUv } from '../../common/localize'; import { traceError, traceWarn } from '../../common/logging'; import { createDeferred, Deferred } from '../../common/utils/deferred'; import { showErrorMessage, withProgress } from '../../common/window.apis'; @@ -83,7 +83,7 @@ export class VenvManager implements EnvironmentManager { // Descriptions were a bit too visually noisy // https://github.com/microsoft/vscode-python-environments/issues/167 this.description = undefined; - this.tooltip = new MarkdownString(VenvManagerStrings.venvManagerDescription, true); + this.tooltip = new MarkdownString(VenvManagerStringsNoUv.venvManagerDescription, true); this.preferredPackageManagerId = 'ms-python.python:pip'; this.iconPath = new ThemeIcon('python'); } @@ -97,7 +97,7 @@ export class VenvManager implements EnvironmentManager { this._initialized = createDeferred(); try { - await this.internalRefresh(undefined, false, VenvManagerStrings.venvInitialize); + await this.internalRefresh(undefined, false, VenvManagerStringsNoUv.venvInitialize); } finally { this._initialized.resolve(); } @@ -156,7 +156,7 @@ export class VenvManager implements EnvironmentManager { // error on missing information if (!this.globalEnv) { this.log.error('No base python found'); - showErrorMessage(VenvManagerStrings.venvErrorNoBasePython); + showErrorMessage(VenvManagerStringsNoUv.venvErrorNoBasePython); throw new Error('No base python found'); } if (!this.globalEnv.version.startsWith('3.')) { @@ -164,7 +164,7 @@ export class VenvManager implements EnvironmentManager { globals.forEach((e, i) => { this.log.error(`${i}: ${e.version} : ${e.environmentPath.fsPath}`); }); - showErrorMessage(VenvManagerStrings.venvErrorNoPython3); + showErrorMessage(VenvManagerStringsNoUv.venvErrorNoPython3); throw new Error('Did not find any base python 3.*'); } if (this.globalEnv && this.globalEnv.version.startsWith('3.')) { @@ -292,14 +292,14 @@ export class VenvManager implements EnvironmentManager { } async refresh(scope: RefreshEnvironmentsScope): Promise { - return this.internalRefresh(scope, true, VenvManagerStrings.venvRefreshing); + return this.internalRefresh(scope, true, VenvManagerStringsNoUv.venvRefreshing); } async watcherRefresh(): Promise { if (this.skipWatcherRefresh) { return; } - return this.internalRefresh(undefined, true, VenvManagerStrings.venvRefreshing); + return this.internalRefresh(undefined, true, VenvManagerStringsNoUv.venvRefreshing); } private async internalRefresh( diff --git a/src/managers/builtin/venvUtils.ts b/src/managers/builtin/venvUtils.ts index ebcd630a..dde71733 100644 --- a/src/managers/builtin/venvUtils.ts +++ b/src/managers/builtin/venvUtils.ts @@ -4,7 +4,7 @@ import * as path from 'path'; import { l10n, LogOutputChannel, ProgressLocation, QuickPickItem, QuickPickItemKind, ThemeIcon, Uri } from 'vscode'; import { EnvironmentManager, PythonEnvironment, PythonEnvironmentApi, PythonEnvironmentInfo } from '../../api'; import { ENVS_EXTENSION_ID } from '../../common/constants'; -import { Common, VenvManagerStrings } from '../../common/localize'; +import { Common, VenvManagerStringsNoUv } from '../../common/localize'; import { traceInfo } from '../../common/logging'; import { getWorkspacePersistentState } from '../../common/persistentState'; import { pickEnvironmentFrom } from '../../common/pickers/environments'; @@ -209,7 +209,7 @@ export async function getGlobalVenvLocation(): Promise { const items: FolderQuickPickItem[] = [ { label: Common.browse, - description: VenvManagerStrings.venvGlobalFolder, + description: VenvManagerStringsNoUv.venvGlobalFolder, }, ]; @@ -217,7 +217,7 @@ export async function getGlobalVenvLocation(): Promise { if (venvPaths.length > 0) { items.push( { - label: VenvManagerStrings.venvGlobalFoldersSetting, + label: VenvManagerStringsNoUv.venvGlobalFoldersSetting, kind: QuickPickItemKind.Separator, }, ...venvPaths.map((p) => ({ @@ -243,7 +243,7 @@ export async function getGlobalVenvLocation(): Promise { } const selected = await showQuickPick(items, { - placeHolder: VenvManagerStrings.venvGlobalFolder, + placeHolder: VenvManagerStringsNoUv.venvGlobalFolder, ignoreFocusOut: true, }); @@ -269,24 +269,24 @@ async function createWithCustomization(version: string): Promise { const result: CreateEnvironmentResult = {}; try { - const useUv = await isUvInstalled(log); // env creation if (basePython.execInfo?.run.executable) { if (useUv) { @@ -353,13 +361,19 @@ async function createWithProgress( }); } catch (e) { // error occurred while installing packages - result.pkgInstallationErr = e instanceof Error ? e.message : String(e); + result.pkgInstallationErr = useUv + ? `Failed to install packages with uv: ${e instanceof Error ? e.message : String(e)}` + : e instanceof Error + ? e.message + : String(e); } } result.environment = env; } catch (e) { - log.error(`Failed to create virtual environment: ${e}`); - result.envCreationErr = `Failed to create virtual environment: ${e}`; + result.envCreationErr = useUv + ? `Failed to create virtual environment with uv: ${e}` + : `Failed to create virtual environment: ${e}`; + log.error(result.envCreationErr); } return result; }, @@ -369,14 +383,14 @@ async function createWithProgress( export function ensureGlobalEnv(basePythons: PythonEnvironment[], log: LogOutputChannel): PythonEnvironment[] { if (basePythons.length === 0) { log.error('No base python found'); - showErrorMessage(VenvManagerStrings.venvErrorNoBasePython); + showErrorMessage(VenvManagerStringsNoUv.venvErrorNoBasePython); throw new Error('No base python found'); } const filtered = basePythons.filter((e) => e.version.startsWith('3.')); if (filtered.length === 0) { log.error('Did not find any base python 3.*'); - showErrorMessage(VenvManagerStrings.venvErrorNoPython3); + showErrorMessage(VenvManagerStringsNoUv.venvErrorNoPython3); basePythons.forEach((e, i) => { log.error(`${i}: ${e.version} : ${e.environmentPath.fsPath}`); }); @@ -457,15 +471,15 @@ export async function createPythonVenv( } const name = await showInputBox({ - prompt: VenvManagerStrings.venvName, + prompt: VenvManagerStringsNoUv.venvName, value: '.venv', ignoreFocusOut: true, validateInput: async (value) => { if (!value) { - return VenvManagerStrings.venvNameErrorEmpty; + return VenvManagerStringsNoUv.venvNameErrorEmpty; } if (await fsapi.pathExists(path.join(venvRoot.fsPath, value))) { - return VenvManagerStrings.venvNameErrorExists; + return VenvManagerStringsNoUv.venvNameErrorExists; } }, }); @@ -513,7 +527,7 @@ export async function removeVenv(environment: PythonEnvironment, log: LogOutputC const result = await withProgress( { location: ProgressLocation.Notification, - title: VenvManagerStrings.venvRemoving, + title: VenvManagerStringsNoUv.venvRemoving, }, async () => { try { @@ -521,7 +535,7 @@ export async function removeVenv(environment: PythonEnvironment, log: LogOutputC return true; } catch (e) { log.error(`Failed to remove virtual environment: ${e}`); - showErrorMessage(VenvManagerStrings.venvRemoveFailed); + showErrorMessage(VenvManagerStringsNoUv.venvRemoveFailed); return false; } }, From 986119841749641235d2123dc71da9386df4031e Mon Sep 17 00:00:00 2001 From: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:16:01 -0700 Subject: [PATCH 7/8] fix localized text --- src/common/localize.ts | 39 ++++++----------------------- src/managers/builtin/pipUtils.ts | 6 ++--- src/managers/builtin/venvManager.ts | 12 ++++----- src/managers/builtin/venvUtils.ts | 32 +++++++++++------------ 4 files changed, 33 insertions(+), 56 deletions(-) diff --git a/src/common/localize.ts b/src/common/localize.ts index e001b6ef..2b9017b9 100644 --- a/src/common/localize.ts +++ b/src/common/localize.ts @@ -77,64 +77,41 @@ export namespace ProjectViews { export const noPackages = l10n.t('No packages found'); } -export class VenvManagerStringsNoUv { - static venvManagerDescription = l10n.t('Manages virtual environments created using `venv`'); +export class VenvManagerCommonStrings { static venvInitialize = l10n.t('Initializing virtual environments'); static venvRefreshing = l10n.t('Refreshing virtual environments'); static venvGlobalFolder = l10n.t('Select a folder to create a global virtual environment'); static venvGlobalFoldersSetting = l10n.t('Venv Folders Setting'); - static venvErrorNoBasePython = l10n.t('No base Python found'); static venvErrorNoPython3 = l10n.t('Did not find any base Python 3'); - static venvName = l10n.t('Enter a name for the virtual environment'); static venvNameErrorEmpty = l10n.t('Name cannot be empty'); static venvNameErrorExists = l10n.t('A folder with the same name already exists'); static venvCreateFailed = l10n.t('Failed to create virtual environment'); - static venvRemoving = l10n.t('Removing virtual environment'); static venvRemoveFailed = l10n.t('Failed to remove virtual environment'); - - static installEditable = l10n.t('Install project as editable'); static searchingDependencies = l10n.t('Searching for dependencies'); - static selectQuickOrCustomize = l10n.t('Select environment creation mode'); static quickCreate = l10n.t('Quick Create'); - static quickCreateDescription = l10n.t('Create a virtual environment in the workspace root'); static customize = l10n.t('Custom'); +} + +export class VenvManagerStringsNoUv { + static venvManagerDescription = l10n.t('Manages virtual environments created using `venv`'); + static quickCreateDescription = l10n.t('Create a virtual environment in the workspace root'); static customizeDescription = l10n.t('Choose python version, location, packages, name, etc.'); + static installEditable = l10n.t('Install project as editable'); } export class VenvManagerStringsWithUv { static venvManagerDescription = l10n.t('Manages virtual environments created using `venv [uv]`'); - static venvInitialize = l10n.t('Initializing virtual environments'); - static venvRefreshing = l10n.t('Refreshing virtual environments'); - static venvGlobalFolder = l10n.t('Select a folder to create a global virtual environment'); - static venvGlobalFoldersSetting = l10n.t('Venv Folders Setting'); - - static venvErrorNoBasePython = l10n.t('No base Python found'); - static venvErrorNoPython3 = l10n.t('Did not find any base Python 3'); - - static venvName = l10n.t('Enter a name for the virtual environment'); - static venvNameErrorEmpty = l10n.t('Name cannot be empty'); - static venvNameErrorExists = l10n.t('A folder with the same name already exists'); - static venvCreateFailed = l10n.t('Failed to create virtual environment'); - - static venvRemoving = l10n.t('Removing virtual environment'); - static venvRemoveFailed = l10n.t('Failed to remove virtual environment'); - - static installEditable = l10n.t('Install project as editable'); - static searchingDependencies = l10n.t('Searching for dependencies'); - - static selectQuickOrCustomize = l10n.t('Select environment creation mode'); - static quickCreate = l10n.t('Quick Create'); static quickCreateDescription = l10n.t( 'Create a virtual environment in the workspace root using uv for fast installs', ); - static customize = l10n.t('Custom'); static customizeDescription = l10n.t( 'Choose python version, location, packages, name, etc. (uses uv for installs)', ); + static installEditable = l10n.t('Install project as editable using uv'); } /** diff --git a/src/managers/builtin/pipUtils.ts b/src/managers/builtin/pipUtils.ts index 31bb5f22..e3180e15 100644 --- a/src/managers/builtin/pipUtils.ts +++ b/src/managers/builtin/pipUtils.ts @@ -4,7 +4,7 @@ import * as path from 'path'; import { LogOutputChannel, ProgressLocation, QuickInputButtons, QuickPickItem, Uri } from 'vscode'; import { PackageManagementOptions, PythonEnvironment, PythonEnvironmentApi, PythonProject } from '../../api'; import { EXTENSION_ROOT_DIR } from '../../common/constants'; -import { PackageManagement, Pickers, VenvManagerStringsNoUv } from '../../common/localize'; +import { PackageManagement, Pickers, VenvManagerCommonStrings, VenvManagerStrings } from '../../common/localize'; import { traceInfo } from '../../common/logging'; import { showQuickPickWithButtons, withProgress } from '../../common/window.apis'; import { findFiles } from '../../common/workspace.apis'; @@ -36,7 +36,7 @@ function getTomlInstallable(toml: tomljs.JsonMap, tomlPath: Uri): Installable[] extras.push({ name, displayName: name, - description: VenvManagerStringsNoUv.installEditable, + description: VenvManagerStrings.installEditable, group: 'TOML', args: ['-e', projectDir], uri: tomlPath, @@ -177,7 +177,7 @@ export async function getProjectInstallable( await withProgress( { location: ProgressLocation.Notification, - title: VenvManagerStringsNoUv.searchingDependencies, + title: VenvManagerCommonStrings.searchingDependencies, }, async (_progress, token) => { const results: Uri[] = ( diff --git a/src/managers/builtin/venvManager.ts b/src/managers/builtin/venvManager.ts index 32264534..978f8908 100644 --- a/src/managers/builtin/venvManager.ts +++ b/src/managers/builtin/venvManager.ts @@ -29,7 +29,7 @@ import { SetEnvironmentScope, } from '../../api'; import { PYTHON_EXTENSION_ID } from '../../common/constants'; -import { VenvManagerStringsNoUv } from '../../common/localize'; +import { VenvManagerCommonStrings, VenvManagerStringsNoUv } from '../../common/localize'; import { traceError, traceWarn } from '../../common/logging'; import { createDeferred, Deferred } from '../../common/utils/deferred'; import { showErrorMessage, withProgress } from '../../common/window.apis'; @@ -97,7 +97,7 @@ export class VenvManager implements EnvironmentManager { this._initialized = createDeferred(); try { - await this.internalRefresh(undefined, false, VenvManagerStringsNoUv.venvInitialize); + await this.internalRefresh(undefined, false, VenvManagerCommonStrings.venvInitialize); } finally { this._initialized.resolve(); } @@ -156,7 +156,7 @@ export class VenvManager implements EnvironmentManager { // error on missing information if (!this.globalEnv) { this.log.error('No base python found'); - showErrorMessage(VenvManagerStringsNoUv.venvErrorNoBasePython); + showErrorMessage(VenvManagerCommonStrings.venvErrorNoBasePython); throw new Error('No base python found'); } if (!this.globalEnv.version.startsWith('3.')) { @@ -164,7 +164,7 @@ export class VenvManager implements EnvironmentManager { globals.forEach((e, i) => { this.log.error(`${i}: ${e.version} : ${e.environmentPath.fsPath}`); }); - showErrorMessage(VenvManagerStringsNoUv.venvErrorNoPython3); + showErrorMessage(VenvManagerCommonStrings.venvErrorNoPython3); throw new Error('Did not find any base python 3.*'); } if (this.globalEnv && this.globalEnv.version.startsWith('3.')) { @@ -292,14 +292,14 @@ export class VenvManager implements EnvironmentManager { } async refresh(scope: RefreshEnvironmentsScope): Promise { - return this.internalRefresh(scope, true, VenvManagerStringsNoUv.venvRefreshing); + return this.internalRefresh(scope, true, VenvManagerCommonStrings.venvRefreshing); } async watcherRefresh(): Promise { if (this.skipWatcherRefresh) { return; } - return this.internalRefresh(undefined, true, VenvManagerStringsNoUv.venvRefreshing); + return this.internalRefresh(undefined, true, VenvManagerCommonStrings.venvRefreshing); } private async internalRefresh( diff --git a/src/managers/builtin/venvUtils.ts b/src/managers/builtin/venvUtils.ts index dde71733..4f480bca 100644 --- a/src/managers/builtin/venvUtils.ts +++ b/src/managers/builtin/venvUtils.ts @@ -4,7 +4,7 @@ import * as path from 'path'; import { l10n, LogOutputChannel, ProgressLocation, QuickPickItem, QuickPickItemKind, ThemeIcon, Uri } from 'vscode'; import { EnvironmentManager, PythonEnvironment, PythonEnvironmentApi, PythonEnvironmentInfo } from '../../api'; import { ENVS_EXTENSION_ID } from '../../common/constants'; -import { Common, VenvManagerStringsNoUv } from '../../common/localize'; +import { Common, VenvManagerCommonStrings, VenvManagerStrings, VenvManagerStringsNoUv } from '../../common/localize'; import { traceInfo } from '../../common/logging'; import { getWorkspacePersistentState } from '../../common/persistentState'; import { pickEnvironmentFrom } from '../../common/pickers/environments'; @@ -209,7 +209,7 @@ export async function getGlobalVenvLocation(): Promise { const items: FolderQuickPickItem[] = [ { label: Common.browse, - description: VenvManagerStringsNoUv.venvGlobalFolder, + description: VenvManagerCommonStrings.venvGlobalFolder, }, ]; @@ -217,7 +217,7 @@ export async function getGlobalVenvLocation(): Promise { if (venvPaths.length > 0) { items.push( { - label: VenvManagerStringsNoUv.venvGlobalFoldersSetting, + label: VenvManagerCommonStrings.venvGlobalFoldersSetting, kind: QuickPickItemKind.Separator, }, ...venvPaths.map((p) => ({ @@ -243,7 +243,7 @@ export async function getGlobalVenvLocation(): Promise { } const selected = await showQuickPick(items, { - placeHolder: VenvManagerStringsNoUv.venvGlobalFolder, + placeHolder: VenvManagerCommonStrings.venvGlobalFolder, ignoreFocusOut: true, }); @@ -269,24 +269,24 @@ async function createWithCustomization(version: string): Promise e.version.startsWith('3.')); if (filtered.length === 0) { log.error('Did not find any base python 3.*'); - showErrorMessage(VenvManagerStringsNoUv.venvErrorNoPython3); + showErrorMessage(VenvManagerCommonStrings.venvErrorNoPython3); basePythons.forEach((e, i) => { log.error(`${i}: ${e.version} : ${e.environmentPath.fsPath}`); }); @@ -471,15 +471,15 @@ export async function createPythonVenv( } const name = await showInputBox({ - prompt: VenvManagerStringsNoUv.venvName, + prompt: VenvManagerCommonStrings.venvName, value: '.venv', ignoreFocusOut: true, validateInput: async (value) => { if (!value) { - return VenvManagerStringsNoUv.venvNameErrorEmpty; + return VenvManagerCommonStrings.venvNameErrorEmpty; } if (await fsapi.pathExists(path.join(venvRoot.fsPath, value))) { - return VenvManagerStringsNoUv.venvNameErrorExists; + return VenvManagerCommonStrings.venvNameErrorExists; } }, }); @@ -527,7 +527,7 @@ export async function removeVenv(environment: PythonEnvironment, log: LogOutputC const result = await withProgress( { location: ProgressLocation.Notification, - title: VenvManagerStringsNoUv.venvRemoving, + title: VenvManagerCommonStrings.venvRemoving, }, async () => { try { @@ -535,7 +535,7 @@ export async function removeVenv(environment: PythonEnvironment, log: LogOutputC return true; } catch (e) { log.error(`Failed to remove virtual environment: ${e}`); - showErrorMessage(VenvManagerStringsNoUv.venvRemoveFailed); + showErrorMessage(VenvManagerCommonStrings.venvRemoveFailed); return false; } }, From bb30e4fa22f6226575258eef8a9da6876f2d91d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Sep 2025 17:02:50 +0000 Subject: [PATCH 8/8] Address code review feedback: remove comment and simplify progress message Co-authored-by: karthiknadig <3840081+karthiknadig@users.noreply.github.com> --- src/managers/builtin/venvManager.ts | 2 +- src/managers/builtin/venvUtils.ts | 17 ++++++----------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/managers/builtin/venvManager.ts b/src/managers/builtin/venvManager.ts index 978f8908..b84451f4 100644 --- a/src/managers/builtin/venvManager.ts +++ b/src/managers/builtin/venvManager.ts @@ -66,7 +66,7 @@ export class VenvManager implements EnvironmentManager { public readonly onDidChangeEnvironments = this._onDidChangeEnvironments.event; readonly name: string; - displayName: string; // Made mutable to update with uv detection + displayName: string; readonly preferredPackageManagerId: string; readonly description?: string | undefined; readonly tooltip?: string | MarkdownString | undefined; diff --git a/src/managers/builtin/venvUtils.ts b/src/managers/builtin/venvUtils.ts index 4f480bca..4c604d06 100644 --- a/src/managers/builtin/venvUtils.ts +++ b/src/managers/builtin/venvUtils.ts @@ -306,17 +306,12 @@ async function createWithProgress( os.platform() === 'win32' ? path.join(envPath, 'Scripts', 'python.exe') : path.join(envPath, 'bin', 'python'); const useUv = await isUvInstalled(log); - const progressTitle = useUv - ? l10n.t( - 'Creating virtual environment named {0} using python version {1} [uv].', - path.basename(envPath), - basePython.version, - ) - : l10n.t( - 'Creating virtual environment named {0} using python version {1}.', - path.basename(envPath), - basePython.version, - ); + const progressTitle = l10n.t( + 'Creating virtual environment named {0} using python version {1}{2}.', + path.basename(envPath), + basePython.version, + useUv ? ' [uv]' : '', + ); return await withProgress( {