Skip to content

Commit 2f3102f

Browse files
authored
1 parent 63cf263 commit 2f3102f

File tree

6 files changed

+106
-5
lines changed

6 files changed

+106
-5
lines changed

src/client/common/application/commands.ts

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { CancellationToken, Position, TextDocument, Uri } from 'vscode';
77
import { Commands as LSCommands } from '../../activation/commands';
88
import { TensorBoardEntrypoint, TensorBoardEntrypointTrigger } from '../../tensorBoard/constants';
99
import { Channel, Commands, CommandSource } from '../constants';
10+
import { CreateEnvironmentOptions } from '../../pythonEnvironments/creation/proposed.createEnvApis';
1011

1112
export type CommandsWithoutArgs = keyof ICommandNameWithoutArgumentTypeMapping;
1213

@@ -56,6 +57,7 @@ export type AllCommands = keyof ICommandNameArgumentTypeMapping;
5657
* @extends {ICommandNameWithoutArgumentTypeMapping}
5758
*/
5859
export interface ICommandNameArgumentTypeMapping extends ICommandNameWithoutArgumentTypeMapping {
60+
[Commands.Create_Environment]: [CreateEnvironmentOptions];
5961
['vscode.openWith']: [Uri, string];
6062
['workbench.action.quickOpen']: [string];
6163
['workbench.action.openWalkthrough']: [string | { category: string; step: string }, boolean | undefined];

src/client/common/constants.ts

+2
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,14 @@ export namespace Octicons {
7474
export const Test_Skip = '$(circle-slash)';
7575
export const Downloading = '$(cloud-download)';
7676
export const Installing = '$(desktop-download)';
77+
export const Search = '$(search)';
7778
export const Search_Stop = '$(search-stop)';
7879
export const Star = '$(star-full)';
7980
export const Gear = '$(gear)';
8081
export const Warning = '$(warning)';
8182
export const Error = '$(error)';
8283
export const Lightbulb = '$(lightbulb)';
84+
export const Folder = '$(folder)';
8385
}
8486

8587
/**

src/client/common/utils/localize.ts

+3
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,9 @@ export namespace InterpreterQuickPickList {
259259
};
260260
export const refreshInterpreterList = l10n.t('Refresh Interpreter list');
261261
export const refreshingInterpreterList = l10n.t('Refreshing Interpreter list...');
262+
export const create = {
263+
label: l10n.t('Create Virtual Environment...'),
264+
};
262265
}
263266

264267
export namespace OutputChannelNames {

src/client/interpreter/configuration/interpreterSelector/commands/setInterpreter.ts

+24-3
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,13 @@ export namespace EnvGroups {
8181

8282
@injectable()
8383
export class SetInterpreterCommand extends BaseInterpreterSelectorCommand implements IInterpreterQuickPick {
84+
private readonly createEnvironmentSuggestion: QuickPickItem = {
85+
label: `${Octicons.Add} ${InterpreterQuickPickList.create.label}`,
86+
alwaysShow: true,
87+
};
88+
8489
private readonly manualEntrySuggestion: ISpecialQuickPickItem = {
85-
label: `${Octicons.Add} ${InterpreterQuickPickList.enterPath.label}`,
90+
label: `${Octicons.Folder} ${InterpreterQuickPickList.enterPath.label}`,
8691
alwaysShow: true,
8792
};
8893

@@ -220,6 +225,13 @@ export class SetInterpreterCommand extends BaseInterpreterSelectorCommand implem
220225
} else if (selection.label === this.manualEntrySuggestion.label) {
221226
sendTelemetryEvent(EventName.SELECT_INTERPRETER_ENTER_OR_FIND);
222227
return this._enterOrBrowseInterpreterPath.bind(this);
228+
} else if (selection.label === this.createEnvironmentSuggestion.label) {
229+
this.commandManager
230+
.executeCommand(Commands.Create_Environment, {
231+
showBackButton: false,
232+
selectEnvironment: true,
233+
})
234+
.then(noop, noop);
223235
} else if (selection.label === this.noPythonInstalled.label) {
224236
this.commandManager.executeCommand(Commands.InstallPython).then(noop, noop);
225237
this.wasNoPythonInstalledItemClicked = true;
@@ -237,7 +249,13 @@ export class SetInterpreterCommand extends BaseInterpreterSelectorCommand implem
237249
filter: ((i: PythonEnvironment) => boolean) | undefined,
238250
params?: InterpreterQuickPickParams,
239251
): QuickPickType[] {
240-
const suggestions: QuickPickType[] = [this.manualEntrySuggestion];
252+
const suggestions: QuickPickType[] = [];
253+
if (params?.showCreateEnvironment) {
254+
suggestions.push(this.createEnvironmentSuggestion, { label: '', kind: QuickPickItemKind.Separator });
255+
}
256+
257+
suggestions.push(this.manualEntrySuggestion, { label: '', kind: QuickPickItemKind.Separator });
258+
241259
const defaultInterpreterPathSuggestion = this.getDefaultInterpreterPathSuggestion(resource);
242260
if (defaultInterpreterPathSuggestion) {
243261
suggestions.push(defaultInterpreterPathSuggestion);
@@ -553,7 +571,10 @@ export class SetInterpreterCommand extends BaseInterpreterSelectorCommand implem
553571
const wkspace = targetConfig[0].folderUri;
554572
const interpreterState: InterpreterStateArgs = { path: undefined, workspace: wkspace };
555573
const multiStep = this.multiStepFactory.create<InterpreterStateArgs>();
556-
await multiStep.run((input, s) => this._pickInterpreter(input, s, undefined), interpreterState);
574+
await multiStep.run(
575+
(input, s) => this._pickInterpreter(input, s, undefined, { showCreateEnvironment: true }),
576+
interpreterState,
577+
);
557578

558579
if (interpreterState.path !== undefined) {
559580
// User may choose to have an empty string stored, so variable `interpreterState.path` may be

src/client/interpreter/configuration/types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ export interface InterpreterQuickPickParams {
8080
* Specify `true` to show back button.
8181
*/
8282
showBackButton?: boolean;
83+
84+
/**
85+
* Show button to create a new environment.
86+
*/
87+
showCreateEnvironment?: boolean;
8388
}
8489

