Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 41 additions & 22 deletions src/common/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@karthiknadig can you just review this to make sure this structure of setting strings makes sense and follows good coding practices

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually looks a bit weird. There are several string duplicates.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@karthiknadig moved to duplicates to a diff function so there shouldn't be repeats. Lmk your thoughts

VenvManagerStrings = val;
}

export namespace SysManagerStrings {
Expand Down
10 changes: 10 additions & 0 deletions src/managers/builtin/main.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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),
Expand Down
4 changes: 2 additions & 2 deletions src/managers/builtin/pipUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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[] = (
Expand Down
24 changes: 16 additions & 8 deletions src/managers/builtin/venvManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand All @@ -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');
}
Expand All @@ -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();
}
Expand All @@ -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,
Expand Down Expand Up @@ -148,15 +156,15 @@ 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.')) {
this.log.error('Did not find any base python 3.*');
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.')) {
Expand Down Expand Up @@ -284,14 +292,14 @@ export class VenvManager implements EnvironmentManager {
}

async refresh(scope: RefreshEnvironmentsScope): Promise<void> {
return this.internalRefresh(scope, true, VenvManagerStrings.venvRefreshing);
return this.internalRefresh(scope, true, VenvManagerCommonStrings.venvRefreshing);
}

async watcherRefresh(): Promise<void> {
if (this.skipWatcherRefresh) {
return;
}
return this.internalRefresh(undefined, true, VenvManagerStrings.venvRefreshing);
return this.internalRefresh(undefined, true, VenvManagerCommonStrings.venvRefreshing);
}

private async internalRefresh(
Expand Down
59 changes: 34 additions & 25 deletions src/managers/builtin/venvUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -209,15 +209,15 @@ export async function getGlobalVenvLocation(): Promise<Uri | undefined> {
const items: FolderQuickPickItem[] = [
{
label: Common.browse,
description: VenvManagerStrings.venvGlobalFolder,
description: VenvManagerCommonStrings.venvGlobalFolder,
},
];

const venvPaths = getVenvFoldersSetting();
if (venvPaths.length > 0) {
items.push(
{
label: VenvManagerStrings.venvGlobalFoldersSetting,
label: VenvManagerCommonStrings.venvGlobalFoldersSetting,
kind: QuickPickItemKind.Separator,
},
...venvPaths.map((p) => ({
Expand All @@ -243,7 +243,7 @@ export async function getGlobalVenvLocation(): Promise<Uri | undefined> {
}

const selected = await showQuickPick(items, {
placeHolder: VenvManagerStrings.venvGlobalFolder,
placeHolder: VenvManagerCommonStrings.venvGlobalFolder,
ignoreFocusOut: true,
});

Expand All @@ -269,24 +269,24 @@ async function createWithCustomization(version: string): Promise<boolean | undef
const selection: QuickPickItem | undefined = await showQuickPick(
[
{
label: VenvManagerStrings.quickCreate,
description: VenvManagerStrings.quickCreateDescription,
label: VenvManagerCommonStrings.quickCreate,
description: VenvManagerStringsNoUv.quickCreateDescription,
detail: l10n.t('Uses Python version {0} and installs workspace dependencies.', version),
},
{
label: VenvManagerStrings.customize,
label: VenvManagerCommonStrings.customize,
description: VenvManagerStrings.customizeDescription,
},
],
{
placeHolder: VenvManagerStrings.selectQuickOrCustomize,
placeHolder: VenvManagerCommonStrings.selectQuickOrCustomize,
ignoreFocusOut: true,
},
);

if (selection === undefined) {
return undefined;
} else if (selection.label === VenvManagerStrings.quickCreate) {
} else if (selection.label === VenvManagerCommonStrings.quickCreate) {
return false;
}
return true;
Expand All @@ -305,19 +305,22 @@ async function createWithProgress(
const pythonPath =
os.platform() === 'win32' ? path.join(envPath, 'Scripts', 'python.exe') : path.join(envPath, 'bin', 'python');

const useUv = await isUvInstalled(log);
const progressTitle = l10n.t(
'Creating virtual environment named {0} using python version {1}{2}.',
path.basename(envPath),
basePython.version,
useUv ? ' [uv]' : '',
);

return await withProgress(
{
location: ProgressLocation.Notification,
title: l10n.t(
'Creating virtual environment named {0} using python version {1}.',
path.basename(envPath),
basePython.version,
),
title: progressTitle,
},
async () => {
const result: CreateEnvironmentResult = {};
try {
const useUv = await isUvInstalled(log);
// env creation
if (basePython.execInfo?.run.executable) {
if (useUv) {
Expand Down Expand Up @@ -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;
},
Expand All @@ -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}`);
});
Expand Down Expand Up @@ -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;
}
},
});
Expand Down Expand Up @@ -513,15 +522,15 @@ export async function removeVenv(environment: PythonEnvironment, log: LogOutputC
const result = await withProgress(
{
location: ProgressLocation.Notification,
title: VenvManagerStrings.venvRemoving,
title: VenvManagerCommonStrings.venvRemoving,
},
async () => {
try {
await fsapi.remove(envPath);
return true;
} catch (e) {
log.error(`Failed to remove virtual environment: ${e}`);
showErrorMessage(VenvManagerStrings.venvRemoveFailed);
showErrorMessage(VenvManagerCommonStrings.venvRemoveFailed);
return false;
}
},
Expand Down
Loading