From fc0c9f0b0dbf957d29c09f0bba283061309db724 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Thu, 13 Apr 2023 15:42:43 -0700 Subject: [PATCH 1/3] Create environment proposed api --- src/client/environmentApi.ts | 2 + src/client/proposedApiTypes.ts | 6 +- .../creation/createEnvApi.ts | 47 +++-- .../creation/createEnvironment.ts | 30 ++-- .../creation/proposed.createEnvApis.ts | 161 ++++++++++++++++++ .../provider/condaCreationProvider.ts | 12 +- .../creation/provider/venvCreationProvider.ts | 12 +- .../pythonEnvironments/creation/types.ts | 48 +----- .../creation/createEnvApi.unit.test.ts | 2 +- .../creation/createEnvironment.unit.test.ts | 6 +- .../condaCreationProvider.unit.test.ts | 10 +- .../venvCreationProvider.unit.test.ts | 10 +- 12 files changed, 242 insertions(+), 104 deletions(-) create mode 100644 src/client/pythonEnvironments/creation/proposed.createEnvApis.ts diff --git a/src/client/environmentApi.ts b/src/client/environmentApi.ts index 533d187ca520..3e846eb2772f 100644 --- a/src/client/environmentApi.ts +++ b/src/client/environmentApi.ts @@ -31,6 +31,7 @@ import { ResolvedEnvironment, Resource, } from './apiTypes'; +import { buildEnvironmentCreationApi } from './pythonEnvironments/creation/createEnvApi'; type ActiveEnvironmentChangeEvent = { resource: WorkspaceFolder | undefined; @@ -253,6 +254,7 @@ export function buildEnvironmentApi( sendApiTelemetry('onDidChangeEnvironments'); return onEnvironmentsChanged.event; }, + ...buildEnvironmentCreationApi(), }; return environmentApi; } diff --git a/src/client/proposedApiTypes.ts b/src/client/proposedApiTypes.ts index 1b772b406644..be5874ca47e5 100644 --- a/src/client/proposedApiTypes.ts +++ b/src/client/proposedApiTypes.ts @@ -1,4 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -export interface ProposedExtensionAPI {} +export interface ProposedExtensionAPI { + /** + * Top level poposed APIs should go here. + */ +} diff --git a/src/client/pythonEnvironments/creation/createEnvApi.ts b/src/client/pythonEnvironments/creation/createEnvApi.ts index cfbf81909d59..078b66df74e8 100644 --- a/src/client/pythonEnvironments/creation/createEnvApi.ts +++ b/src/client/pythonEnvironments/creation/createEnvApi.ts @@ -9,14 +9,15 @@ import { IInterpreterQuickPick } from '../../interpreter/configuration/types'; import { getCreationEvents, handleCreateEnvironmentCommand } from './createEnvironment'; import { condaCreationProvider } from './provider/condaCreationProvider'; import { VenvCreationProvider } from './provider/venvCreationProvider'; +import { showInformationMessage } from '../../common/vscodeApis/windowApis'; +import { CreateEnv } from '../../common/utils/localize'; import { - CreateEnvironmentExitedEventArgs, - CreateEnvironmentOptions, CreateEnvironmentProvider, + CreateEnvironmentOptions, CreateEnvironmentResult, -} from './types'; -import { showInformationMessage } from '../../common/vscodeApis/windowApis'; -import { CreateEnv } from '../../common/utils/localize'; + ProposedCreateEnvironmentAPI, + DidCreateEnvironmentParams, +} from './proposed.createEnvApis'; class CreateEnvironmentProviders { private _createEnvProviders: CreateEnvironmentProvider[] = []; @@ -26,6 +27,9 @@ class CreateEnvironmentProviders { } public add(provider: CreateEnvironmentProvider) { + if (this._createEnvProviders.filter((p) => p.id === provider.id).length > 0) { + throw new Error(`Create Environment provider with id ${provider.id} already registered`); + } this._createEnvProviders.push(provider); } @@ -63,15 +67,32 @@ export function registerCreateEnvironmentFeatures( return handleCreateEnvironmentCommand(providers, options); }, ), - ); - disposables.push(registerCreateEnvironmentProvider(new VenvCreationProvider(interpreterQuickPick))); - disposables.push(registerCreateEnvironmentProvider(condaCreationProvider())); - disposables.push( - onCreateEnvironmentExited(async (e: CreateEnvironmentExitedEventArgs) => { - if (e.result?.path && e.options?.selectEnvironment) { - await interpreterPathService.update(e.result.uri, ConfigurationTarget.WorkspaceFolder, e.result.path); - showInformationMessage(`${CreateEnv.informEnvCreation} ${pathUtils.getDisplayName(e.result.path)}`); + registerCreateEnvironmentProvider(new VenvCreationProvider(interpreterQuickPick)), + registerCreateEnvironmentProvider(condaCreationProvider()), + onCreateEnvironmentExited(async (e: DidCreateEnvironmentParams) => { + if (e.path && e.options?.selectEnvironment) { + await interpreterPathService.update(e.uri, ConfigurationTarget.WorkspaceFolder, e.path); + showInformationMessage(`${CreateEnv.informEnvCreation} ${pathUtils.getDisplayName(e.path)}`); } }), ); } + +export function buildEnvironmentCreationApi(): ProposedCreateEnvironmentAPI { + return { + onWillCreateEnvironment: onCreateEnvironmentStarted, + onDidCreateEnvironment: onCreateEnvironmentExited, + createEnvironment: async ( + options?: CreateEnvironmentOptions | undefined, + ): Promise => { + const providers = _createEnvironmentProviders.getAll(); + try { + return await handleCreateEnvironmentCommand(providers, options); + } catch (err) { + return { error: err as Error }; + } + }, + registerCreateEnvironmentProvider: (provider: CreateEnvironmentProvider) => + registerCreateEnvironmentProvider(provider), + }; +} diff --git a/src/client/pythonEnvironments/creation/createEnvironment.ts b/src/client/pythonEnvironments/creation/createEnvironment.ts index bdeaf89ba82d..7c446381a042 100644 --- a/src/client/pythonEnvironments/creation/createEnvironment.ts +++ b/src/client/pythonEnvironments/creation/createEnvironment.ts @@ -11,15 +11,15 @@ import { } from '../../common/vscodeApis/windowApis'; import { traceError, traceVerbose } from '../../logging'; import { - CreateEnvironmentExitedEventArgs, CreateEnvironmentOptions, - CreateEnvironmentProvider, CreateEnvironmentResult, - CreateEnvironmentStartedEventArgs, -} from './types'; + CreateEnvironmentProvider, + WillCreateEnvironmentParams, + DidCreateEnvironmentParams, +} from './proposed.createEnvApis'; -const onCreateEnvironmentStartedEvent = new EventEmitter(); -const onCreateEnvironmentExitedEvent = new EventEmitter(); +const onCreateEnvironmentStartedEvent = new EventEmitter(); +const onCreateEnvironmentExitedEvent = new EventEmitter(); let startedEventCount = 0; @@ -32,14 +32,14 @@ function fireStartedEvent(options?: CreateEnvironmentOptions): void { startedEventCount += 1; } -function fireExitedEvent(result?: CreateEnvironmentResult, options?: CreateEnvironmentOptions, error?: unknown): void { - onCreateEnvironmentExitedEvent.fire({ result, options, error }); +function fireExitedEvent(result?: CreateEnvironmentResult, options?: CreateEnvironmentOptions, error?: Error): void { + onCreateEnvironmentExitedEvent.fire({ ...result, options, error }); startedEventCount -= 1; } export function getCreationEvents(): { - onCreateEnvironmentStarted: Event; - onCreateEnvironmentExited: Event; + onCreateEnvironmentStarted: Event; + onCreateEnvironmentExited: Event; isCreatingEnvironment: () => boolean; } { return { @@ -54,7 +54,7 @@ async function createEnvironment( options: CreateEnvironmentOptions, ): Promise { let result: CreateEnvironmentResult | undefined; - let err: unknown | undefined; + let err: Error | undefined; try { fireStartedEvent(options); result = await provider.createEnvironment(options); @@ -65,7 +65,7 @@ async function createEnvironment( return undefined; } } - err = ex; + err = ex as Error; throw err; } finally { fireExitedEvent(result, options, err); @@ -185,11 +185,7 @@ export async function handleCreateEnvironmentCommand( const action = await MultiStepNode.run(envTypeStep); if (options?.showBackButton) { if (action === MultiStepAction.Back || action === MultiStepAction.Cancel) { - result = { - path: result?.path, - uri: result?.uri, - action: action === MultiStepAction.Back ? 'Back' : 'Cancel', - }; + result = { action }; } } diff --git a/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts b/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts new file mode 100644 index 000000000000..1f3eed5d305b --- /dev/null +++ b/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License + +import { Uri, Event, Disposable } from 'vscode'; + +export type CreateEnvironmentUserActions = 'Back' | 'Cancel'; +export type CreateEnvironmentProviderId = string; + +/** + * Options used when creating a Python environment. + */ +export interface CreateEnvironmentOptions { + /** + * Default `true`. If `true`, the environment creation handler is expected to install packages. + */ + installPackages?: boolean; + + /** + * Default `true`. If `true`, the environment creation provider is expected to add the environment to ignore list + * for the source control. + */ + ignoreSourceControl?: boolean; + + /** + * Default `false`. If `true` the creation provider should show back button when showing QuickPick or QuickInput. + */ + showBackButton?: boolean; + + /** + * Default `true`. If `true`, the environment will be selected as the environment to be used for the workspace. + */ + selectEnvironment?: boolean; +} + +/** + * Params passed on `onWillCreateEnvironment` event handler. + */ +export interface WillCreateEnvironmentParams { + /** + * Options used to create a Python environment. + */ + options: CreateEnvironmentOptions | undefined; +} + +/** + * Params passed on `onDidCreateEnvironment` event handler. + */ +export interface DidCreateEnvironmentParams { + /** + * Options used to create the Python environment. + */ + options?: CreateEnvironmentOptions; + + /** + * Workspace Uri associated with the environment. + */ + uri?: Uri; + + /** + * Path to the executable python in the environment + */ + path?: string; + + /** + * User action that resulted in exit from the create environment flow. + */ + action?: CreateEnvironmentUserActions; + + /** + * Error if any occurred during environment creation. + */ + error?: Error; +} + +export interface CreateEnvironmentResult { + /** + * Workspace Uri associated with the environment. + */ + uri?: Uri; + + /** + * Path to the python executable in the environment. + */ + path?: string; + + /** + * User action that resulted in exit from the create environment flow. + */ + action?: CreateEnvironmentUserActions; + + /** + * Error if any occurred during environment creation. + */ + error?: Error; +} + +/** + * Extensions that want to contribute their own environment creation can do that by registering an object + * that implements this interface. + */ +export interface CreateEnvironmentProvider { + /** + * This API is called when user selects this provider from a QuickPick to select the type of environment + * user wants. This API is expected to show a QuickPick or QuickInput to get the user input and return + * the path to the Python executable in the environment. + * + * @param {CreateEnvironmentOptions} [options] Options used to create a Python environment. + * + * @returns a promise that resolves to the path to the + * Python executable in the environment. Or any action taken by the user, such as back or cancel. + */ + createEnvironment(options?: CreateEnvironmentOptions): Promise; + + /** + * Unique ID for the creation provider, typically : + */ + id: CreateEnvironmentProviderId; + + /** + * Display name for the creation provider. + */ + name: string; + + /** + * Description displayed to the user in the QuickPick to select environment provider. + */ + description: string; + + /** + * Tools used to manage this environment. e.g., ['conda'] + */ + tools?: string[]; +} + +export interface ProposedCreateEnvironmentAPI { + /** + * This API can be used to detect when the environment creation starts for any registered + * provider (including internal providers). This will also receive any options passed in + * or defaults used to create environment. + */ + onWillCreateEnvironment: Event; + + /** + * This API can be used to detect when the environment provider exits for any registered + * provider (including internal providers). This will also receive created environment path, + * any errors, or user actions taken from the provider. + */ + onDidCreateEnvironment: Event; + + /** + * This API will show a QuickPick to select an environment provider from available list of + * providers. Based on the selection the `createEnvironment` will be called on the provider. + */ + createEnvironment(options: CreateEnvironmentOptions): Promise; + + /** + * This API should be called to register an environment creation provider. It returns + * a (@link Disposable} which can be used to remove the registration. + */ + registerCreateEnvironmentProvider(provider: CreateEnvironmentProvider): Disposable; +} diff --git a/src/client/pythonEnvironments/creation/provider/condaCreationProvider.ts b/src/client/pythonEnvironments/creation/provider/condaCreationProvider.ts index 5bf032f9f65f..8fa3c6e3f29c 100644 --- a/src/client/pythonEnvironments/creation/provider/condaCreationProvider.ts +++ b/src/client/pythonEnvironments/creation/provider/condaCreationProvider.ts @@ -5,12 +5,7 @@ import { CancellationToken, ProgressLocation, WorkspaceFolder } from 'vscode'; import * as path from 'path'; import { Commands, PVSC_EXTENSION_ID } from '../../../common/constants'; import { traceError, traceLog } from '../../../logging'; -import { - CreateEnvironmentOptions, - CreateEnvironmentProgress, - CreateEnvironmentProvider, - CreateEnvironmentResult, -} from '../types'; +import { CreateEnvironmentProgress } from '../types'; import { pickWorkspaceFolder } from '../common/workspaceSelection'; import { execObservable } from '../../../common/process/rawProcessApis'; import { createDeferred } from '../../../common/utils/async'; @@ -28,6 +23,11 @@ import { CONDA_ENV_EXISTING_MARKER, } from './condaProgressAndTelemetry'; import { splitLines } from '../../../common/stringUtils'; +import { + CreateEnvironmentOptions, + CreateEnvironmentResult, + CreateEnvironmentProvider, +} from '../proposed.createEnvApis'; function generateCommandArgs(version?: string, options?: CreateEnvironmentOptions): string[] { let addGitIgnore = true; diff --git a/src/client/pythonEnvironments/creation/provider/venvCreationProvider.ts b/src/client/pythonEnvironments/creation/provider/venvCreationProvider.ts index cdcb6436643c..9eb0c6edc2cf 100644 --- a/src/client/pythonEnvironments/creation/provider/venvCreationProvider.ts +++ b/src/client/pythonEnvironments/creation/provider/venvCreationProvider.ts @@ -9,12 +9,7 @@ import { execObservable } from '../../../common/process/rawProcessApis'; import { createDeferred } from '../../../common/utils/async'; import { Common, CreateEnv } from '../../../common/utils/localize'; import { traceError, traceInfo, traceLog } from '../../../logging'; -import { - CreateEnvironmentOptions, - CreateEnvironmentProgress, - CreateEnvironmentProvider, - CreateEnvironmentResult, -} from '../types'; +import { CreateEnvironmentProgress } from '../types'; import { pickWorkspaceFolder } from '../common/workspaceSelection'; import { IInterpreterQuickPick } from '../../../interpreter/configuration/types'; import { EnvironmentType, PythonEnvironment } from '../../info'; @@ -25,6 +20,11 @@ import { VenvProgressAndTelemetry, VENV_CREATED_MARKER, VENV_EXISTING_MARKER } f import { showErrorMessageWithLogs } from '../common/commonUtils'; import { IPackageInstallSelection, pickPackagesToInstall } from './venvUtils'; import { InputFlowAction } from '../../../common/utils/multiStepInput'; +import { + CreateEnvironmentProvider, + CreateEnvironmentOptions, + CreateEnvironmentResult, +} from '../proposed.createEnvApis'; function generateCommandArgs(installInfo?: IPackageInstallSelection[], addGitIgnore?: boolean): string[] { const command: string[] = [createVenvScript()]; diff --git a/src/client/pythonEnvironments/creation/types.ts b/src/client/pythonEnvironments/creation/types.ts index 6dbd8adfe1f4..611af5bf7841 100644 --- a/src/client/pythonEnvironments/creation/types.ts +++ b/src/client/pythonEnvironments/creation/types.ts @@ -1,52 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License -import { Progress, Uri } from 'vscode'; +import { Progress } from 'vscode'; export interface CreateEnvironmentProgress extends Progress<{ message?: string; increment?: number }> {} - -export interface CreateEnvironmentOptions { - /** - * Default `true`. If `true`, the environment creation handler is expected to install packages. - */ - installPackages?: boolean; - - /** - * Default `true`. If `true`, the environment creation provider is expected to add the environment to ignore list - * for the source control. - */ - ignoreSourceControl?: boolean; - - /** - * Default `false`. If `true` the creation provider should show back button when showing QuickPick or QuickInput. - */ - showBackButton?: boolean; - - /** - * Default `true`. If `true`, the environment will be selected as the environment to be used for the workspace. - */ - selectEnvironment?: boolean; -} - -export interface CreateEnvironmentResult { - path: string | undefined; - uri: Uri | undefined; - action?: 'Back' | 'Cancel'; -} - -export interface CreateEnvironmentStartedEventArgs { - options: CreateEnvironmentOptions | undefined; -} - -export interface CreateEnvironmentExitedEventArgs { - result: CreateEnvironmentResult | undefined; - error?: unknown; - options: CreateEnvironmentOptions | undefined; -} - -export interface CreateEnvironmentProvider { - createEnvironment(options?: CreateEnvironmentOptions): Promise; - name: string; - description: string; - id: string; -} diff --git a/src/test/pythonEnvironments/creation/createEnvApi.unit.test.ts b/src/test/pythonEnvironments/creation/createEnvApi.unit.test.ts index 1286ac44d58d..ed3ffd98910f 100644 --- a/src/test/pythonEnvironments/creation/createEnvApi.unit.test.ts +++ b/src/test/pythonEnvironments/creation/createEnvApi.unit.test.ts @@ -12,8 +12,8 @@ import * as commandApis from '../../../client/common/vscodeApis/commandApis'; import { IInterpreterQuickPick } from '../../../client/interpreter/configuration/types'; import { registerCreateEnvironmentFeatures } from '../../../client/pythonEnvironments/creation/createEnvApi'; import * as windowApis from '../../../client/common/vscodeApis/windowApis'; -import { CreateEnvironmentProvider } from '../../../client/pythonEnvironments/creation/types'; import { handleCreateEnvironmentCommand } from '../../../client/pythonEnvironments/creation/createEnvironment'; +import { CreateEnvironmentProvider } from '../../../client/pythonEnvironments/creation/proposed.createEnvApis'; chaiUse(chaiAsPromised); diff --git a/src/test/pythonEnvironments/creation/createEnvironment.unit.test.ts b/src/test/pythonEnvironments/creation/createEnvironment.unit.test.ts index 507a2aee88cd..06d481fafcd0 100644 --- a/src/test/pythonEnvironments/creation/createEnvironment.unit.test.ts +++ b/src/test/pythonEnvironments/creation/createEnvironment.unit.test.ts @@ -8,9 +8,9 @@ import * as typemoq from 'typemoq'; import { assert, use as chaiUse } from 'chai'; import * as windowApis from '../../../client/common/vscodeApis/windowApis'; import { handleCreateEnvironmentCommand } from '../../../client/pythonEnvironments/creation/createEnvironment'; -import { CreateEnvironmentProvider } from '../../../client/pythonEnvironments/creation/types'; import { IDisposableRegistry } from '../../../client/common/types'; import { onCreateEnvironmentStarted } from '../../../client/pythonEnvironments/creation/createEnvApi'; +import { CreateEnvironmentProvider } from '../../../client/pythonEnvironments/creation/proposed.createEnvApis'; chaiUse(chaiAsPromised); @@ -233,7 +233,7 @@ suite('Create Environments Tests', () => { showBackButton: true, }); - assert.deepStrictEqual(result, { path: undefined, uri: undefined, action: 'Back' }); + assert.deepStrictEqual(result, { action: 'Back' }); assert.isTrue(showQuickPickStub.notCalled); assert.isTrue(showQuickPickWithBackStub.calledOnce); }); @@ -259,7 +259,7 @@ suite('Create Environments Tests', () => { showBackButton: true, }); - assert.deepStrictEqual(result, { path: undefined, uri: undefined, action: 'Cancel' }); + assert.deepStrictEqual(result, { action: 'Cancel' }); assert.isTrue(showQuickPickStub.notCalled); assert.isTrue(showQuickPickWithBackStub.calledOnce); }); diff --git a/src/test/pythonEnvironments/creation/provider/condaCreationProvider.unit.test.ts b/src/test/pythonEnvironments/creation/provider/condaCreationProvider.unit.test.ts index db5eb351211e..a022596aabbb 100644 --- a/src/test/pythonEnvironments/creation/provider/condaCreationProvider.unit.test.ts +++ b/src/test/pythonEnvironments/creation/provider/condaCreationProvider.unit.test.ts @@ -7,11 +7,7 @@ import { assert, use as chaiUse } from 'chai'; import * as sinon from 'sinon'; import * as typemoq from 'typemoq'; import { CancellationToken, ProgressOptions, Uri } from 'vscode'; -import { - CreateEnvironmentProgress, - CreateEnvironmentProvider, - CreateEnvironmentResult, -} from '../../../../client/pythonEnvironments/creation/types'; +import { CreateEnvironmentProgress } from '../../../../client/pythonEnvironments/creation/types'; import { condaCreationProvider } from '../../../../client/pythonEnvironments/creation/provider/condaCreationProvider'; import * as wsSelect from '../../../../client/pythonEnvironments/creation/common/workspaceSelection'; import * as windowApis from '../../../../client/common/vscodeApis/windowApis'; @@ -23,6 +19,10 @@ import { createDeferred } from '../../../../client/common/utils/async'; import * as commonUtils from '../../../../client/pythonEnvironments/creation/common/commonUtils'; import { CONDA_ENV_CREATED_MARKER } from '../../../../client/pythonEnvironments/creation/provider/condaProgressAndTelemetry'; import { CreateEnv } from '../../../../client/common/utils/localize'; +import { + CreateEnvironmentProvider, + CreateEnvironmentResult, +} from '../../../../client/pythonEnvironments/creation/proposed.createEnvApis'; chaiUse(chaiAsPromised); diff --git a/src/test/pythonEnvironments/creation/provider/venvCreationProvider.unit.test.ts b/src/test/pythonEnvironments/creation/provider/venvCreationProvider.unit.test.ts index b56fb158d6ae..673f4c1397ac 100644 --- a/src/test/pythonEnvironments/creation/provider/venvCreationProvider.unit.test.ts +++ b/src/test/pythonEnvironments/creation/provider/venvCreationProvider.unit.test.ts @@ -6,11 +6,7 @@ import * as typemoq from 'typemoq'; import { assert, use as chaiUse } from 'chai'; import * as sinon from 'sinon'; import { CancellationToken, ProgressOptions, Uri } from 'vscode'; -import { - CreateEnvironmentProgress, - CreateEnvironmentProvider, - CreateEnvironmentResult, -} from '../../../../client/pythonEnvironments/creation/types'; +import { CreateEnvironmentProgress } from '../../../../client/pythonEnvironments/creation/types'; import { VenvCreationProvider } from '../../../../client/pythonEnvironments/creation/provider/venvCreationProvider'; import { IInterpreterQuickPick } from '../../../../client/interpreter/configuration/types'; import * as wsSelect from '../../../../client/pythonEnvironments/creation/common/workspaceSelection'; @@ -23,6 +19,10 @@ import { Output } from '../../../../client/common/process/types'; import { VENV_CREATED_MARKER } from '../../../../client/pythonEnvironments/creation/provider/venvProgressAndTelemetry'; import { CreateEnv } from '../../../../client/common/utils/localize'; import * as venvUtils from '../../../../client/pythonEnvironments/creation/provider/venvUtils'; +import { + CreateEnvironmentProvider, + CreateEnvironmentResult, +} from '../../../../client/pythonEnvironments/creation/proposed.createEnvApis'; chaiUse(chaiAsPromised); From 873fa2dff07ed564037ff00fde9efa1bf9e435fb Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Tue, 18 Apr 2023 13:01:57 -0700 Subject: [PATCH 2/3] Address comments --- src/client/proposedApiTypes.ts | 2 +- .../creation/createEnvApi.ts | 4 +- .../creation/createEnvironment.ts | 10 ++++- .../creation/proposed.createEnvApis.ts | 43 ++++++------------- .../provider/condaCreationProvider.ts | 2 +- .../creation/provider/venvCreationProvider.ts | 2 +- .../creation/createEnvApi.unit.test.ts | 9 +++- .../creation/createEnvironment.unit.test.ts | 4 +- .../condaCreationProvider.unit.test.ts | 7 ++- .../venvCreationProvider.unit.test.ts | 7 ++- 10 files changed, 47 insertions(+), 43 deletions(-) diff --git a/src/client/proposedApiTypes.ts b/src/client/proposedApiTypes.ts index be5874ca47e5..13ad5af543ec 100644 --- a/src/client/proposedApiTypes.ts +++ b/src/client/proposedApiTypes.ts @@ -3,6 +3,6 @@ export interface ProposedExtensionAPI { /** - * Top level poposed APIs should go here. + * Top level proposed APIs should go here. */ } diff --git a/src/client/pythonEnvironments/creation/createEnvApi.ts b/src/client/pythonEnvironments/creation/createEnvApi.ts index 078b66df74e8..1fcba70cba84 100644 --- a/src/client/pythonEnvironments/creation/createEnvApi.ts +++ b/src/client/pythonEnvironments/creation/createEnvApi.ts @@ -71,7 +71,7 @@ export function registerCreateEnvironmentFeatures( registerCreateEnvironmentProvider(condaCreationProvider()), onCreateEnvironmentExited(async (e: DidCreateEnvironmentParams) => { if (e.path && e.options?.selectEnvironment) { - await interpreterPathService.update(e.uri, ConfigurationTarget.WorkspaceFolder, e.path); + await interpreterPathService.update(e.workspace?.uri, ConfigurationTarget.WorkspaceFolder, e.path); showInformationMessage(`${CreateEnv.informEnvCreation} ${pathUtils.getDisplayName(e.path)}`); } }), @@ -89,7 +89,7 @@ export function buildEnvironmentCreationApi(): ProposedCreateEnvironmentAPI { try { return await handleCreateEnvironmentCommand(providers, options); } catch (err) { - return { error: err as Error }; + return { path: undefined, workspace: undefined, action: undefined, error: err as Error }; } }, registerCreateEnvironmentProvider: (provider: CreateEnvironmentProvider) => diff --git a/src/client/pythonEnvironments/creation/createEnvironment.ts b/src/client/pythonEnvironments/creation/createEnvironment.ts index 7c446381a042..d8dee2dfbca1 100644 --- a/src/client/pythonEnvironments/creation/createEnvironment.ts +++ b/src/client/pythonEnvironments/creation/createEnvironment.ts @@ -33,7 +33,13 @@ function fireStartedEvent(options?: CreateEnvironmentOptions): void { } function fireExitedEvent(result?: CreateEnvironmentResult, options?: CreateEnvironmentOptions, error?: Error): void { - onCreateEnvironmentExitedEvent.fire({ ...result, options, error }); + onCreateEnvironmentExitedEvent.fire({ + options, + workspace: result?.workspace, + path: result?.path, + action: result?.action, + error: error || result?.error, + }); startedEventCount -= 1; } @@ -185,7 +191,7 @@ export async function handleCreateEnvironmentCommand( const action = await MultiStepNode.run(envTypeStep); if (options?.showBackButton) { if (action === MultiStepAction.Back || action === MultiStepAction.Cancel) { - result = { action }; + result = { action, workspace: undefined, path: undefined, error: undefined }; } } diff --git a/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts b/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts index 1f3eed5d305b..fa21111e9728 100644 --- a/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts +++ b/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License -import { Uri, Event, Disposable } from 'vscode'; +import { Event, Disposable, WorkspaceFolder } from 'vscode'; export type CreateEnvironmentUserActions = 'Back' | 'Cancel'; export type CreateEnvironmentProviderId = string; @@ -27,7 +27,7 @@ export interface CreateEnvironmentOptions { showBackButton?: boolean; /** - * Default `true`. If `true`, the environment will be selected as the environment to be used for the workspace. + * Default `true`. If `true`, the environment after creation will be selected. */ selectEnvironment?: boolean; } @@ -45,53 +45,33 @@ export interface WillCreateEnvironmentParams { /** * Params passed on `onDidCreateEnvironment` event handler. */ -export interface DidCreateEnvironmentParams { +export interface DidCreateEnvironmentParams extends CreateEnvironmentResult { /** * Options used to create the Python environment. */ - options?: CreateEnvironmentOptions; - - /** - * Workspace Uri associated with the environment. - */ - uri?: Uri; - - /** - * Path to the executable python in the environment - */ - path?: string; - - /** - * User action that resulted in exit from the create environment flow. - */ - action?: CreateEnvironmentUserActions; - - /** - * Error if any occurred during environment creation. - */ - error?: Error; + options: CreateEnvironmentOptions | undefined; } export interface CreateEnvironmentResult { /** * Workspace Uri associated with the environment. */ - uri?: Uri; + workspace: WorkspaceFolder | undefined; /** - * Path to the python executable in the environment. + * Path to the executable python in the environment */ - path?: string; + path: string | undefined; /** * User action that resulted in exit from the create environment flow. */ - action?: CreateEnvironmentUserActions; + action: CreateEnvironmentUserActions | undefined; /** * Error if any occurred during environment creation. */ - error?: Error; + error: Error | undefined; } /** @@ -127,7 +107,8 @@ export interface CreateEnvironmentProvider { description: string; /** - * Tools used to manage this environment. e.g., ['conda'] + * Tools used to manage this environment. e.g., ['conda']. In the most to least priority order + * for resolving and working with the environment. */ tools?: string[]; } @@ -151,7 +132,7 @@ export interface ProposedCreateEnvironmentAPI { * This API will show a QuickPick to select an environment provider from available list of * providers. Based on the selection the `createEnvironment` will be called on the provider. */ - createEnvironment(options: CreateEnvironmentOptions): Promise; + createEnvironment(options?: CreateEnvironmentOptions): Promise; /** * This API should be called to register an environment creation provider. It returns diff --git a/src/client/pythonEnvironments/creation/provider/condaCreationProvider.ts b/src/client/pythonEnvironments/creation/provider/condaCreationProvider.ts index 8fa3c6e3f29c..1abde911a131 100644 --- a/src/client/pythonEnvironments/creation/provider/condaCreationProvider.ts +++ b/src/client/pythonEnvironments/creation/provider/condaCreationProvider.ts @@ -247,7 +247,7 @@ async function createEnvironment(options?: CreateEnvironmentOptions): Promise { [true, false].forEach((selectEnvironment) => { test(`Set environment selectEnvironment == ${selectEnvironment}`, async () => { + const workspace1 = { + uri: Uri.file('/path/to/env'), + name: 'workspace1', + index: 0, + }; const provider = typemoq.Mock.ofType(); provider.setup((p) => p.name).returns(() => 'test'); provider.setup((p) => p.id).returns(() => 'test-id'); @@ -66,7 +71,9 @@ suite('Create Environment APIs', () => { .returns(() => Promise.resolve({ path: '/path/to/env', - uri: Uri.file('/path/to/env'), + workspace: workspace1, + action: undefined, + error: undefined, }), ); provider.setup((p) => (p as any).then).returns(() => undefined); diff --git a/src/test/pythonEnvironments/creation/createEnvironment.unit.test.ts b/src/test/pythonEnvironments/creation/createEnvironment.unit.test.ts index 06d481fafcd0..215a94790768 100644 --- a/src/test/pythonEnvironments/creation/createEnvironment.unit.test.ts +++ b/src/test/pythonEnvironments/creation/createEnvironment.unit.test.ts @@ -233,7 +233,7 @@ suite('Create Environments Tests', () => { showBackButton: true, }); - assert.deepStrictEqual(result, { action: 'Back' }); + assert.deepStrictEqual(result, { action: 'Back', workspace: undefined, path: undefined, error: undefined }); assert.isTrue(showQuickPickStub.notCalled); assert.isTrue(showQuickPickWithBackStub.calledOnce); }); @@ -259,7 +259,7 @@ suite('Create Environments Tests', () => { showBackButton: true, }); - assert.deepStrictEqual(result, { action: 'Cancel' }); + assert.deepStrictEqual(result, { action: 'Cancel', workspace: undefined, path: undefined, error: undefined }); assert.isTrue(showQuickPickStub.notCalled); assert.isTrue(showQuickPickWithBackStub.calledOnce); }); diff --git a/src/test/pythonEnvironments/creation/provider/condaCreationProvider.unit.test.ts b/src/test/pythonEnvironments/creation/provider/condaCreationProvider.unit.test.ts index a022596aabbb..11da2bcabfbd 100644 --- a/src/test/pythonEnvironments/creation/provider/condaCreationProvider.unit.test.ts +++ b/src/test/pythonEnvironments/creation/provider/condaCreationProvider.unit.test.ts @@ -131,7 +131,12 @@ suite('Conda Creation provider tests', () => { _next!({ out: `${CONDA_ENV_CREATED_MARKER}new_environment`, source: 'stdout' }); _complete!(); - assert.deepStrictEqual(await promise, { path: 'new_environment', uri: workspace1.uri }); + assert.deepStrictEqual(await promise, { + path: 'new_environment', + workspace: workspace1, + action: undefined, + error: undefined, + }); assert.isTrue(showErrorMessageWithLogsStub.notCalled); }); diff --git a/src/test/pythonEnvironments/creation/provider/venvCreationProvider.unit.test.ts b/src/test/pythonEnvironments/creation/provider/venvCreationProvider.unit.test.ts index 673f4c1397ac..fa3a682dd515 100644 --- a/src/test/pythonEnvironments/creation/provider/venvCreationProvider.unit.test.ts +++ b/src/test/pythonEnvironments/creation/provider/venvCreationProvider.unit.test.ts @@ -155,7 +155,12 @@ suite('venv Creation provider tests', () => { _complete!(); const actual = await promise; - assert.deepStrictEqual(actual, { path: 'new_environment', uri: workspace1.uri }); + assert.deepStrictEqual(actual, { + path: 'new_environment', + workspace: workspace1, + action: undefined, + error: undefined, + }); interpreterQuickPick.verifyAll(); progressMock.verifyAll(); assert.isTrue(showErrorMessageWithLogsStub.notCalled); From f0f8a1f24150b23b77fac4c8b87c48549dce2fed Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Tue, 18 Apr 2023 16:16:35 -0700 Subject: [PATCH 3/3] Addressed more comments --- .../creation/createEnvApi.ts | 12 ++++++++---- .../creation/createEnvironment.ts | 16 ++++++++-------- .../creation/proposed.createEnvApis.ts | 19 ++++++++++--------- .../provider/condaCreationProvider.ts | 4 +++- .../creation/provider/venvCreationProvider.ts | 4 +++- .../creation/createEnvApi.unit.test.ts | 2 +- .../creation/createEnvironment.unit.test.ts | 14 ++++++++++++-- .../condaCreationProvider.unit.test.ts | 2 +- .../venvCreationProvider.unit.test.ts | 2 +- 9 files changed, 47 insertions(+), 28 deletions(-) diff --git a/src/client/pythonEnvironments/creation/createEnvApi.ts b/src/client/pythonEnvironments/creation/createEnvApi.ts index 1fcba70cba84..6445e0938025 100644 --- a/src/client/pythonEnvironments/creation/createEnvApi.ts +++ b/src/client/pythonEnvironments/creation/createEnvApi.ts @@ -16,7 +16,7 @@ import { CreateEnvironmentOptions, CreateEnvironmentResult, ProposedCreateEnvironmentAPI, - DidCreateEnvironmentParams, + EnvironmentDidCreateEvent, } from './proposed.createEnvApis'; class CreateEnvironmentProviders { @@ -69,9 +69,13 @@ export function registerCreateEnvironmentFeatures( ), registerCreateEnvironmentProvider(new VenvCreationProvider(interpreterQuickPick)), registerCreateEnvironmentProvider(condaCreationProvider()), - onCreateEnvironmentExited(async (e: DidCreateEnvironmentParams) => { + onCreateEnvironmentExited(async (e: EnvironmentDidCreateEvent) => { if (e.path && e.options?.selectEnvironment) { - await interpreterPathService.update(e.workspace?.uri, ConfigurationTarget.WorkspaceFolder, e.path); + await interpreterPathService.update( + e.workspaceFolder?.uri, + ConfigurationTarget.WorkspaceFolder, + e.path, + ); showInformationMessage(`${CreateEnv.informEnvCreation} ${pathUtils.getDisplayName(e.path)}`); } }), @@ -89,7 +93,7 @@ export function buildEnvironmentCreationApi(): ProposedCreateEnvironmentAPI { try { return await handleCreateEnvironmentCommand(providers, options); } catch (err) { - return { path: undefined, workspace: undefined, action: undefined, error: err as Error }; + return { path: undefined, workspaceFolder: undefined, action: undefined, error: err as Error }; } }, registerCreateEnvironmentProvider: (provider: CreateEnvironmentProvider) => diff --git a/src/client/pythonEnvironments/creation/createEnvironment.ts b/src/client/pythonEnvironments/creation/createEnvironment.ts index d8dee2dfbca1..4593ff1abf92 100644 --- a/src/client/pythonEnvironments/creation/createEnvironment.ts +++ b/src/client/pythonEnvironments/creation/createEnvironment.ts @@ -14,12 +14,12 @@ import { CreateEnvironmentOptions, CreateEnvironmentResult, CreateEnvironmentProvider, - WillCreateEnvironmentParams, - DidCreateEnvironmentParams, + EnvironmentWillCreateEvent, + EnvironmentDidCreateEvent, } from './proposed.createEnvApis'; -const onCreateEnvironmentStartedEvent = new EventEmitter(); -const onCreateEnvironmentExitedEvent = new EventEmitter(); +const onCreateEnvironmentStartedEvent = new EventEmitter(); +const onCreateEnvironmentExitedEvent = new EventEmitter(); let startedEventCount = 0; @@ -35,7 +35,7 @@ function fireStartedEvent(options?: CreateEnvironmentOptions): void { function fireExitedEvent(result?: CreateEnvironmentResult, options?: CreateEnvironmentOptions, error?: Error): void { onCreateEnvironmentExitedEvent.fire({ options, - workspace: result?.workspace, + workspaceFolder: result?.workspaceFolder, path: result?.path, action: result?.action, error: error || result?.error, @@ -44,8 +44,8 @@ function fireExitedEvent(result?: CreateEnvironmentResult, options?: CreateEnvir } export function getCreationEvents(): { - onCreateEnvironmentStarted: Event; - onCreateEnvironmentExited: Event; + onCreateEnvironmentStarted: Event; + onCreateEnvironmentExited: Event; isCreatingEnvironment: () => boolean; } { return { @@ -191,7 +191,7 @@ export async function handleCreateEnvironmentCommand( const action = await MultiStepNode.run(envTypeStep); if (options?.showBackButton) { if (action === MultiStepAction.Back || action === MultiStepAction.Cancel) { - result = { action, workspace: undefined, path: undefined, error: undefined }; + result = { action, workspaceFolder: undefined, path: undefined, error: undefined }; } } diff --git a/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts b/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts index fa21111e9728..52209a5a31d0 100644 --- a/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts +++ b/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts @@ -2,9 +2,10 @@ // Licensed under the MIT License import { Event, Disposable, WorkspaceFolder } from 'vscode'; +import { EnvironmentTools } from '../../apiTypes'; export type CreateEnvironmentUserActions = 'Back' | 'Cancel'; -export type CreateEnvironmentProviderId = string; +export type EnvironmentProviderId = string; /** * Options used when creating a Python environment. @@ -35,7 +36,7 @@ export interface CreateEnvironmentOptions { /** * Params passed on `onWillCreateEnvironment` event handler. */ -export interface WillCreateEnvironmentParams { +export interface EnvironmentWillCreateEvent { /** * Options used to create a Python environment. */ @@ -45,7 +46,7 @@ export interface WillCreateEnvironmentParams { /** * Params passed on `onDidCreateEnvironment` event handler. */ -export interface DidCreateEnvironmentParams extends CreateEnvironmentResult { +export interface EnvironmentDidCreateEvent extends CreateEnvironmentResult { /** * Options used to create the Python environment. */ @@ -54,9 +55,9 @@ export interface DidCreateEnvironmentParams extends CreateEnvironmentResult { export interface CreateEnvironmentResult { /** - * Workspace Uri associated with the environment. + * Workspace folder associated with the environment. */ - workspace: WorkspaceFolder | undefined; + workspaceFolder: WorkspaceFolder | undefined; /** * Path to the executable python in the environment @@ -94,7 +95,7 @@ export interface CreateEnvironmentProvider { /** * Unique ID for the creation provider, typically : */ - id: CreateEnvironmentProviderId; + id: EnvironmentProviderId; /** * Display name for the creation provider. @@ -110,7 +111,7 @@ export interface CreateEnvironmentProvider { * Tools used to manage this environment. e.g., ['conda']. In the most to least priority order * for resolving and working with the environment. */ - tools?: string[]; + tools: EnvironmentTools[]; } export interface ProposedCreateEnvironmentAPI { @@ -119,14 +120,14 @@ export interface ProposedCreateEnvironmentAPI { * provider (including internal providers). This will also receive any options passed in * or defaults used to create environment. */ - onWillCreateEnvironment: Event; + onWillCreateEnvironment: Event; /** * This API can be used to detect when the environment provider exits for any registered * provider (including internal providers). This will also receive created environment path, * any errors, or user actions taken from the provider. */ - onDidCreateEnvironment: Event; + onDidCreateEnvironment: Event; /** * This API will show a QuickPick to select an environment provider from available list of diff --git a/src/client/pythonEnvironments/creation/provider/condaCreationProvider.ts b/src/client/pythonEnvironments/creation/provider/condaCreationProvider.ts index 1abde911a131..39cd40afd41a 100644 --- a/src/client/pythonEnvironments/creation/provider/condaCreationProvider.ts +++ b/src/client/pythonEnvironments/creation/provider/condaCreationProvider.ts @@ -247,7 +247,7 @@ async function createEnvironment(options?: CreateEnvironmentOptions): Promise { .returns(() => Promise.resolve({ path: '/path/to/env', - workspace: workspace1, + workspaceFolder: workspace1, action: undefined, error: undefined, }), diff --git a/src/test/pythonEnvironments/creation/createEnvironment.unit.test.ts b/src/test/pythonEnvironments/creation/createEnvironment.unit.test.ts index 215a94790768..f16f81233369 100644 --- a/src/test/pythonEnvironments/creation/createEnvironment.unit.test.ts +++ b/src/test/pythonEnvironments/creation/createEnvironment.unit.test.ts @@ -233,7 +233,12 @@ suite('Create Environments Tests', () => { showBackButton: true, }); - assert.deepStrictEqual(result, { action: 'Back', workspace: undefined, path: undefined, error: undefined }); + assert.deepStrictEqual(result, { + action: 'Back', + workspaceFolder: undefined, + path: undefined, + error: undefined, + }); assert.isTrue(showQuickPickStub.notCalled); assert.isTrue(showQuickPickWithBackStub.calledOnce); }); @@ -259,7 +264,12 @@ suite('Create Environments Tests', () => { showBackButton: true, }); - assert.deepStrictEqual(result, { action: 'Cancel', workspace: undefined, path: undefined, error: undefined }); + assert.deepStrictEqual(result, { + action: 'Cancel', + workspaceFolder: undefined, + path: undefined, + error: undefined, + }); assert.isTrue(showQuickPickStub.notCalled); assert.isTrue(showQuickPickWithBackStub.calledOnce); }); diff --git a/src/test/pythonEnvironments/creation/provider/condaCreationProvider.unit.test.ts b/src/test/pythonEnvironments/creation/provider/condaCreationProvider.unit.test.ts index 11da2bcabfbd..cb4df95c8c1f 100644 --- a/src/test/pythonEnvironments/creation/provider/condaCreationProvider.unit.test.ts +++ b/src/test/pythonEnvironments/creation/provider/condaCreationProvider.unit.test.ts @@ -133,7 +133,7 @@ suite('Conda Creation provider tests', () => { _complete!(); assert.deepStrictEqual(await promise, { path: 'new_environment', - workspace: workspace1, + workspaceFolder: workspace1, action: undefined, error: undefined, }); diff --git a/src/test/pythonEnvironments/creation/provider/venvCreationProvider.unit.test.ts b/src/test/pythonEnvironments/creation/provider/venvCreationProvider.unit.test.ts index fa3a682dd515..1c22264f2ada 100644 --- a/src/test/pythonEnvironments/creation/provider/venvCreationProvider.unit.test.ts +++ b/src/test/pythonEnvironments/creation/provider/venvCreationProvider.unit.test.ts @@ -157,7 +157,7 @@ suite('venv Creation provider tests', () => { const actual = await promise; assert.deepStrictEqual(actual, { path: 'new_environment', - workspace: workspace1, + workspaceFolder: workspace1, action: undefined, error: undefined, });