Skip to content

Commit a414275

Browse files
committed
feat: Add support for Quick Create
1 parent faab63f commit a414275

10 files changed

Lines changed: 245 additions & 51 deletions

File tree

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.
@@ -728,6 +750,25 @@ export interface PackageInstallOptions {
728750
showSkipOption?: boolean;
729751
}
730752

753+
/**
754+
* Options for creating a Python environment.
755+
*/
756+
export interface CreateEnvironmentOptions {
757+
/**
758+
* Provides some context about quick create based on user input.
759+
* - if true, the environment should be created without any user input or prompts.
760+
* - if false, the environment creation can show user input or prompts.
761+
* This also means user explicitly skipped the quick create option.
762+
* - if undefined, the environment creation can show user input or prompts.
763+
* You can show quick create option to the user if you support it.
764+
*/
765+
quickCreate?: boolean;
766+
/**
767+
* Packages to install in addition to the automatically picked packages as a part of creating environment.
768+
*/
769+
additionalPackages?: string[];
770+
}
771+
731772
export interface PythonProcess {
732773
/**
733774
* The process ID of the Python process.
@@ -788,9 +829,13 @@ export interface PythonEnvironmentManagementApi {
788829
* Create a Python environment using environment manager associated with the scope.
789830
*
790831
* @param scope Where the environment is to be created.
832+
* @param options Optional parameters for creating the Python environment.
791833
* @returns The Python environment created. `undefined` if not created.
792834
*/
793-
createEnvironment(scope: CreateEnvironmentScope): Promise<PythonEnvironment | undefined>;
835+
createEnvironment(
836+
scope: CreateEnvironmentScope,
837+
options: CreateEnvironmentOptions | undefined,
838+
): Promise<PythonEnvironment | undefined>;
794839

795840
/**
796841
* 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,
@@ -77,7 +83,7 @@ export async function createEnvironmentCommand(
7783
const manager = (context as EnvManagerTreeItem).manager;
7884
const projects = pm.getProjects();
7985
if (projects.length === 0) {
80-
const env = await manager.create('global');
86+
const env = await manager.create('global', undefined);
8187
if (env) {
8288
await em.setEnvironments('global', env);
8389
}
@@ -86,7 +92,7 @@ export async function createEnvironmentCommand(
8692
const selected = await pickProjectMany(projects);
8793
if (selected) {
8894
const scope = selected.length === 0 ? 'global' : selected.map((p) => p.uri);
89-
const env = await manager.create(scope);
95+
const env = await manager.create(scope, undefined);
9096
if (env) {
9197
await em.setEnvironments(scope, env);
9298
}
@@ -99,7 +105,7 @@ export async function createEnvironmentCommand(
99105
const manager = em.getEnvironmentManager(context as Uri);
100106
const project = pm.get(context as Uri);
101107
if (project) {
102-
return await manager?.create(project.uri);
108+
return await manager?.create(project.uri, undefined);
103109
} else {
104110
traceError(`No project found for ${context}`);
105111
}
@@ -111,16 +117,15 @@ export async function createEnvironmentCommand(
111117
export async function createAnyEnvironmentCommand(
112118
em: EnvironmentManagers,
113119
pm: PythonProjectManager,
114-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
115-
options?: any,
120+
options?: CreateEnvironmentOptions & { selectEnvironment: boolean },
116121
): Promise<PythonEnvironment | undefined> {
117122
const select = options?.selectEnvironment;
118123
const projects = pm.getProjects();
119124
if (projects.length === 0) {
120125
const managerId = await pickEnvironmentManager(em.managers.filter((m) => m.supportsCreate));
121126
const manager = em.managers.find((m) => m.id === managerId);
122127
if (manager) {
123-
const env = await manager.create('global');
128+
const env = await manager.create('global', { ...options });
124129
if (select && env) {
125130
await manager.set(undefined, env);
126131
}
@@ -139,14 +144,22 @@ export async function createAnyEnvironmentCommand(
139144
}
140145
});
141146

142-
const managerId = await pickEnvironmentManager(
147+
let managerId = await pickEnvironmentManager(
143148
em.managers.filter((m) => m.supportsCreate),
144149
defaultManagers,
145150
);
151+
let quickCreate = false;
152+
if (managerId?.startsWith('QuickCreate#')) {
153+
quickCreate = true;
154+
managerId = managerId.replace('QuickCreate#', '');
155+
}
146156

147157
const manager = em.managers.find((m) => m.id === managerId);
148158
if (manager) {
149-
const env = await manager.create(selected.map((p) => p.uri));
159+
const env = await manager.create(
160+
selected.map((p) => p.uri),
161+
{ ...options, quickCreate },
162+
);
150163
if (select && env) {
151164
await em.setEnvironments(
152165
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
PackageInstallOptions,
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)