Skip to content
Merged
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
2 changes: 2 additions & 0 deletions src/common/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export namespace Interpreter {
export const statusBarSelect = l10n.t('Select Interpreter');
export const browsePath = l10n.t('Browse...');
export const createVirtualEnvironment = l10n.t('Create Virtual Environment...');
export const enterInterpreterPath = l10n.t('Enter Interpreter Path...');
export const enterInterpreterPathDescription = l10n.t('Browse and select a Python interpreter from anywhere');
}

export namespace PackageManagement {
Expand Down
35 changes: 32 additions & 3 deletions src/common/pickers/managers.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import { commands, QuickInputButtons, QuickPickItem, QuickPickItemKind, workspace, WorkspaceFolder } from 'vscode';
import {
commands,
QuickInputButtons,
QuickPickItem,
QuickPickItemKind,
ThemeIcon,
workspace,
WorkspaceFolder,
} from 'vscode';
import { PythonProjectCreator } from '../../api';
import { InternalEnvironmentManager, InternalPackageManager } from '../../internal.api';
import { Common, Pickers } from '../localize';
import { Common, Interpreter, Pickers } from '../localize';
import { showQuickPickWithButtons } from '../window.apis';

export const ENTER_INTERPRETER_PATH_ID = 'EnterInterpreterPath';

function getDescription(mgr: InternalEnvironmentManager | InternalPackageManager): string | undefined {
if (mgr.description) {
return mgr.description;
Expand All @@ -22,17 +32,19 @@ export async function pickEnvironmentManager(
managers: InternalEnvironmentManager[],
defaultManagers?: InternalEnvironmentManager[],
showBackButton?: boolean,
showEnterInterpreterPath?: boolean,
): Promise<string | undefined> {
if (managers.length === 0) {
return;
}

if (managers.length === 1 && !managers[0].supportsQuickCreate) {
if (managers.length === 1 && !managers[0].supportsQuickCreate && !showEnterInterpreterPath) {
// If there's only one manager and it doesn't support quick create, return its ID directly.
return managers[0].id;
}

const items: (QuickPickItem | (QuickPickItem & { id: string }))[] = [];

if (defaultManagers && defaultManagers.length > 0) {
items.push({
label: Common.recommended,
Expand Down Expand Up @@ -71,6 +83,23 @@ export async function pickEnvironmentManager(
id: m.id,
})),
);

// Add "Enter Interpreter Path" option at the bottom if enabled
if (showEnterInterpreterPath) {
items.push(
{
label: '',
kind: QuickPickItemKind.Separator,
},
{
label: Interpreter.enterInterpreterPath,
description: Interpreter.enterInterpreterPathDescription,
id: ENTER_INTERPRETER_PATH_ID,
iconPath: new ThemeIcon('folder-opened'),
},
);
}

const item = await showQuickPickWithButtons(items, {
placeHolder: Pickers.Managers.selectEnvironmentManager,
ignoreFocusOut: true,
Expand Down
92 changes: 89 additions & 3 deletions src/features/envCommands.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import { commands, QuickInputButtons, TaskExecution, TaskRevealKind, Terminal, Uri, workspace } from 'vscode';
import {
commands,
ProgressLocation,
QuickInputButtons,
TaskExecution,
TaskRevealKind,
Terminal,
Uri,
workspace,
} from 'vscode';
import {
CreateEnvironmentOptions,
PythonEnvironment,
Expand All @@ -21,15 +30,25 @@ import { removePythonProjectSetting, setEnvironmentManager, setPackageManager }

import { clipboardWriteText } from '../common/env.apis';
import {} from '../common/errors/utils';
import { Pickers } from '../common/localize';
import { pickEnvironment } from '../common/pickers/environments';
import {
ENTER_INTERPRETER_PATH_ID,
pickCreator,
pickEnvironmentManager,
pickPackageManager,
pickWorkspaceFolder,
} from '../common/pickers/managers';
import { pickProject, pickProjectMany } from '../common/pickers/projects';
import { activeTextEditor, showErrorMessage, showInformationMessage } from '../common/window.apis';
import { isWindows } from '../common/utils/platformUtils';
import { handlePythonPath } from '../common/utils/pythonPath';
import {
activeTextEditor,
showErrorMessage,
showInformationMessage,
showOpenDialog,
withProgress,
} from '../common/window.apis';
import { runAsTask } from './execution/runAsTask';
import { runInTerminal } from './terminal/runInTerminal';
import { TerminalManager } from './terminal/terminalManager';
Expand All @@ -44,6 +63,46 @@ import {
PythonEnvTreeItem,
} from './views/treeViewItems';

/**
* Opens a file dialog to browse for a Python interpreter and resolves it using available managers.
*/
async function browseAndResolveInterpreter(
em: EnvironmentManagers,
projectUris?: Uri[],
): Promise<PythonEnvironment | undefined> {
const filters = isWindows() ? { python: ['exe'] } : undefined;
const uris = await showOpenDialog({
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
filters,
title: Pickers.Environments.selectExecutable,
});
if (!uris || uris.length === 0) {
return;
}
const interpreterUri = uris[0];

// Get project-specific managers if projectUris are provided
const projectEnvManagers = projectUris
? projectUris
.map((uri) => em.getEnvironmentManager(uri))
.filter((m): m is InternalEnvironmentManager => m !== undefined)
: [];

const environment = await withProgress(
{
location: ProgressLocation.Notification,
cancellable: false,
},
async (reporter, token) => {
return await handlePythonPath(interpreterUri, em.managers, projectEnvManagers, reporter, token);
},
);

return environment;
}

export async function refreshManagerCommand(context: unknown): Promise<void> {
if (context instanceof EnvManagerTreeItem) {
const manager = (context as EnvManagerTreeItem).manager;
Expand Down Expand Up @@ -133,7 +192,22 @@ export async function createAnyEnvironmentCommand(
const select = options?.selectEnvironment;
const projects = pm.getProjects(options?.uri ? [options?.uri] : undefined);
if (projects.length === 0) {
const managerId = await pickEnvironmentManager(em.managers.filter((m) => m.supportsCreate));
const managerId = await pickEnvironmentManager(
em.managers.filter((m) => m.supportsCreate),
undefined,
undefined,
true, // showEnterInterpreterPath
);

// Handle "Enter Interpreter Path" selection
if (managerId === ENTER_INTERPRETER_PATH_ID) {
const env = await browseAndResolveInterpreter(em);
if (select && env) {
await em.setEnvironments('global', env);
}
return env;
}

const manager = em.managers.find((m) => m.id === managerId);
if (manager) {
const env = await manager.create('global', { ...options });
Expand Down Expand Up @@ -165,7 +239,19 @@ export async function createAnyEnvironmentCommand(
em.managers.filter((m) => m.supportsCreate),
defaultManagers,
options?.showBackButton,
true, // showEnterInterpreterPath
);

// Handle "Enter Interpreter Path" selection
if (managerId === ENTER_INTERPRETER_PATH_ID) {
const projectUris = selected.map((p) => p.uri);
const env = await browseAndResolveInterpreter(em, projectUris);
if (select && env) {
await em.setEnvironments(projectUris, env);
}
return env;
}

if (managerId?.startsWith('QuickCreate#')) {
quickCreate = true;
managerId = managerId.replace('QuickCreate#', '');
Expand Down
Loading