diff --git a/src/common/localize.ts b/src/common/localize.ts index 1674a397..2b9017b9 100644 --- a/src/common/localize.ts +++ b/src/common/localize.ts @@ -77,32 +77,51 @@ 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 const venvErrorNoBasePython = l10n.t('No base Python found'); - export const venvErrorNoPython3 = l10n.t('Did not find any base Python 3'); +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 searchingDependencies = l10n.t('Searching for dependencies'); + static selectQuickOrCustomize = l10n.t('Select environment creation mode'); + static quickCreate = l10n.t('Quick Create'); + static customize = l10n.t('Custom'); +} - 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'); +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 const venvRemoving = l10n.t('Removing virtual environment'); - export const venvRemoveFailed = l10n.t('Failed to remove virtual environment'); +export class VenvManagerStringsWithUv { + static venvManagerDescription = l10n.t('Manages virtual environments created using `venv [uv]`'); + static quickCreateDescription = l10n.t( + 'Create a virtual environment in the workspace root using uv for fast installs', + ); + 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'); +} - export const installEditable = l10n.t('Install project as editable'); - export const searchingDependencies = l10n.t('Searching for dependencies'); +/** + * 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 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.'); +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 d1f4a400..33d817d4 100644 --- a/src/managers/builtin/main.ts +++ b/src/managers/builtin/main.ts @@ -1,10 +1,12 @@ 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'; 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 +22,14 @@ 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'); + // Switch all venv-related UI strings to uv versions + setVenvManagerStrings(VenvManagerStringsWithUv); + } + disposables.push( api.registerPackageManager(pkgManager), api.registerEnvironmentManager(envManager), diff --git a/src/managers/builtin/pipUtils.ts b/src/managers/builtin/pipUtils.ts index ef3ef88b..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, VenvManagerStrings } 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'; @@ -177,7 +177,7 @@ export async function getProjectInstallable( await withProgress( { location: ProgressLocation.Notification, - title: VenvManagerStrings.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 cf5027a9..b84451f4 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 { 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'; @@ -66,7 +66,7 @@ export class VenvManager implements EnvironmentManager { public readonly onDidChangeEnvironments = this._onDidChangeEnvironments.event; readonly name: string; - readonly displayName: string; + displayName: string; readonly preferredPackageManagerId: string; readonly description?: string | undefined; readonly tooltip?: string | MarkdownString | undefined; @@ -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, VenvManagerCommonStrings.venvInitialize); } finally { this._initialized.resolve(); } @@ -119,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, @@ -148,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(VenvManagerCommonStrings.venvErrorNoBasePython); throw new Error('No base python found'); } if (!this.globalEnv.version.startsWith('3.')) { @@ -156,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(VenvManagerCommonStrings.venvErrorNoPython3); throw new Error('Did not find any base python 3.*'); } if (this.globalEnv && this.globalEnv.version.startsWith('3.')) { @@ -284,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, VenvManagerCommonStrings.venvRefreshing); } async watcherRefresh(): Promise { if (this.skipWatcherRefresh) { return; } - return this.internalRefresh(undefined, true, VenvManagerStrings.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 ebcd630a..4c604d06 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, 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: VenvManagerStrings.venvGlobalFolder, + description: VenvManagerCommonStrings.venvGlobalFolder, }, ]; @@ -217,7 +217,7 @@ export async function getGlobalVenvLocation(): Promise { if (venvPaths.length > 0) { items.push( { - label: VenvManagerStrings.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: VenvManagerStrings.venvGlobalFolder, + placeHolder: VenvManagerCommonStrings.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 +356,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 +378,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(VenvManagerCommonStrings.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(VenvManagerCommonStrings.venvErrorNoPython3); basePythons.forEach((e, i) => { log.error(`${i}: ${e.version} : ${e.environmentPath.fsPath}`); }); @@ -457,15 +466,15 @@ export async function createPythonVenv( } const name = await showInputBox({ - prompt: VenvManagerStrings.venvName, + prompt: VenvManagerCommonStrings.venvName, value: '.venv', ignoreFocusOut: true, validateInput: async (value) => { if (!value) { - return VenvManagerStrings.venvNameErrorEmpty; + return VenvManagerCommonStrings.venvNameErrorEmpty; } if (await fsapi.pathExists(path.join(venvRoot.fsPath, value))) { - return VenvManagerStrings.venvNameErrorExists; + return VenvManagerCommonStrings.venvNameErrorExists; } }, }); @@ -513,7 +522,7 @@ export async function removeVenv(environment: PythonEnvironment, log: LogOutputC const result = await withProgress( { location: ProgressLocation.Notification, - title: VenvManagerStrings.venvRemoving, + title: VenvManagerCommonStrings.venvRemoving, }, async () => { try { @@ -521,7 +530,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(VenvManagerCommonStrings.venvRemoveFailed); return false; } },