Skip to content

Commit be70775

Browse files
committed
Create environment proposed api
1 parent 586d18f commit be70775

12 files changed

+241
-102
lines changed

src/client/environmentApi.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
ResolvedEnvironment,
3232
Resource,
3333
} from './apiTypes';
34+
import { buildEnvironmentCreationApi } from './pythonEnvironments/creation/createEnvApi';
3435

3536
type ActiveEnvironmentChangeEvent = {
3637
resource: WorkspaceFolder | undefined;
@@ -253,6 +254,7 @@ export function buildEnvironmentApi(
253254
sendApiTelemetry('onDidChangeEnvironments');
254255
return onEnvironmentsChanged.event;
255256
},
257+
...buildEnvironmentCreationApi(),
256258
};
257259
return environmentApi;
258260
}

src/client/proposedApiTypes.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
export interface ProposedExtensionAPI {}
4+
export interface ProposedExtensionAPI {
5+
/**
6+
* Top level poposed APIs should go here.
7+
*/
8+
}

src/client/pythonEnvironments/creation/createEnvApi.ts

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ import { IInterpreterQuickPick } from '../../interpreter/configuration/types';
99
import { getCreationEvents, handleCreateEnvironmentCommand } from './createEnvironment';
1010
import { condaCreationProvider } from './provider/condaCreationProvider';
1111
import { VenvCreationProvider } from './provider/venvCreationProvider';
12+
import { showInformationMessage } from '../../common/vscodeApis/windowApis';
13+
import { CreateEnv } from '../../common/utils/localize';
1214
import {
13-
CreateEnvironmentExitedEventArgs,
14-
CreateEnvironmentOptions,
1515
CreateEnvironmentProvider,
16+
CreateEnvironmentOptions,
1617
CreateEnvironmentResult,
17-
} from './types';
18-
import { showInformationMessage } from '../../common/vscodeApis/windowApis';
19-
import { CreateEnv } from '../../common/utils/localize';
18+
ProposedCreateEnvironmentAPI,
19+
DidCreateEnvironmentParams,
20+
} from './proposed.createEnvApis';
2021

