Skip to content

Commit 816a92a

Browse files
authored
Allow entering interpreter path (#9)
Alos fixes #6
1 parent ff9ac2d commit 816a92a

24 files changed

+1010
-433
lines changed

src/api.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ export type GetEnvironmentScope = undefined | Uri;
217217
* Type representing the scope for creating a Python environment.
218218
* Can be a Python project or 'global'.
219219
*/
220-
export type CreateEnvironmentScope = PythonProject | 'global';
220+
export type CreateEnvironmentScope = Uri | Uri[] | 'global';
221221
/**
222222
* The scope for which environments are to be refreshed.
223223
* - `undefined`: Search for environments globally and workspaces.
@@ -828,7 +828,7 @@ export interface PythonEnvironmentManagerApi {
828828
* @param scope - The scope within which to set the environment.
829829
* @param environment - The Python environment to set. If undefined, the environment is unset.
830830
*/
831-
setEnvironment(scope: SetEnvironmentScope, environment?: PythonEnvironment): void;
831+
setEnvironment(scope: SetEnvironmentScope, environment?: PythonEnvironment): Promise<void>;
832832

833833
/**
834834
* Retrieves the current Python environment within the specified scope.

src/common/errors/utils.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,14 @@ export async function showErrorMessage(message: string, log?: LogOutputChannel)
1919
}
2020
}
2121
}
22+
23+
export async function showWarningMessage(message: string, log?: LogOutputChannel) {
24+
const result = await window.showWarningMessage(message, 'View Logs');
25+
if (result === 'View Logs') {
26+
if (log) {
27+
log.show();
28+
} else {
29+
commands.executeCommand('python-envs.viewLogs');
30+
}
31+
}
32+
}

src/common/localize.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { l10n } from 'vscode';
22

33
export namespace Common {
4-
export const recommended = l10n.t('recommended');
4+
export const recommended = l10n.t('Recommended');
55
export const install = l10n.t('Install');
66
export const uninstall = l10n.t('Uninstall');
77
export const openInBrowser = l10n.t('Open in Browser');
@@ -10,6 +10,8 @@ export namespace Common {
1010

1111
export namespace Interpreter {
1212
export const statusBarSelect = l10n.t('Select Interpreter');
13+
export const browsePath = l10n.t('Browse...');
14+
export const createVirtualEnvironment = l10n.t('Create Virtual Environment...');
1315
}
1416

1517
export namespace PackageManagement {

src/common/pickers/environments.ts

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import { Uri, ThemeIcon, QuickPickItem, QuickPickItemKind, ProgressLocation, QuickInputButtons } from 'vscode';
2+
import { IconPath, PythonEnvironment, PythonProject } from '../../api';
3+
import { InternalEnvironmentManager } from '../../internal.api';
4+
import { Common, Interpreter } from '../localize';
5+
import { showQuickPickWithButtons, showQuickPick, showOpenDialog, withProgress } from '../window.apis';
6+
import { isWindows } from '../../managers/common/utils';
7+
import { traceError } from '../logging';
8+
import { pickEnvironmentManager } from './managers';
9+
import { handlePythonPath } from '../utils/pythonPath';
10+
11+
type QuickPickIcon =
12+
| Uri
13+
| {
14+
light: Uri;
15+
dark: Uri;
16+
}
17+
| ThemeIcon
18+
| undefined;
19+
20+
function getIconPath(i: IconPath | undefined): QuickPickIcon {
21+
if (i === undefined || i instanceof ThemeIcon) {
22+
return i;
23+
}
24+
25+
if (i instanceof Uri) {
26+
return i.fsPath.endsWith('__icon__.py') ? undefined : i;
27+
}
28+
29+
if (typeof i === 'string') {
30+
return Uri.file(i);
31+
}
32+
33+
return {
34+
light: i.light instanceof Uri ? i.light : Uri.file(i.light),
35+
dark: i.dark instanceof Uri ? i.dark : Uri.file(i.dark),
36+
};
37+
}
38+
39+
interface EnvironmentPickOptions {
40+
recommended?: PythonEnvironment;
41+
showBackButton?: boolean;
42+
projects: PythonProject[];
43+
}
44+
async function browseForPython(
45+
managers: InternalEnvironmentManager[],
46+
projectEnvManagers: InternalEnvironmentManager[],
47+
): Promise<PythonEnvironment | undefined> {
48+
const filters = isWindows() ? { python: ['exe'] } : undefined;
49+
const uris = await showOpenDialog({
50+
canSelectFiles: true,
51+
canSelectFolders: false,
52+
canSelectMany: false,
53+
filters,
54+
title: 'Select Python executable',
55+
});
56+
if (!uris || uris.length === 0) {
57+
return;
58+
}
59+
const uri = uris[0];
60+
61+
const environment = await withProgress(
62+
{
63+
location: ProgressLocation.Notification,
64+
cancellable: false,
65+
},
66+
async (reporter, token) => {
67+
const env = await handlePythonPath(uri, managers, projectEnvManagers, reporter, token);
68+
return env;
69+
},
70+
);
71+
return environment;
72+
}
73+
74+
async function createEnvironment(
75+
managers: InternalEnvironmentManager[],
76+
projectEnvManagers: InternalEnvironmentManager[],
77+
options: EnvironmentPickOptions,
78+
): Promise<PythonEnvironment | undefined> {
79+
const managerId = await pickEnvironmentManager(
80+
managers.filter((m) => m.supportsCreate),
81+
projectEnvManagers.filter((m) => m.supportsCreate),
82+
);
83+
84+
const manager = managers.find((m) => m.id === managerId);
85+
if (manager) {
86+
try {
87+
const env = await manager.create(options.projects.map((p) => p.uri));
88+
return env;
89+
} catch (ex) {
90+
if (ex === QuickInputButtons.Back) {
91+
return createEnvironment(managers, projectEnvManagers, options);
92+
}
93+
traceError(`Failed to create environment using ${manager.id}`, ex);
94+
throw ex;
95+
}
96+
}
97+
}
98+
99+
async function pickEnvironmentImpl(
100+
items: (QuickPickItem | (QuickPickItem & { result: PythonEnvironment }))[],
101+
managers: InternalEnvironmentManager[],
102+
projectEnvManagers: InternalEnvironmentManager[],
103+
options: EnvironmentPickOptions,
104+
): Promise<PythonEnvironment | undefined> {
105+
const selected = await showQuickPickWithButtons(items, {
106+
placeHolder: `Select a Python Environment`,
107+
ignoreFocusOut: true,
108+
showBackButton: options?.showBackButton,
109+
});
110+
111+
if (selected && !Array.isArray(selected)) {
112+
if (selected.label === Interpreter.browsePath) {
113+
return browseForPython(managers, projectEnvManagers);
114+
} else if (selected.label === Interpreter.createVirtualEnvironment) {
115+
return createEnvironment(managers, projectEnvManagers, options);
116+
}
117+
return (selected as { result: PythonEnvironment })?.result;
118+
}
119+
return undefined;
120+
}
121+
122+
export async function pickEnvironment(
123+
managers: InternalEnvironmentManager[],
124+
projectEnvManagers: InternalEnvironmentManager[],
125+
options: EnvironmentPickOptions,
126+
): Promise<PythonEnvironment | undefined> {
127+
const items: (QuickPickItem | (QuickPickItem & { result: PythonEnvironment }))[] = [
128+
{
129+
label: Interpreter.browsePath,
130+
iconPath: new ThemeIcon('folder'),
131+
},
132+
{
133+
label: '',
134+
kind: QuickPickItemKind.Separator,
135+
},
136+
{
137+
label: Interpreter.createVirtualEnvironment,
138+
iconPath: new ThemeIcon('add'),
139+
},
140+
];
141+
142+
if (options?.recommended) {
143+
items.push(
144+
{
145+
label: Common.recommended,
146+
kind: QuickPickItemKind.Separator,
147+
},
148+
{
149+
label: options.recommended.displayName,
150+
description: options.recommended.description,
151+
result: options.recommended,
152+
iconPath: getIconPath(options.recommended.iconPath),
153+
},
154+
);
155+
}
156+
157+
for (const manager of managers) {
158+
items.push({
159+
label: manager.displayName,
160+
kind: QuickPickItemKind.Separator,
161+
});
162+
const envs = await manager.getEnvironments('all');
163+
items.push(
164+
...envs.map((e) => {
165+
return {
166+
label: e.displayName ?? e.name,
167+
description: e.description,
168+
e: { selected: e, manager: manager },
169+
iconPath: getIconPath(e.iconPath),
170+
};
171+
}),
172+
);
173+
}
174+
175+
return pickEnvironmentImpl(items, managers, projectEnvManagers, options);
176+
}
177+
178+
export async function pickEnvironmentFrom(environments: PythonEnvironment[]): Promise<PythonEnvironment | undefined> {
179+
const items = environments.map((e) => ({
180+
label: e.displayName ?? e.name,
181+
description: e.description,
182+
e: e,
183+
iconPath: getIconPath(e.iconPath),
184+
}));
185+
const selected = await showQuickPick(items, {
186+
placeHolder: 'Select Python Environment',
187+
ignoreFocusOut: true,
188+
});
189+
return (selected as { e: PythonEnvironment })?.e;
190+
}

src/common/pickers/managers.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { QuickPickItem, QuickPickItemKind } from 'vscode';
2+
import { PythonProjectCreator } from '../../api';
3+
import { InternalEnvironmentManager, InternalPackageManager } from '../../internal.api';
4+
import { Common } from '../localize';
5+
import { showQuickPickWithButtons, showQuickPick } from '../window.apis';
6+
7+
export async function pickEnvironmentManager(
8+
managers: InternalEnvironmentManager[],
9+
defaultManagers?: InternalEnvironmentManager[],
10+
): Promise<string | undefined> {
11+
if (managers.length === 0) {
12+
return;
13+
}
14+
15+
if (managers.length === 1) {
16+
return managers[0].id;
17+
}
18+
19+
const items: (QuickPickItem | (QuickPickItem & { id: string }))[] = [];
20+
if (defaultManagers && defaultManagers.length > 0) {
21+
items.push(
22+
{
23+
label: Common.recommended,
24+
kind: QuickPickItemKind.Separator,
25+
},
26+
...defaultManagers.map((defaultMgr) => ({
27+
label: defaultMgr.displayName,
28+
description: defaultMgr.description,
29+
id: defaultMgr.id,
30+
})),
31+
{
32+
label: '',
33+
kind: QuickPickItemKind.Separator,
34+
},
35+
);
36+
}
37+
items.push(
38+
...managers
39+
.filter((m) => !defaultManagers?.includes(m))
40+
.map((m) => ({
41+
label: m.displayName,
42+
description: m.description,
43+
id: m.id,
44+
})),
45+
);
46+
const item = await showQuickPickWithButtons(items, {
47+
placeHolder: 'Select an environment manager',
48+
ignoreFocusOut: true,
49+
});
50+
return (item as QuickPickItem & { id: string })?.id;
51+
}
52+
53+
export async function pickPackageManager(
54+
managers: InternalPackageManager[],
55+
defaultManagers?: InternalPackageManager[],
56+
): Promise<string | undefined> {
57+
if (managers.length === 0) {
58+
return;
59+
}
60+
61+
if (managers.length === 1) {
62+
return managers[0].id;
63+
}
64+
65+
const items: (QuickPickItem | (QuickPickItem & { id: string }))[] = [];
66+
if (defaultManagers && defaultManagers.length > 0) {
67+
items.push(
68+
{
69+
label: Common.recommended,
70+
kind: QuickPickItemKind.Separator,
71+
},
72+
...defaultManagers.map((defaultMgr) => ({
73+
label: defaultMgr.displayName,
74+
description: defaultMgr.description,
75+
id: defaultMgr.id,
76+
})),
77+
{
78+
label: '',
79+
kind: QuickPickItemKind.Separator,
80+
},
81+
);
82+
}
83+
items.push(
84+
...managers
85+
.filter((m) => !defaultManagers?.includes(m))
86+
.map((m) => ({
87+
label: m.displayName,
88+
description: m.description,
89+
id: m.id,
90+
})),
91+
);
92+
const item = await showQuickPickWithButtons(items, {
93+
placeHolder: 'Select an package manager',
94+
ignoreFocusOut: true,
95+
});
96+
return (item as QuickPickItem & { id: string })?.id;
97+
}
98+
99+
export async function pickCreator(creators: PythonProjectCreator[]): Promise<PythonProjectCreator | undefined> {
100+
if (creators.length === 0) {
101+
return;
102+
}
103+
104+
if (creators.length === 1) {
105+
return creators[0];
106+
}
107+
108+
const items: (QuickPickItem & { c: PythonProjectCreator })[] = creators.map((c) => ({
109+
label: c.displayName ?? c.name,
110+
description: c.description,
111+
c: c,
112+
}));
113+
const selected = await showQuickPick(items, {
114+
placeHolder: 'Select a project creator',
115+
ignoreFocusOut: true,
116+
});
117+
return (selected as { c: PythonProjectCreator })?.c;
118+
}

0 commit comments

Comments
 (0)