Skip to content

Commit 2379238

Browse files
author
Kartik Raj
authored
Add button to clear list and refresh in interpreters quickpick (microsoft#19628)
* Change refresh icon when interpreter list is refreshing * Add tests * Minor tweaks * Fix situation if dialog box is cancelled * Fix tests * Improve ignoreErrors() typing * Update to not use custom svg, instead use built VSCode icon * Add vscode mock * Add button to clear interpreters list and refresh in quickpick
1 parent 93fd4e8 commit 2379238

5 files changed

Lines changed: 88 additions & 46 deletions

File tree

src/client/common/constants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,13 @@ export namespace Octicons {
7979
export const Lightbulb = '$(lightbulb)';
8080
}
8181

82+
/**
83+
* Look at https://code.visualstudio.com/api/references/icons-in-labels#icon-listing for ThemeIcon ids.
84+
* Using a theme icon is preferred over a custom icon as it gives product theme authors the possibility
85+
* to change the icons.
86+
*/
8287
export namespace ThemeIcons {
88+
export const ClearAll = 'clear-all';
8389
export const Refresh = 'refresh';
8490
export const SpinningLoader = 'loading~spin';
8591
}

src/client/common/utils/localize.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,10 @@ export namespace InterpreterQuickPickList {
317317
'InterpreterQuickPickList.refreshInterpreterList',
318318
'Refresh Interpreter list',
319319
);
320+
export const clearAllAndRefreshInterpreterList = localize(
321+
'InterpreterQuickPickList.clearAllAndRefreshInterpreterList',
322+
'Clear all and Refresh Interpreter list',
323+
);
320324
export const refreshingInterpreterList = localize(
321325
'InterpreterQuickPickList.refreshingInterpreterList',
322326
'Refreshing Interpreter list...',

src/client/common/utils/multiStepInput.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export interface IQuickPickParameters<T extends QuickPickItem, E = any> {
4848
items: T[];
4949
activeItem?: T;
5050
placeholder: string;
51-
customButtonSetup?: QuickInputButtonSetup;
51+
customButtonSetups?: QuickInputButtonSetup[];
5252
matchOnDescription?: boolean;
5353
matchOnDetail?: boolean;
5454
keepScrollPosition?: boolean;
@@ -86,7 +86,7 @@ export interface IMultiStepInput<S> {
8686
items,
8787
activeItem,
8888
placeholder,
89-
customButtonSetup,
89+
customButtonSetups,
9090
}: P): Promise<MultiStepInputQuickPicResponseType<T, P>>;
9191
showInputBox<P extends InputBoxParameters>({
9292
title,
@@ -117,7 +117,7 @@ export class MultiStepInput<S> implements IMultiStepInput<S> {
117117
items,
118118
activeItem,
119119
placeholder,
120-
customButtonSetup,
120+
customButtonSetups,
121121
matchOnDescription,
122122
matchOnDetail,
123123
acceptFilterBoxTextAsSelection,
@@ -145,15 +145,22 @@ export class MultiStepInput<S> implements IMultiStepInput<S> {
145145
input.activeItems = [];
146146
}
147147
input.buttons = this.steps.length > 1 ? [QuickInputButtons.Back] : [];
148-
if (customButtonSetup) {
149-
input.buttons = [...input.buttons, customButtonSetup.button];
148+
if (customButtonSetups) {
149+
for (const customButtonSetup of customButtonSetups) {
150+
input.buttons = [...input.buttons, customButtonSetup.button];
151+
}
150152
}
151153
disposables.push(
152154
input.onDidTriggerButton(async (item) => {
153155
if (item === QuickInputButtons.Back) {
154156
reject(InputFlowAction.back);
155-
} else if (JSON.stringify(item) === JSON.stringify(customButtonSetup?.button)) {
156-
await customButtonSetup?.callback(input);
157+
}
158+
if (customButtonSetups) {
159+
for (const customButtonSetup of customButtonSetups) {
160+
if (JSON.stringify(item) === JSON.stringify(customButtonSetup?.button)) {
161+
await customButtonSetup?.callback(input);
162+
}
163+
}
157164
}
158165
}),
159166
input.onDidChangeSelection((selectedItems) => resolve(selectedItems[0])),

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

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,16 @@ export class SetInterpreterCommand extends BaseInterpreterSelectorCommand {
7575
alwaysShow: true,
7676
};
7777

78+
private readonly refreshButton = {
79+
iconPath: new ThemeIcon(ThemeIcons.Refresh),
80+
tooltip: InterpreterQuickPickList.refreshInterpreterList,
81+
};
82+
83+
private readonly hardRefreshButton = {
84+
iconPath: new ThemeIcon(ThemeIcons.ClearAll),
85+
tooltip: InterpreterQuickPickList.clearAllAndRefreshInterpreterList,
86+
};
87+
7888
private readonly noPythonInstalled: ISpecialQuickPickItem = {
7989
label: `${Octicons.Error} ${InterpreterQuickPickList.noPythonInstalled}`,
8090
detail: InterpreterQuickPickList.clickForInstructions,
@@ -126,10 +136,6 @@ export class SetInterpreterCommand extends BaseInterpreterSelectorCommand {
126136
// times so that the visible items do not change.
127137
const preserveOrderWhenFiltering = !!this.interpreterService.refreshPromise;
128138
const suggestions = this._getItems(state.workspace);
129-
const refreshButton = {
130-
iconPath: new ThemeIcon(ThemeIcons.Refresh),
131-
tooltip: InterpreterQuickPickList.refreshInterpreterList,
132-
};
133139
state.path = undefined;
134140
const currentInterpreterPathDisplay = this.pathUtils.getDisplayName(
135141
this.configurationService.getSettings(state.workspace).pythonPath,
@@ -148,23 +154,20 @@ export class SetInterpreterCommand extends BaseInterpreterSelectorCommand {
148154
matchOnDetail: true,
149155
matchOnDescription: true,
150156
title: InterpreterQuickPickList.browsePath.openButtonLabel,
151-
customButtonSetup: {
152-
button: refreshButton,
153-
callback: (quickpickInput) => {
154-
quickpickInput.buttons = [
155-
{
156-
iconPath: new ThemeIcon(ThemeIcons.SpinningLoader),
157-
tooltip: InterpreterQuickPickList.refreshingInterpreterList,
158-
},
159-
];
160-
this.interpreterService
161-
.triggerRefresh()
162-
.finally(() => {
163-
quickpickInput.buttons = [refreshButton];
164-
})
165-
.ignoreErrors();
157+
customButtonSetups: [
158+
{
159+
button: this.hardRefreshButton,
160+
callback: (quickpickInput) => {
161+
this.refreshButtonCallback(quickpickInput, true);
162+
},
166163
},
167-
},
164+
{
165+
button: this.refreshButton,
166+
callback: (quickpickInput) => {
167+
this.refreshButtonCallback(quickpickInput, false);
168+
},
169+
},
170+
],
168171
initialize: () => {
169172
// Note discovery is no longer guranteed to be auto-triggered on extension load, so trigger it when
170173
// user interacts with the interpreter picker but only once per session. Users can rely on the
@@ -415,6 +418,21 @@ export class SetInterpreterCommand extends BaseInterpreterSelectorCommand {
415418
}
416419
}
417420

421+
private refreshButtonCallback(input: QuickPick<QuickPickItem>, clearCache: boolean) {
422+
input.buttons = [
423+
{
424+
iconPath: new ThemeIcon(ThemeIcons.SpinningLoader),
425+
tooltip: InterpreterQuickPickList.refreshingInterpreterList,
426+
},
427+
];
428+
this.interpreterService
429+
.triggerRefresh(undefined, { clearCache })
430+
.finally(() => {
431+
input.buttons = [this.hardRefreshButton, this.refreshButton];
432+
})
433+
.ignoreErrors();
434+
}
435+
418436
@captureTelemetry(EventName.SELECT_INTERPRETER_ENTER_BUTTON)
419437
public async _enterOrBrowseInterpreterPath(
420438
input: IMultiStepInput<InterpreterStateArgs>,

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

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
WorkspaceFolder,
1717
} from 'vscode';
1818
import { cloneDeep } from 'lodash';
19-
import { anything, instance, mock, verify, when } from 'ts-mockito';
19+
import { anything, instance, mock, when } from 'ts-mockito';
2020
import { IApplicationShell, ICommandManager, IWorkspaceService } from '../../../../client/common/application/types';
2121
import { PathUtils } from '../../../../client/common/platform/pathUtils';
2222
import { IPlatformService } from '../../../../client/common/platform/types';
@@ -261,10 +261,10 @@ suite('Set Interpreter Command', () => {
261261
await setInterpreterCommand._pickInterpreter(multiStepInput.object, state);
262262

263263
expect(actualParameters).to.not.equal(undefined, 'Parameters not set');
264-
const refreshButtonCallback = actualParameters!.customButtonSetup?.callback;
265-
expect(refreshButtonCallback).to.not.equal(undefined, 'Callback not set');
264+
const refreshButtons = actualParameters!.customButtonSetups;
265+
expect(refreshButtons).to.not.equal(undefined, 'Callback not set');
266266
delete actualParameters!.initialize;
267-
delete actualParameters!.customButtonSetup;
267+
delete actualParameters!.customButtonSetups;
268268
delete actualParameters!.onChangeItem;
269269
assert.deepStrictEqual(actualParameters, expectedParameters, 'Params not equal');
270270
});
@@ -302,10 +302,10 @@ suite('Set Interpreter Command', () => {
302302
await setInterpreterCommand._pickInterpreter(multiStepInput.object, state);
303303

304304
expect(actualParameters).to.not.equal(undefined, 'Parameters not set');
305-
const refreshButtonCallback = actualParameters!.customButtonSetup?.callback;
306-
expect(refreshButtonCallback).to.not.equal(undefined, 'Callback not set');
305+
const refreshButtons = actualParameters!.customButtonSetups;
306+
expect(refreshButtons).to.not.equal(undefined, 'Callback not set');
307307
delete actualParameters!.initialize;
308-
delete actualParameters!.customButtonSetup;
308+
delete actualParameters!.customButtonSetups;
309309
delete actualParameters!.onChangeItem;
310310
assert.deepStrictEqual(actualParameters, expectedParameters, 'Params not equal');
311311
});
@@ -458,10 +458,10 @@ suite('Set Interpreter Command', () => {
458458
await setInterpreterCommand._pickInterpreter(multiStepInput.object, state);
459459

460460
expect(actualParameters).to.not.equal(undefined, 'Parameters not set');
461-
const refreshButtonCallback = actualParameters!.customButtonSetup?.callback;
462-
expect(refreshButtonCallback).to.not.equal(undefined, 'Callback not set');
461+
const refreshButtons = actualParameters!.customButtonSetups;
462+
expect(refreshButtons).to.not.equal(undefined, 'Callback not set');
463463
delete actualParameters!.initialize;
464-
delete actualParameters!.customButtonSetup;
464+
delete actualParameters!.customButtonSetups;
465465
delete actualParameters!.onChangeItem;
466466
assert.deepStrictEqual(actualParameters?.items, expectedParameters.items, 'Params not equal');
467467
});
@@ -542,11 +542,11 @@ suite('Set Interpreter Command', () => {
542542
await setInterpreterCommand._pickInterpreter(multiStepInput.object, state);
543543

544544
expect(actualParameters).to.not.equal(undefined, 'Parameters not set');
545-
const refreshButtonCallback = actualParameters!.customButtonSetup?.callback;
546-
expect(refreshButtonCallback).to.not.equal(undefined, 'Callback not set');
545+
const refreshButtons = actualParameters!.customButtonSetups;
546+
expect(refreshButtons).to.not.equal(undefined, 'Callback not set');
547547

548548
delete actualParameters!.initialize;
549-
delete actualParameters!.customButtonSetup;
549+
delete actualParameters!.customButtonSetups;
550550
delete actualParameters!.onChangeItem;
551551

552552
assert.deepStrictEqual(actualParameters, expectedParameters, 'Params not equal');
@@ -566,12 +566,19 @@ suite('Set Interpreter Command', () => {
566566
await setInterpreterCommand._pickInterpreter(multiStepInput.object, state);
567567

568568
expect(actualParameters).to.not.equal(undefined, 'Parameters not set');
569-
const refreshButtonCallback = actualParameters!.customButtonSetup?.callback;
570-
expect(refreshButtonCallback).to.not.equal(undefined, 'Callback not set');
571-
572-
when(interpreterService.triggerRefresh()).thenResolve();
573-
await refreshButtonCallback!({} as QuickPick<QuickPickItem>); // Invoke callback, meaning that the refresh button is clicked.
574-
verify(interpreterService.triggerRefresh()).once();
569+
const refreshButtons = actualParameters!.customButtonSetups;
570+
expect(refreshButtons).to.not.equal(undefined, 'Callback not set');
571+
572+
expect(refreshButtons?.length).to.equal(2);
573+
let arg;
574+
when(interpreterService.triggerRefresh(undefined, anything())).thenCall((_, _arg) => {
575+
arg = _arg;
576+
return Promise.resolve();
577+
});
578+
await refreshButtons![0].callback!({} as QuickPick<QuickPickItem>); // Invoke callback, meaning that the refresh button is clicked.
579+
expect(arg).to.deep.equal({ clearCache: true });
580+
await refreshButtons![1].callback!({} as QuickPick<QuickPickItem>); // Invoke callback, meaning that the refresh button is clicked.
581+
expect(arg).to.deep.equal({ clearCache: false });
575582
});
576583

577584
test('Events to update quickpick updates the quickpick accordingly', async () => {

0 commit comments

Comments
 (0)