2122
class CreateEnvironmentProviders {
2223
private _createEnvProviders: CreateEnvironmentProvider[] = [];
@@ -26,6 +27,9 @@ class CreateEnvironmentProviders {
2627
}
2728

2829
public add(provider: CreateEnvironmentProvider) {
30+
if (this._createEnvProviders.filter((p) => p.id === provider.id).length > 0) {
31+
throw new Error(`Create Environment provider with id ${provider.id} already registered`);
32+
}
2933
this._createEnvProviders.push(provider);
3034
}
3135

@@ -63,15 +67,32 @@ export function registerCreateEnvironmentFeatures(
6367
return handleCreateEnvironmentCommand(providers, options);
6468
},
6569
),
66-
);
67-
disposables.push(registerCreateEnvironmentProvider(new VenvCreationProvider(interpreterQuickPick)));
68-
disposables.push(registerCreateEnvironmentProvider(condaCreationProvider()));
69-
disposables.push(
70-
onCreateEnvironmentExited(async (e: CreateEnvironmentExitedEventArgs) => {
71-
if (e.result?.path && e.options?.selectEnvironment) {
72-
await interpreterPathService.update(e.result.uri, ConfigurationTarget.WorkspaceFolder, e.result.path);
73-
showInformationMessage(`${CreateEnv.informEnvCreation} ${pathUtils.getDisplayName(e.result.path)}`);
70+
registerCreateEnvironmentProvider(new VenvCreationProvider(interpreterQuickPick)),
71+
registerCreateEnvironmentProvider(condaCreationProvider()),
72+
onCreateEnvironmentExited(async (e: DidCreateEnvironmentParams) => {
73+
if (e.path && e.options?.selectEnvironment) {
74+
await interpreterPathService.update(e.uri, ConfigurationTarget.WorkspaceFolder, e.path);
75+
showInformationMessage(`${CreateEnv.informEnvCreation} ${pathUtils.getDisplayName(e.path)}`);
7476
}
7577
}),
7678
);
7779
}
80+
81+
export function buildEnvironmentCreationApi(): ProposedCreateEnvironmentAPI {
82+
return {
83+
onWillCreateEnvironment: onCreateEnvironmentStarted,
84+
onDidCreateEnvironment: onCreateEnvironmentExited,
85+
createEnvironment: async (
86+
options?: CreateEnvironmentOptions | undefined,
87+
): Promise<CreateEnvironmentResult | undefined> => {
88+
const providers = _createEnvironmentProviders.getAll();
89+
try {
90+
return await handleCreateEnvironmentCommand(providers, options);
91+
} catch (err) {
92+
return { error: err as Error };
93+
}
94+
},
95+
registerCreateEnvironmentProvider: (provider: CreateEnvironmentProvider) =>
96+
registerCreateEnvironmentProvider(provider),
97+
};
98+
}

src/client/pythonEnvironments/creation/createEnvironment.ts

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ import {
1111
} from '../../common/vscodeApis/windowApis';
1212
import { traceError, traceVerbose } from '../../logging';
1313
import {
14-
CreateEnvironmentExitedEventArgs,
1514
CreateEnvironmentOptions,
16-
CreateEnvironmentProvider,
1715
CreateEnvironmentResult,
18-
CreateEnvironmentStartedEventArgs,
19-
} from './types';
16+
CreateEnvironmentProvider,
17+
WillCreateEnvironmentParams,
18+
DidCreateEnvironmentParams,
19+
} from './proposed.createEnvApis';
2020

21-
const onCreateEnvironmentStartedEvent = new EventEmitter<CreateEnvironmentStartedEventArgs>();
22-
const onCreateEnvironmentExitedEvent = new EventEmitter<CreateEnvironmentExitedEventArgs>();
21+
const onCreateEnvironmentStartedEvent = new EventEmitter<WillCreateEnvironmentParams>();
22+
const onCreateEnvironmentExitedEvent = new EventEmitter<DidCreateEnvironmentParams>();
2323

2424
let startedEventCount = 0;
2525

@@ -32,14 +32,14 @@ function fireStartedEvent(options?: CreateEnvironmentOptions): void {
3232
startedEventCount += 1;
3333
}
3434

35-
function fireExitedEvent(result?: CreateEnvironmentResult, options?: CreateEnvironmentOptions, error?: unknown): void {
36-
onCreateEnvironmentExitedEvent.fire({ result, options, error });
35+
function fireExitedEvent(result?: CreateEnvironmentResult, options?: CreateEnvironmentOptions, error?: Error): void {
36+
onCreateEnvironmentExitedEvent.fire({ ...result, options, error });
3737
startedEventCount -= 1;
3838
}
3939

4040
export function getCreationEvents(): {
41-
onCreateEnvironmentStarted: Event<CreateEnvironmentStartedEventArgs>;
42-
onCreateEnvironmentExited: Event<CreateEnvironmentExitedEventArgs>;
41+
onCreateEnvironmentStarted: Event<WillCreateEnvironmentParams>;
42+
onCreateEnvironmentExited: Event<DidCreateEnvironmentParams>;
4343
isCreatingEnvironment: () => boolean;
4444
} {
4545
return {
@@ -54,7 +54,7 @@ async function createEnvironment(
5454
options: CreateEnvironmentOptions,
5555
): Promise<CreateEnvironmentResult | undefined> {
5656
let result: CreateEnvironmentResult | undefined;
57-
let err: unknown | undefined;
57+
let err: Error | undefined;
5858
try {
5959
fireStartedEvent(options);
6060
result = await provider.createEnvironment(options);
@@ -65,7 +65,7 @@ async function createEnvironment(
6565
return undefined;
6666
}
6767
}
68-
err = ex;
68+
err = ex as Error;
6969
throw err;
7070
} finally {
7171
fireExitedEvent(result, options, err);
@@ -186,9 +186,7 @@ export async function handleCreateEnvironmentCommand(
186186
if (options?.showBackButton) {
187187
if (action === MultiStepAction.Back || action === MultiStepAction.Cancel) {
188188
result = {
189-
path: result?.path,
190-
uri: result?.uri,
191-
action: action === MultiStepAction.Back ? 'Back' : 'Cancel',
189+
action: action === MultiStepAction.Back ? 'back' : 'cancel',
192190
};
193191
}
194192
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License
3+
4+
import { Progress, Uri, Event, Disposable } from 'vscode';
5+
6+
export interface CreateEnvironmentProgress extends Progress<{ message?: string; increment?: number }> {}
7+
8+
/**
9+
* Options used when creating a Python environment.
10+
*/
11+
export interface CreateEnvironmentOptions {
12+
/**
13+
* Default `true`. If `true`, the environment creation handler is expected to install packages.
14+
*/
15+
installPackages?: boolean;
16+
17+
/**
18+
* Default `true`. If `true`, the environment creation provider is expected to add the environment to ignore list
19+
* for the source control.
20+
*/
21+
ignoreSourceControl?: boolean;
22+
23+
/**
24+
* Default `false`. If `true` the creation provider should show back button when showing QuickPick or QuickInput.
25+
*/
26+
showBackButton?: boolean;
27+
28+
/**
29+
* Default `true`. If `true`, the environment will be selected as the environment to be used for the workspace.
30+
*/
31+
selectEnvironment?: boolean;
32+
}
33+
34+
/**
35+
* Params passed on `onWillCreateEnvironment` event handler.
36+
*/
37+
export interface WillCreateEnvironmentParams {
38+
/**
39+
* Options used to create a Python environment.
40+
*/
41+
options: CreateEnvironmentOptions | undefined;
42+
}
43+
44+
/**
45+
* Params passed on `onDidCreateEnvironment` event handler.
46+
*/
47+
export interface DidCreateEnvironmentParams {
48+
/**
49+
* Options used to create the Python environment.
50+
*/
51+
options?: CreateEnvironmentOptions;
52+
53+
/**
54+
* Workspace Uri associated with the environment.
55+
*/
56+
uri?: Uri;
57+
58+
/**
59+
* Path to the executable python in the environment
60+
*/
61+
path?: string;
62+
63+
/**
64+
* User action that resulted in exit from the create environment flow.
65+
*/
66+
action?: 'cancel' | 'back';
67+
68+
/**
69+
* Error if any occurred during environment creation.
70+
*/
71+
error?: Error;
72+
}
73+
74+
export interface CreateEnvironmentResult {
75+
/**
76+
* Workspace Uri associated with the environment.
77+
*/
78+
uri?: Uri;
79+
80+
/**
81+
* Path to the python executable in the environment.
82+
*/
83+
path?: string;
84+
85+
/**
86+
* User action that resulted in exit from the create environment flow.
87+
*/
88+
action?: 'cancel' | 'back';
89+
90+
/**
91+
* Error if any occurred during environment creation.
92+
*/
93+
error?: Error;
94+
}
95+
96+
/**
97+
* Extensions that want to contribute their own environment creation can do that by registering an object
98+
* that implements this interface.
99+
*/
100+
export interface CreateEnvironmentProvider {
101+
/**
102+
* This API is called when user selects this provider from a QuickPick to select the type of environment
103+
* user wants. This API is expected to show a QuickPick or QuickInput to get the user input and return
104+
* the path to the Python executable in the environment.
105+
*
106+
* @param {CreateEnvironmentOptions} [options] Options used to create a Python environment.
107+
*
108+
* @returns a promise that resolves to the path to the
109+
* Python executable in the environment. Or any action taken by the user, such as back or cancel.
110+
*/
111+
createEnvironment(options?: CreateEnvironmentOptions): Promise<CreateEnvironmentResult | undefined>;
112+
113+
/**
114+
* Unique ID for the creation provider, typically <ExtensionId>:<environment-type | guid>
115+
*/
116+
id: string;
117+
118+
/**
119+
* Display name for the creation provider.
120+
*/
121+
name: string;
122+
123+
/**
124+
* Description displayed to the user in the QuickPick to select environment provider.
125+
*/
126+
description: string;
127+
128+
/**
129+
* Tools used to manage this environment. e.g., ['conda']
130+
*/
131+
tools?: string[];
132+
}
133+
134+
export interface ProposedCreateEnvironmentAPI {
135+
/**
136+
* This API can be used to detect when the environment creation starts for any registered
137+
* provider (including internal providers). This will also receive any options passed in
138+
* or defaults used to create environment.
139+
*/
140+
onWillCreateEnvironment: Event<WillCreateEnvironmentParams>;
141+
142+
/**
143+
* This API can be used to detect when the environment provider exits for any registered
144+
* provider (including internal providers). This will also receive created environment path,
145+
* any errors, or user actions taken from the provider.
146+
*/
147+
onDidCreateEnvironment: Event<DidCreateEnvironmentParams>;
148+
149+
/**
150+
* This API will show a QuickPick to select an environment provider from available list of
151+
* providers. Based on the selection the `createEnvironment` will be called on the provider.
152+
*/
153+
createEnvironment(options: CreateEnvironmentOptions): Promise<CreateEnvironmentResult | undefined>;
154+
155+
/**
156+
* This API should be called to register an environment creation provider. It returns
157+
* a (@link Disposable} which can be used to remove the registration.
158+
*/
159+
registerCreateEnvironmentProvider(provider: CreateEnvironmentProvider): Disposable;
160+
}

src/client/pythonEnvironments/creation/provider/condaCreationProvider.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,7 @@ import { CancellationToken, ProgressLocation, WorkspaceFolder } from 'vscode';
55
import * as path from 'path';
66
import { Commands, PVSC_EXTENSION_ID } from '../../../common/constants';
77
import { traceError, traceLog } from '../../../logging';
8-
import {
9-
CreateEnvironmentOptions,
10-
CreateEnvironmentProgress,
11-
CreateEnvironmentProvider,
12-
CreateEnvironmentResult,
13-
} from '../types';
8+
import { CreateEnvironmentProgress } from '../types';
149
import { pickWorkspaceFolder } from '../common/workspaceSelection';
1510
import { execObservable } from '../../../common/process/rawProcessApis';
1611
import { createDeferred } from '../../../common/utils/async';
@@ -28,6 +23,11 @@ import {
2823
CONDA_ENV_EXISTING_MARKER,
2924
} from './condaProgressAndTelemetry';
3025
import { splitLines } from '../../../common/stringUtils';
26+
import {
27+
CreateEnvironmentOptions,
28+
CreateEnvironmentResult,
29+
CreateEnvironmentProvider,
30+
} from '../proposed.createEnvApis';
3131

3232
function generateCommandArgs(version?: string, options?: CreateEnvironmentOptions): string[] {
3333
let addGitIgnore = true;

src/client/pythonEnvironments/creation/provider/venvCreationProvider.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,7 @@ import { execObservable } from '../../../common/process/rawProcessApis';
99
import { createDeferred } from '../../../common/utils/async';
1010
import { Common, CreateEnv } from '../../../common/utils/localize';
1111
import { traceError, traceInfo, traceLog } from '../../../logging';
12-
import {
13-
CreateEnvironmentOptions,
14-
CreateEnvironmentProgress,
15-
CreateEnvironmentProvider,
16-
CreateEnvironmentResult,
17-
} from '../types';
12+
import { CreateEnvironmentProgress } from '../types';
1813
import { pickWorkspaceFolder } from '../common/workspaceSelection';
1914
import { IInterpreterQuickPick } from '../../../interpreter/configuration/types';
2015
import { EnvironmentType, PythonEnvironment } from '../../info';
@@ -25,6 +20,11 @@ import { VenvProgressAndTelemetry, VENV_CREATED_MARKER, VENV_EXISTING_MARKER } f
2520
import { showErrorMessageWithLogs } from '../common/commonUtils';
2621
import { IPackageInstallSelection, pickPackagesToInstall } from './venvUtils';
2722
import { InputFlowAction } from '../../../common/utils/multiStepInput';
23+
import {
24+
CreateEnvironmentProvider,
25+
CreateEnvironmentOptions,
26+
CreateEnvironmentResult,
27+
} from '../proposed.createEnvApis';
2828

2929
function generateCommandArgs(installInfo?: IPackageInstallSelection[], addGitIgnore?: boolean): string[] {
3030
const command: string[] = [createVenvScript()];

0 commit comments

Comments
 (0)