Skip to content

Commit

Permalink
Add create environment to select interpreter (#22746)
Browse files Browse the repository at this point in the history
  • Loading branch information
karthiknadig authored Jan 16, 2024
1 parent 63cf263 commit 2f3102f
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 5 deletions.
2 changes: 2 additions & 0 deletions src/client/common/application/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { CancellationToken, Position, TextDocument, Uri } from 'vscode';
import { Commands as LSCommands } from '../../activation/commands';
import { TensorBoardEntrypoint, TensorBoardEntrypointTrigger } from '../../tensorBoard/constants';
import { Channel, Commands, CommandSource } from '../constants';
import { CreateEnvironmentOptions } from '../../pythonEnvironments/creation/proposed.createEnvApis';

export type CommandsWithoutArgs = keyof ICommandNameWithoutArgumentTypeMapping;

Expand Down Expand Up @@ -56,6 +57,7 @@ export type AllCommands = keyof ICommandNameArgumentTypeMapping;
* @extends {ICommandNameWithoutArgumentTypeMapping}
*/
export interface ICommandNameArgumentTypeMapping extends ICommandNameWithoutArgumentTypeMapping {
[Commands.Create_Environment]: [CreateEnvironmentOptions];
['vscode.openWith']: [Uri, string];
['workbench.action.quickOpen']: [string];
['workbench.action.openWalkthrough']: [string | { category: string; step: string }, boolean | undefined];
Expand Down
2 changes: 2 additions & 0 deletions src/client/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,14 @@ export namespace Octicons {
export const Test_Skip = '$(circle-slash)';
export const Downloading = '$(cloud-download)';
export const Installing = '$(desktop-download)';
export const Search = '$(search)';
export const Search_Stop = '$(search-stop)';
export const Star = '$(star-full)';
export const Gear = '$(gear)';
export const Warning = '$(warning)';
export const Error = '$(error)';
export const Lightbulb = '$(lightbulb)';
export const Folder = '$(folder)';
}

/**
Expand Down
3 changes: 3 additions & 0 deletions src/client/common/utils/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@ export namespace InterpreterQuickPickList {
};
export const refreshInterpreterList = l10n.t('Refresh Interpreter list');
export const refreshingInterpreterList = l10n.t('Refreshing Interpreter list...');
export const create = {
label: l10n.t('Create Virtual Environment...'),
};
}

export namespace OutputChannelNames {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,13 @@ export namespace EnvGroups {

@injectable()
export class SetInterpreterCommand extends BaseInterpreterSelectorCommand implements IInterpreterQuickPick {
private readonly createEnvironmentSuggestion: QuickPickItem = {
label: `${Octicons.Add} ${InterpreterQuickPickList.create.label}`,
alwaysShow: true,
};

private readonly manualEntrySuggestion: ISpecialQuickPickItem = {
label: `${Octicons.Add} ${InterpreterQuickPickList.enterPath.label}`,
label: `${Octicons.Folder} ${InterpreterQuickPickList.enterPath.label}`,
alwaysShow: true,
};

Expand Down Expand Up @@ -220,6 +225,13 @@ export class SetInterpreterCommand extends BaseInterpreterSelectorCommand implem
} else if (selection.label === this.manualEntrySuggestion.label) {
sendTelemetryEvent(EventName.SELECT_INTERPRETER_ENTER_OR_FIND);
return this._enterOrBrowseInterpreterPath.bind(this);
} else if (selection.label === this.createEnvironmentSuggestion.label) {
this.commandManager
.executeCommand(Commands.Create_Environment, {
showBackButton: false,
selectEnvironment: true,
})
.then(noop, noop);
} else if (selection.label === this.noPythonInstalled.label) {
this.commandManager.executeCommand(Commands.InstallPython).then(noop, noop);
this.wasNoPythonInstalledItemClicked = true;
Expand All @@ -237,7 +249,13 @@ export class SetInterpreterCommand extends BaseInterpreterSelectorCommand implem
filter: ((i: PythonEnvironment) => boolean) | undefined,
params?: InterpreterQuickPickParams,
): QuickPickType[] {
const suggestions: QuickPickType[] = [this.manualEntrySuggestion];
const suggestions: QuickPickType[] = [];
if (params?.showCreateEnvironment) {
suggestions.push(this.createEnvironmentSuggestion, { label: '', kind: QuickPickItemKind.Separator });
}

suggestions.push(this.manualEntrySuggestion, { label: '', kind: QuickPickItemKind.Separator });

const defaultInterpreterPathSuggestion = this.getDefaultInterpreterPathSuggestion(resource);
if (defaultInterpreterPathSuggestion) {
suggestions.push(defaultInterpreterPathSuggestion);
Expand Down Expand Up @@ -553,7 +571,10 @@ export class SetInterpreterCommand extends BaseInterpreterSelectorCommand implem
const wkspace = targetConfig[0].folderUri;
const interpreterState: InterpreterStateArgs = { path: undefined, workspace: wkspace };
const multiStep = this.multiStepFactory.create<InterpreterStateArgs>();
await multiStep.run((input, s) => this._pickInterpreter(input, s, undefined), interpreterState);
await multiStep.run(
(input, s) => this._pickInterpreter(input, s, undefined, { showCreateEnvironment: true }),
interpreterState,
);

if (interpreterState.path !== undefined) {
// User may choose to have an empty string stored, so variable `interpreterState.path` may be
Expand Down
5 changes: 5 additions & 0 deletions src/client/interpreter/configuration/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ export interface InterpreterQuickPickParams {
* Specify `true` to show back button.
*/
showBackButton?: boolean;

/**
* Show button to create a new environment.
*/
showCreateEnvironment?: boolean;
}

export const IInterpreterQuickPick = Symbol('IInterpreterQuickPick');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,11 @@ suite('Set Interpreter Command', () => {
} as PythonEnvironment,
};
const expectedEnterInterpreterPathSuggestion = {
label: `${Octicons.Add} ${InterpreterQuickPickList.enterPath.label}`,
label: `${Octicons.Folder} ${InterpreterQuickPickList.enterPath.label}`,
alwaysShow: true,
};
const expectedCreateEnvSuggestion = {
label: `${Octicons.Add} ${InterpreterQuickPickList.create.label}`,
alwaysShow: true,
};
const currentPythonPath = 'python';
Expand Down Expand Up @@ -237,6 +241,7 @@ suite('Set Interpreter Command', () => {
recommended.description = interpreterPath;
const suggestions = [
expectedEnterInterpreterPathSuggestion,
{ kind: QuickPickItemKind.Separator, label: '' },
defaultInterpreterPathSuggestion,
{ kind: QuickPickItemKind.Separator, label: EnvGroups.Recommended },
recommended,
Expand Down Expand Up @@ -278,11 +283,66 @@ suite('Set Interpreter Command', () => {
assert.deepStrictEqual(actualParameters, expectedParameters, 'Params not equal');
});

test('Picker should show create env when set in options', async () => {
const state: InterpreterStateArgs = { path: 'some path', workspace: undefined };
const multiStepInput = TypeMoq.Mock.ofType<IMultiStepInput<InterpreterStateArgs>>();
const recommended = cloneDeep(item);
recommended.label = `${Octicons.Star} ${item.label}`;
recommended.description = interpreterPath;
const suggestions = [
expectedCreateEnvSuggestion,
{ kind: QuickPickItemKind.Separator, label: '' },
expectedEnterInterpreterPathSuggestion,
{ kind: QuickPickItemKind.Separator, label: '' },
defaultInterpreterPathSuggestion,
{ kind: QuickPickItemKind.Separator, label: EnvGroups.Recommended },
recommended,
];
const expectedParameters: IQuickPickParameters<QuickPickItem> = {
placeholder: `Selected Interpreter: ${currentPythonPath}`,
items: suggestions,
matchOnDetail: true,
matchOnDescription: true,
title: InterpreterQuickPickList.browsePath.openButtonLabel,
sortByLabel: true,
keepScrollPosition: true,
};
let actualParameters: IQuickPickParameters<QuickPickItem> | undefined;
multiStepInput
.setup((i) => i.showQuickPick(TypeMoq.It.isAny()))
.callback((options) => {
actualParameters = options;
})
.returns(() => Promise.resolve((undefined as unknown) as QuickPickItem));

await setInterpreterCommand._pickInterpreter(multiStepInput.object, state, undefined, {
showCreateEnvironment: true,
});

expect(actualParameters).to.not.equal(undefined, 'Parameters not set');
const refreshButtons = actualParameters!.customButtonSetups;
expect(refreshButtons).to.not.equal(undefined, 'Callback not set');
delete actualParameters!.initialize;
delete actualParameters!.customButtonSetups;
delete actualParameters!.onChangeItem;
if (typeof actualParameters!.activeItem === 'function') {
const activeItem = await actualParameters!.activeItem(({ items: suggestions } as unknown) as QuickPick<
QuickPickType
>);
assert.deepStrictEqual(activeItem, recommended);
} else {
assert(false, 'Not a function');
}
delete actualParameters!.activeItem;
assert.deepStrictEqual(actualParameters, expectedParameters, 'Params not equal');
});

test('Picker should be displayed with expected items if no interpreters are available', async () => {
const state: InterpreterStateArgs = { path: 'some path', workspace: undefined };
const multiStepInput = TypeMoq.Mock.ofType<IMultiStepInput<InterpreterStateArgs>>();
const suggestions = [
expectedEnterInterpreterPathSuggestion,
{ kind: QuickPickItemKind.Separator, label: '' },
defaultInterpreterPathSuggestion,
noPythonInstalled,
];
Expand Down Expand Up @@ -440,6 +500,7 @@ suite('Set Interpreter Command', () => {
recommended.description = interpreterPath;
const suggestions = [
expectedEnterInterpreterPathSuggestion,
{ kind: QuickPickItemKind.Separator, label: '' },
defaultInterpreterPathSuggestion,
{ kind: QuickPickItemKind.Separator, label: EnvGroups.Recommended },
recommended,
Expand Down Expand Up @@ -556,6 +617,7 @@ suite('Set Interpreter Command', () => {
recommended.description = interpreterPath;
const suggestions = [
expectedEnterInterpreterPathSuggestion,
{ kind: QuickPickItemKind.Separator, label: '' },
defaultInterpreterPathSuggestion,
{ kind: QuickPickItemKind.Separator, label: EnvGroups.Recommended },
recommended,
Expand Down Expand Up @@ -652,7 +714,13 @@ suite('Set Interpreter Command', () => {
alwaysShow: true,
};

const suggestions = [expectedEnterInterpreterPathSuggestion, defaultPathSuggestion, separator, recommended];
const suggestions = [
expectedEnterInterpreterPathSuggestion,
{ kind: QuickPickItemKind.Separator, label: '' },
defaultPathSuggestion,
separator,
recommended,
];
const expectedParameters: IQuickPickParameters<QuickPickItem> = {
placeholder: `Selected Interpreter: ${currentPythonPath}`,
items: suggestions,
Expand Down

0 comments on commit 2f3102f

Please sign in to comment.