8590
export const IInterpreterQuickPick = Symbol('IInterpreterQuickPick');

src/test/configuration/interpreterSelector/commands/setInterpreter.unit.test.ts

+70-2
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,11 @@ suite('Set Interpreter Command', () => {
155155
} as PythonEnvironment,
156156
};
157157
const expectedEnterInterpreterPathSuggestion = {
158-
label: `${Octicons.Add} ${InterpreterQuickPickList.enterPath.label}`,
158+
label: `${Octicons.Folder} ${InterpreterQuickPickList.enterPath.label}`,
159+
alwaysShow: true,
160+
};
161+
const expectedCreateEnvSuggestion = {
162+
label: `${Octicons.Add} ${InterpreterQuickPickList.create.label}`,
159163
alwaysShow: true,
160164
};
161165
const currentPythonPath = 'python';
@@ -237,6 +241,7 @@ suite('Set Interpreter Command', () => {
237241
recommended.description = interpreterPath;
238242
const suggestions = [
239243
expectedEnterInterpreterPathSuggestion,
244+
{ kind: QuickPickItemKind.Separator, label: '' },
240245
defaultInterpreterPathSuggestion,
241246
{ kind: QuickPickItemKind.Separator, label: EnvGroups.Recommended },
242247
recommended,
@@ -278,11 +283,66 @@ suite('Set Interpreter Command', () => {
278283
assert.deepStrictEqual(actualParameters, expectedParameters, 'Params not equal');
279284
});
280285

286+
test('Picker should show create env when set in options', async () => {
287+
const state: InterpreterStateArgs = { path: 'some path', workspace: undefined };
288+
const multiStepInput = TypeMoq.Mock.ofType<IMultiStepInput<InterpreterStateArgs>>();
289+
const recommended = cloneDeep(item);
290+
recommended.label = `${Octicons.Star} ${item.label}`;
291+
recommended.description = interpreterPath;
292+
const suggestions = [
293+
expectedCreateEnvSuggestion,
294+
{ kind: QuickPickItemKind.Separator, label: '' },
295+
expectedEnterInterpreterPathSuggestion,
296+
{ kind: QuickPickItemKind.Separator, label: '' },
297+
defaultInterpreterPathSuggestion,
298+
{ kind: QuickPickItemKind.Separator, label: EnvGroups.Recommended },
299+
recommended,
300+
];
301+
const expectedParameters: IQuickPickParameters<QuickPickItem> = {
302+
placeholder: `Selected Interpreter: ${currentPythonPath}`,
303+
items: suggestions,
304+
matchOnDetail: true,
305+
matchOnDescription: true,
306+
title: InterpreterQuickPickList.browsePath.openButtonLabel,
307+
sortByLabel: true,
308+
keepScrollPosition: true,
309+
};
310+
let actualParameters: IQuickPickParameters<QuickPickItem> | undefined;
311+
multiStepInput
312+
.setup((i) => i.showQuickPick(TypeMoq.It.isAny()))
313+
.callback((options) => {
314+
actualParameters = options;
315+
})
316+
.returns(() => Promise.resolve((undefined as unknown) as QuickPickItem));
317+
318+
await setInterpreterCommand._pickInterpreter(multiStepInput.object, state, undefined, {
319+
showCreateEnvironment: true,
320+
});
321+
322+
expect(actualParameters).to.not.equal(undefined, 'Parameters not set');
323+
const refreshButtons = actualParameters!.customButtonSetups;
324+
expect(refreshButtons).to.not.equal(undefined, 'Callback not set');
325+
delete actualParameters!.initialize;
326+
delete actualParameters!.customButtonSetups;
327+
delete actualParameters!.onChangeItem;
328+
if (typeof actualParameters!.activeItem === 'function') {
329+
const activeItem = await actualParameters!.activeItem(({ items: suggestions } as unknown) as QuickPick<
330+
QuickPickType
331+
>);
332+
assert.deepStrictEqual(activeItem, recommended);
333+
} else {
334+
assert(false, 'Not a function');
335+
}
336+
delete actualParameters!.activeItem;
337+
assert.deepStrictEqual(actualParameters, expectedParameters, 'Params not equal');
338+
});
339+
281340
test('Picker should be displayed with expected items if no interpreters are available', async () => {
282341
const state: InterpreterStateArgs = { path: 'some path', workspace: undefined };
283342
const multiStepInput = TypeMoq.Mock.ofType<IMultiStepInput<InterpreterStateArgs>>();
284343
const suggestions = [
285344
expectedEnterInterpreterPathSuggestion,
345+
{ kind: QuickPickItemKind.Separator, label: '' },
286346
defaultInterpreterPathSuggestion,
287347
noPythonInstalled,
288348
];
@@ -440,6 +500,7 @@ suite('Set Interpreter Command', () => {
440500
recommended.description = interpreterPath;
441501
const suggestions = [
442502
expectedEnterInterpreterPathSuggestion,
503+
{ kind: QuickPickItemKind.Separator, label: '' },
443504
defaultInterpreterPathSuggestion,
444505
{ kind: QuickPickItemKind.Separator, label: EnvGroups.Recommended },
445506
recommended,
@@ -556,6 +617,7 @@ suite('Set Interpreter Command', () => {
556617
recommended.description = interpreterPath;
557618
const suggestions = [
558619
expectedEnterInterpreterPathSuggestion,
620+
{ kind: QuickPickItemKind.Separator, label: '' },
559621
defaultInterpreterPathSuggestion,
560622
{ kind: QuickPickItemKind.Separator, label: EnvGroups.Recommended },
561623
recommended,
@@ -652,7 +714,13 @@ suite('Set Interpreter Command', () => {
652714
alwaysShow: true,
653715
};
654716

655-
const suggestions = [expectedEnterInterpreterPathSuggestion, defaultPathSuggestion, separator, recommended];
717+
const suggestions = [
718+
expectedEnterInterpreterPathSuggestion,
719+
{ kind: QuickPickItemKind.Separator, label: '' },
720+
defaultPathSuggestion,
721+
separator,
722+
recommended,
723+
];
656724
const expectedParameters: IQuickPickParameters<QuickPickItem> = {
657725
placeholder: `Selected Interpreter: ${currentPythonPath}`,
658726
items: suggestions,

0 commit comments

Comments
 (0)