diff --git a/src/common/localize.ts b/src/common/localize.ts index 3caa8a9b..2df9dbee 100644 --- a/src/common/localize.ts +++ b/src/common/localize.ts @@ -24,6 +24,7 @@ export namespace WorkbenchStrings { export namespace Interpreter { export const statusBarSelect = l10n.t('Select Interpreter'); export const browsePath = l10n.t('Browse...'); + export const enterPath = l10n.t('Enter Path...'); export const createVirtualEnvironment = l10n.t('Create Virtual Environment...'); } diff --git a/src/common/pickers/environments.ts b/src/common/pickers/environments.ts index 5f6ab63b..2f27b9b6 100644 --- a/src/common/pickers/environments.ts +++ b/src/common/pickers/environments.ts @@ -7,7 +7,7 @@ import { EventNames } from '../telemetry/constants'; import { sendTelemetryEvent } from '../telemetry/sender'; import { isWindows } from '../utils/platformUtils'; import { handlePythonPath } from '../utils/pythonPath'; -import { showOpenDialog, showQuickPick, showQuickPickWithButtons, withProgress } from '../window.apis'; +import { showInputBoxWithButtons, showOpenDialog, showQuickPick, showQuickPickWithButtons, withProgress } from '../window.apis'; import { pickEnvironmentManager } from './managers'; type QuickPickIcon = @@ -69,6 +69,41 @@ async function browseForPython( return environment; } +async function enterPythonPath( + managers: InternalEnvironmentManager[], + projectEnvManagers: InternalEnvironmentManager[], +): Promise { + const placeholder = isWindows() ? 'C:\\path\\to\\python\\executable' : '/path/to/python/executable'; + const inputPath = await showInputBoxWithButtons({ + prompt: 'Enter the path to the Python executable', + placeHolder: placeholder, + ignoreFocusOut: true, + validateInput: (value) => { + if (!value || value.trim().length === 0) { + return 'Please enter a valid path'; + } + return null; + }, + }); + + if (!inputPath) { + return; // User cancelled + } + + const uri = Uri.file(inputPath.trim()); + const environment = await withProgress( + { + location: ProgressLocation.Notification, + cancellable: false, + }, + async (reporter, token) => { + const env = await handlePythonPath(uri, managers, projectEnvManagers, reporter, token); + return env; + }, + ); + return environment; +} + async function createEnvironment( managers: InternalEnvironmentManager[], projectEnvManagers: InternalEnvironmentManager[], @@ -124,6 +159,8 @@ async function pickEnvironmentImpl( if (selected && !Array.isArray(selected)) { if (selected.label === Interpreter.browsePath) { return browseForPython(managers, projectEnvManagers); + } else if (selected.label === Interpreter.enterPath) { + return enterPythonPath(managers, projectEnvManagers); } else if (selected.label === Interpreter.createVirtualEnvironment) { sendTelemetryEvent(EventNames.CREATE_ENVIRONMENT, undefined, { manager: 'none', @@ -146,6 +183,10 @@ export async function pickEnvironment( label: Interpreter.browsePath, iconPath: new ThemeIcon('folder'), }, + { + label: Interpreter.enterPath, + iconPath: new ThemeIcon('edit'), + }, { label: '', kind: QuickPickItemKind.Separator, diff --git a/src/test/common/environmentPicker.unit.test.ts b/src/test/common/environmentPicker.unit.test.ts index dc0ee3c1..61a3ef08 100644 --- a/src/test/common/environmentPicker.unit.test.ts +++ b/src/test/common/environmentPicker.unit.test.ts @@ -4,6 +4,7 @@ import assert from 'node:assert'; import { Uri } from 'vscode'; import { PythonEnvironment } from '../../api'; +import { Interpreter } from '../../common/localize'; /** * Test the logic used in environment pickers to include interpreter paths in descriptions @@ -84,4 +85,21 @@ suite('Environment Picker Description Logic', () => { assert.strictEqual(description, 'System Python (C:\\Python39\\python.exe)'); }); }); + + suite('Picker options availability', () => { + test('should have Browse option available', () => { + assert.strictEqual(typeof Interpreter.browsePath, 'string'); + assert.strictEqual(Interpreter.browsePath, 'Browse...'); + }); + + test('should have Enter Path option available', () => { + assert.strictEqual(typeof Interpreter.enterPath, 'string'); + assert.strictEqual(Interpreter.enterPath, 'Enter Path...'); + }); + + test('should have Create Virtual Environment option available', () => { + assert.strictEqual(typeof Interpreter.createVirtualEnvironment, 'string'); + assert.strictEqual(Interpreter.createVirtualEnvironment, 'Create Virtual Environment...'); + }); + }); });