Skip to content

Commit 8e1887a

Browse files
committed
feat: Add support for Quick Create
1 parent cda8b00 commit 8e1887a

File tree

10 files changed

+245
-51
lines changed

10 files changed

+245
-51
lines changed

src/api.ts

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,18 @@ export type DidChangeEnvironmentsEventArgs = {
316316
*/
317317
export type ResolveEnvironmentContext = Uri;
318318

319+
export interface QuickCreateConfig {
320+
/**
321+
* The description of the quick create step.
322+
*/
323+
readonly description: string;
324+
325+
/**
326+
* The detail of the quick create step.
327+
*/
328+
readonly detail?: string;
329+
}
330+
319331
/**
320332
* Interface representing an environment manager.
321333
*/
@@ -360,12 +372,22 @@ export interface EnvironmentManager {
360372
*/
361373
readonly log?: LogOutputChannel;
362374

375+
/**
376+
* The quick create details for the environment manager. Having this method also enables the quick create feature
377+
* for the environment manager.
378+
*/
379+
quickCreateConfig?(): QuickCreateConfig | undefined;
380+
363381
/**
364382
* Creates a new Python environment within the specified scope.
365383
* @param scope - The scope within which to create the environment.
384+
* @param options - Optional parameters for creating the Python environment.
366385
* @returns A promise that resolves to the created Python environment, or undefined if creation failed.
367386
*/
368-
create?(scope: CreateEnvironmentScope): Promise<PythonEnvironment | undefined>;
387+
create?(
388+
scope: CreateEnvironmentScope,
389+
options: CreateEnvironmentOptions | undefined,
390+
): Promise<PythonEnvironment | undefined>;
369391

370392
/**
371393
* Removes the specified Python environment.
@@ -747,6 +769,25 @@ export type PackageManagementOptions =
747769
uninstall: string[];
748770
};
749771

772+
/**
773+
* Options for creating a Python environment.
774+
*/
775+
export interface CreateEnvironmentOptions {
776+
/**
777+
* Provides some context about quick create based on user input.
778+
* - if true, the environment should be created without any user input or prompts.
779+
* - if false, the environment creation can show user input or prompts.
780+
* This also means user explicitly skipped the quick create option.
781+
* - if undefined, the environment creation can show user input or prompts.
782+
* You can show quick create option to the user if you support it.
783+
*/
784+
quickCreate?: boolean;
785+
/**
786+
* Packages to install in addition to the automatically picked packages as a part of creating environment.
787+
*/
788+
additionalPackages?: string[];
789+
}
790+
750791
export interface PythonProcess {
751792
/**
752793
* The process ID of the Python process.
@@ -807,9 +848,13 @@ export interface PythonEnvironmentManagementApi {
807848
* Create a Python environment using environment manager associated with the scope.
808849
*
809850
* @param scope Where the environment is to be created.
851+
* @param options Optional parameters for creating the Python environment.
810852
* @returns The Python environment created. `undefined` if not created.
811853
*/
812-
createEnvironment(scope: CreateEnvironmentScope): Promise<PythonEnvironment | undefined>;
854+
createEnvironment(
855+
scope: CreateEnvironmentScope,
856+
options: CreateEnvironmentOptions | undefined,
857+
): Promise<PythonEnvironment | undefined>;
813858

814859
/**
815860
* Remove a Python environment.

src/common/localize.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export namespace Common {
1111
export const viewLogs = l10n.t('View Logs');
1212
export const yes = l10n.t('Yes');
1313
export const no = l10n.t('No');
14+
export const quickCreate = l10n.t('Quick Create');
1415
}
1516

1617
export namespace Interpreter {

src/common/pickers/environments.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,10 @@ async function createEnvironment(
8080
const manager = managers.find((m) => m.id === managerId);
8181
if (manager) {
8282
try {
83-
const env = await manager.create(options.projects.map((p) => p.uri));
83+
const env = await manager.create(
84+
options.projects.map((p) => p.uri),
85+
undefined,
86+
);
8487
return env;
8588
} catch (ex) {
8689
if (ex === QuickInputButtons.Back) {

src/common/pickers/managers.ts

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@ import { InternalEnvironmentManager, InternalPackageManager } from '../../intern
44
import { Common, Pickers } from '../localize';
55
import { showQuickPickWithButtons, showQuickPick } from '../window.apis';
66

7+
function getDescription(mgr: InternalEnvironmentManager | InternalPackageManager): string | undefined {
8+
if (mgr.description) {
9+
return mgr.description;
10+
}
11+
if (mgr.tooltip) {
12+
const tooltip = mgr.tooltip;
13+
if (typeof tooltip === 'string') {
14+
return tooltip;
15+
}
16+
return tooltip.value;
17+
}
18+
return undefined;
19+
}
20+
721
export async function pickEnvironmentManager(
822
managers: InternalEnvironmentManager[],
923
defaultManagers?: InternalEnvironmentManager[],
@@ -18,14 +32,25 @@ export async function pickEnvironmentManager(
1832

1933
const items: (QuickPickItem | (QuickPickItem & { id: string }))[] = [];
2034
if (defaultManagers && defaultManagers.length > 0) {
35+
items.push({
36+
label: Common.recommended,
37+
kind: QuickPickItemKind.Separator,
38+
});
39+
if (defaultManagers.length === 1 && defaultManagers[0].supportsQuickCreate) {
40+
const details = defaultManagers[0].quickCreateConfig();
41+
if (details) {
42+
items.push({
43+
label: Common.quickCreate,
44+
description: details.description,
45+
detail: details.detail,
46+
id: `QuickCreate#${defaultManagers[0].id}`,
47+
});
48+
}
49+
}
2150
items.push(
22-
{
23-
label: Common.recommended,
24-
kind: QuickPickItemKind.Separator,
25-
},
2651
...defaultManagers.map((defaultMgr) => ({
2752
label: defaultMgr.displayName,
28-
description: defaultMgr.description,
53+
description: getDescription(defaultMgr),
2954
id: defaultMgr.id,
3055
})),
3156
{
@@ -39,7 +64,7 @@ export async function pickEnvironmentManager(
3964
.filter((m) => !defaultManagers?.includes(m))
4065
.map((m) => ({
4166
label: m.displayName,
42-
description: m.description,
67+
description: getDescription(m),
4368
id: m.id,
4469
})),
4570
);
@@ -71,7 +96,7 @@ export async function pickPackageManager(
7196
},
7297
...defaultManagers.map((defaultMgr) => ({
7398
label: defaultMgr.displayName,
74-
description: defaultMgr.description,
99+
description: getDescription(defaultMgr),
75100
id: defaultMgr.id,
76101
})),
77102
{
@@ -85,7 +110,7 @@ export async function pickPackageManager(
85110
.filter((m) => !defaultManagers?.includes(m))
86111
.map((m) => ({
87112
label: m.displayName,
88-
description: m.description,
113+
description: getDescription(m),
89114
id: m.id,
90115
})),
91116
);

src/features/envCommands.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ import {
77
PythonProjectManager,
88
} from '../internal.api';
99
import { traceError, traceInfo, traceVerbose } from '../common/logging';
10-
import { PythonEnvironment, PythonEnvironmentApi, PythonProject, PythonProjectCreator } from '../api';
10+
import {
11+
CreateEnvironmentOptions,
12+
PythonEnvironment,
13+
PythonEnvironmentApi,
14+
PythonProject,
15+
PythonProjectCreator,
16+
} from '../api';
1117
import * as path from 'path';
1218
import {
1319
setEnvironmentManager,
@@ -75,7 +81,7 @@ export async function createEnvironmentCommand(
7581
const manager = (context as EnvManagerTreeItem).manager;
7682
const projects = pm.getProjects();
7783
if (projects.length === 0) {
78-
const env = await manager.create('global');
84+
const env = await manager.create('global', undefined);
7985
if (env) {
8086
await em.setEnvironments('global', env);
8187
}
@@ -84,7 +90,7 @@ export async function createEnvironmentCommand(
8490
const selected = await pickProjectMany(projects);
8591
if (selected) {
8692
const scope = selected.length === 0 ? 'global' : selected.map((p) => p.uri);
87-
const env = await manager.create(scope);
93+
const env = await manager.create(scope, undefined);
8894
if (env) {
8995
await em.setEnvironments(scope, env);
9096
}
@@ -97,7 +103,7 @@ export async function createEnvironmentCommand(
97103
const manager = em.getEnvironmentManager(context as Uri);
98104
const project = pm.get(context as Uri);
99105
if (project) {
100-
return await manager?.create(project.uri);
106+
return await manager?.create(project.uri, undefined);
101107
} else {
102108
traceError(`No project found for ${context}`);
103109
}
@@ -109,16 +115,15 @@ export async function createEnvironmentCommand(
109115
export async function createAnyEnvironmentCommand(
110116
em: EnvironmentManagers,
111117
pm: PythonProjectManager,
112-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
113-
options?: any,
118+
options?: CreateEnvironmentOptions & { selectEnvironment: boolean },
114119
): Promise<PythonEnvironment | undefined> {
115120
const select = options?.selectEnvironment;
116121
const projects = pm.getProjects();
117122
if (projects.length === 0) {
118123
const managerId = await pickEnvironmentManager(em.managers.filter((m) => m.supportsCreate));
119124
const manager = em.managers.find((m) => m.id === managerId);
120125
if (manager) {
121-
const env = await manager.create('global');
126+
const env = await manager.create('global', { ...options });
122127
if (select && env) {
123128
await manager.set(undefined, env);
124129
}
@@ -137,14 +142,22 @@ export async function createAnyEnvironmentCommand(
137142
}
138143
});
139144

140-
const managerId = await pickEnvironmentManager(
145+
let managerId = await pickEnvironmentManager(
141146
em.managers.filter((m) => m.supportsCreate),
142147
defaultManagers,
143148
);
149+
let quickCreate = false;
150+
if (managerId?.startsWith('QuickCreate#')) {
151+
quickCreate = true;
152+
managerId = managerId.replace('QuickCreate#', '');
153+
}
144154

145155
const manager = em.managers.find((m) => m.id === managerId);
146156
if (manager) {
147-
const env = await manager.create(selected.map((p) => p.uri));
157+
const env = await manager.create(
158+
selected.map((p) => p.uri),
159+
{ ...options, quickCreate },
160+
);
148161
if (select && env) {
149162
await em.setEnvironments(
150163
selected.map((p) => p.uri),

src/features/pythonApi.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
PythonBackgroundRunOptions,
2929
PythonTerminalCreateOptions,
3030
DidChangeEnvironmentVariablesEventArgs,
31+
CreateEnvironmentOptions,
3132
} from '../api';
3233
import {
3334
EnvironmentManagers,
@@ -116,7 +117,10 @@ class PythonEnvironmentApiImpl implements PythonEnvironmentApi {
116117
return new PythonEnvironmentImpl(envId, info);
117118
}
118119

119-
async createEnvironment(scope: CreateEnvironmentScope): Promise<PythonEnvironment | undefined> {
120+
async createEnvironment(
121+
scope: CreateEnvironmentScope,
122+
options: CreateEnvironmentOptions | undefined,
123+
): Promise<PythonEnvironment | undefined> {
120124
if (scope === 'global' || (!Array.isArray(scope) && scope instanceof Uri)) {
121125
const manager = this.envManagers.getEnvironmentManager(scope === 'global' ? undefined : scope);
122126
if (!manager) {
@@ -125,9 +129,9 @@ class PythonEnvironmentApiImpl implements PythonEnvironmentApi {
125129
if (!manager.supportsCreate) {
126130
throw new Error(`Environment manager does not support creating environments: ${manager.id}`);
127131
}
128-
return manager.create(scope);
132+
return manager.create(scope, options);
129133
} else if (Array.isArray(scope) && scope.length === 1 && scope[0] instanceof Uri) {
130-
return this.createEnvironment(scope[0]);
134+
return this.createEnvironment(scope[0], options);
131135
} else if (Array.isArray(scope) && scope.length > 0 && scope.every((s) => s instanceof Uri)) {
132136
const managers: InternalEnvironmentManager[] = [];
133137
scope.forEach((s) => {
@@ -151,7 +155,7 @@ class PythonEnvironmentApiImpl implements PythonEnvironmentApi {
151155
throw new Error('No environment manager found');
152156
}
153157

154-
const result = await manager.create(scope);
158+
const result = await manager.create(scope, options);
155159
return result;
156160
}
157161
}

src/internal.api.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
ResolveEnvironmentContext,
2525
PackageManagementOptions,
2626
EnvironmentGroupInfo,
27+
QuickCreateConfig,
28+
CreateEnvironmentOptions,
2729
} from './api';
2830
import { CreateEnvironmentNotSupported, RemoveEnvironmentNotSupported } from './common/errors/NotSupportedError';
2931

@@ -141,14 +143,28 @@ export class InternalEnvironmentManager implements EnvironmentManager {
141143
return this.manager.create !== undefined;
142144
}
143145

144-
create(scope: CreateEnvironmentScope): Promise<PythonEnvironment | undefined> {
146+
create(
147+
scope: CreateEnvironmentScope,
148+
options: CreateEnvironmentOptions | undefined,
149+
): Promise<PythonEnvironment | undefined> {
145150
if (this.manager.create) {
146-
return this.manager.create(scope);
151+
return this.manager.create(scope, options);
147152
}
148153

149154
return Promise.reject(new CreateEnvironmentNotSupported(`Create Environment not supported by: ${this.id}`));
150155
}
151156

157+
public get supportsQuickCreate(): boolean {
158+
return this.manager.quickCreateConfig !== undefined;
159+
}
160+
161+
quickCreateConfig(): QuickCreateConfig | undefined {
162+
if (this.manager.quickCreateConfig) {
163+
return this.manager.quickCreateConfig();
164+
}
165+
throw new CreateEnvironmentNotSupported(`Quick Create Environment not supported by: ${this.id}`);
166+
}
167+
152168
public get supportsRemove(): boolean {
153169
return this.manager.remove !== undefined;
154170
}

0 commit comments

Comments
 (0)