Skip to content

Commit

Permalink
Add a refresh icon next to interpreter list (microsoft#15868)
Browse files Browse the repository at this point in the history
* Add a refresh icon next to interpreter list

* News entry

* Add tests

* Add doc comment

* Code reviews

* Merge GetInterpreterOptions and GetInterpreterLocatorOptions
  • Loading branch information
Kartik Raj authored Apr 7, 2021
1 parent fd2beb6 commit 8a04438
Show file tree
Hide file tree
Showing 15 changed files with 129 additions and 45 deletions.
1 change: 1 addition & 0 deletions news/1 Enhancements/15868.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a refresh icon next to interpreter list.
1 change: 1 addition & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"InterpreterQuickPickList.enterPath.placeholder": "Enter path to a Python interpreter.",
"InterpreterQuickPickList.findPath.detail": "Browse the file system to find a Python interpreter.",
"InterpreterQuickPickList.findPath.label": "I can't find the interpreter I want to select...",
"InterpreterQuickPickList.refreshInterpreterList": "Refresh Interpreter list",
"InterpreterQuickPickList.browsePath.label": "Find...",
"InterpreterQuickPickList.browsePath.detail": "Browse your file system to find a Python interpreter.",
"InterpreterQuickPickList.browsePath.title": "Select Python interpreter",
Expand Down
4 changes: 4 additions & 0 deletions src/client/common/utils/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,10 @@ export namespace InterpreterQuickPickList {
openButtonLabel: localize('python.command.python.setInterpreter.title', 'Select Interpreter'),
title: localize('InterpreterQuickPickList.browsePath.title', 'Select Python interpreter'),
};
export const refreshInterpreterList = localize(
'InterpreterQuickPickList.refreshInterpreterList',
'Refresh Interpreter list',
);
}
export namespace ExtensionChannels {
export const yesWeekly = localize('ExtensionChannels.yesWeekly', 'Yes, weekly');
Expand Down
29 changes: 23 additions & 6 deletions src/client/common/utils/multiStepInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
'use strict';

import { inject, injectable } from 'inversify';
import { Disposable, QuickInput, QuickInputButton, QuickInputButtons, QuickPickItem } from 'vscode';
import { Disposable, QuickInput, QuickInputButton, QuickInputButtons, QuickPick, QuickPickItem } from 'vscode';
import { IApplicationShell } from '../application/types';

// Borrowed from https://github.com/Microsoft/vscode-extension-samples/blob/master/quickinput-sample/src/multiStepInput.ts
Expand All @@ -27,6 +27,18 @@ class InputFlowAction {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type InputStep<T extends any> = (input: MultiStepInput<T>, state: T) => Promise<InputStep<T> | void>;

type buttonCallbackType<T extends QuickPickItem> = (quickPick: QuickPick<T>) => Promise<void>;

type QuickInputButtonSetup = {
/**
* Button for an action in a QuickPick.
*/
button: QuickInputButton;
/**
* Callback to be invoked when button is clicked.
*/
callback: buttonCallbackType<QuickPickItem>;
};
export interface IQuickPickParameters<T extends QuickPickItem> {
title?: string;
step?: number;
Expand All @@ -35,7 +47,7 @@ export interface IQuickPickParameters<T extends QuickPickItem> {
items: T[];
activeItem?: T;
placeholder: string;
buttons?: QuickInputButton[];
customButtonSetup?: QuickInputButtonSetup;
matchOnDescription?: boolean;
matchOnDetail?: boolean;
acceptFilterBoxTextAsSelection?: boolean;
Expand Down Expand Up @@ -63,7 +75,7 @@ export interface IMultiStepInput<S> {
items,
activeItem,
placeholder,
buttons,
customButtonSetup,
}: P): Promise<MultiStepInputQuickPicResponseType<T, P>>;
showInputBox<P extends InputBoxParameters>({
title,
Expand Down Expand Up @@ -94,7 +106,7 @@ export class MultiStepInput<S> implements IMultiStepInput<S> {
items,
activeItem,
placeholder,
buttons,
customButtonSetup,
matchOnDescription,
matchOnDetail,
acceptFilterBoxTextAsSelection,
Expand All @@ -116,11 +128,16 @@ export class MultiStepInput<S> implements IMultiStepInput<S> {
} else {
input.activeItems = [];
}
input.buttons = [...(this.steps.length > 1 ? [QuickInputButtons.Back] : []), ...(buttons || [])];
input.buttons = this.steps.length > 1 ? [QuickInputButtons.Back] : [];
if (customButtonSetup) {
input.buttons = [...input.buttons, customButtonSetup.button];
}
disposables.push(
input.onDidTriggerButton((item) => {
input.onDidTriggerButton(async (item) => {
if (item === QuickInputButtons.Back) {
reject(InputFlowAction.back);
} else if (item === customButtonSetup?.button) {
await customButtonSetup.callback(input);
} else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
resolve(item as any);
Expand Down
2 changes: 2 additions & 0 deletions src/client/debugger/extension/attachQuickPick/picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ export class AttachPicker implements IAttachPicker {

quickPick.onDidTriggerButton(
async () => {
quickPick.busy = true;
const attachItems = await this.attachItemsProvider.getAttachItems();
quickPick.items = attachItems;
quickPick.busy = false;
},
this,
disposables,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import { Commands } from '../../../../common/constants';
import { FindInterpreterVariants } from '../../../../common/experiments/groups';
import { IPlatformService } from '../../../../common/platform/types';
import { IConfigurationService, IExperimentService, IPathUtils, Resource } from '../../../../common/types';
import { getIcon } from '../../../../common/utils/icons';
import { InterpreterQuickPickList } from '../../../../common/utils/localize';
import {
IMultiStepInput,
IMultiStepInputFactory,
InputStep,
IQuickPickParameters,
} from '../../../../common/utils/multiStepInput';
import { REFRESH_BUTTON_ICON } from '../../../../debugger/extension/attachQuickPick/types';
import { captureTelemetry, sendTelemetryEvent } from '../../../../telemetry';
import { EventName } from '../../../../telemetry/constants';
import {
Expand Down Expand Up @@ -60,7 +62,7 @@ export class SetInterpreterCommand extends BaseInterpreterSelectorCommand {
input: IMultiStepInput<InterpreterStateArgs>,
state: InterpreterStateArgs,
): Promise<void | InputStep<InterpreterStateArgs>> {
const interpreterSuggestions = await this.interpreterSelector.getSuggestions(state.workspace);
let interpreterSuggestions = await this.interpreterSelector.getSuggestions(state.workspace);

const inFindExperiment = await this.experiments.inExperiment(FindInterpreterVariants.findLast);
const manualEntrySuggestion: IFindInterpreterQuickPickItem = {
Expand All @@ -86,6 +88,10 @@ export class SetInterpreterCommand extends BaseInterpreterSelectorCommand {
);

state.path = undefined;
const refreshButton = {
iconPath: getIcon(REFRESH_BUTTON_ICON),
tooltip: InterpreterQuickPickList.refreshInterpreterList(),
};
const selection = await input.showQuickPick<
IInterpreterQuickPickItem | IFindInterpreterQuickPickItem,
IQuickPickParameters<IInterpreterQuickPickItem | IFindInterpreterQuickPickItem>
Expand All @@ -95,6 +101,20 @@ export class SetInterpreterCommand extends BaseInterpreterSelectorCommand {
activeItem: inFindExperiment ? suggestions[0] : suggestions[1],
matchOnDetail: true,
matchOnDescription: true,
customButtonSetup: {
button: refreshButton,
callback: async (quickPick) => {
quickPick.busy = true;
interpreterSuggestions = await this.interpreterSelector.getSuggestions(state.workspace, true);
if (inFindExperiment) {
quickPick.items = [...interpreterSuggestions, manualEntrySuggestion];
} else {
quickPick.items = [manualEntrySuggestion, ...interpreterSuggestions];
}
quickPick.busy = false;
},
},
title: InterpreterQuickPickList.browsePath.openButtonLabel(),
});

if (selection === undefined) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ export class InterpreterSelector implements IInterpreterSelector {
this.disposables.forEach((disposable) => disposable.dispose());
}

public async getSuggestions(resource: Resource) {
let interpreters = await this.interpreterManager.getInterpreters(resource, { onSuggestion: true });
public async getSuggestions(resource: Resource, ignoreCache?: boolean) {
let interpreters = await this.interpreterManager.getInterpreters(resource, { onSuggestion: true, ignoreCache });
if (this.experimentsManager.inExperiment(DeprecatePythonPath.experiment)) {
interpreters = interpreters
? interpreters.filter((item) => this.interpreterSecurityService.isSafe(item) !== false)
Expand Down
2 changes: 1 addition & 1 deletion src/client/interpreter/configuration/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface IPythonPathUpdaterServiceManager {

export const IInterpreterSelector = Symbol('IInterpreterSelector');
export interface IInterpreterSelector extends Disposable {
getSuggestions(resource: Resource): Promise<IInterpreterQuickPickItem[]>;
getSuggestions(resource: Resource, ignoreCache?: boolean): Promise<IInterpreterQuickPickItem[]>;
}

export interface IInterpreterQuickPickItem extends QuickPickItem {
Expand Down
8 changes: 2 additions & 6 deletions src/client/interpreter/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export interface IInterpreterLocatorService extends Disposable {
readonly onLocating: Event<Promise<PythonEnvironment[]>>;
readonly hasInterpreters: Promise<boolean>;
didTriggerInterpreterSuggestions?: boolean;
getInterpreters(resource?: Uri, options?: GetInterpreterLocatorOptions): Promise<PythonEnvironment[]>;
getInterpreters(resource?: Uri, options?: GetInterpreterOptions): Promise<PythonEnvironment[]>;
}

export const ICondaService = Symbol('ICondaService');
Expand Down Expand Up @@ -179,8 +179,4 @@ export type WorkspacePythonPath = {
configTarget: ConfigurationTarget.Workspace | ConfigurationTarget.WorkspaceFolder;
};

export type GetInterpreterOptions = {
onSuggestion?: boolean;
};

export type GetInterpreterLocatorOptions = GetInterpreterOptions & { ignoreCache?: boolean };
export type GetInterpreterOptions = { ignoreCache?: boolean; onSuggestion?: boolean };
6 changes: 3 additions & 3 deletions src/client/pythonEnvironments/discovery/locators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
CONDA_ENV_FILE_SERVICE,
CONDA_ENV_SERVICE,
CURRENT_PATH_SERVICE,
GetInterpreterLocatorOptions,
GetInterpreterOptions,
GLOBAL_VIRTUAL_ENV_SERVICE,
IInterpreterLocatorHelper,
IInterpreterLocatorService,
Expand Down Expand Up @@ -240,7 +240,7 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi
* interpreters.
*/
@traceDecorators.verbose('Get Interpreters')
public async getInterpreters(resource?: Uri, options?: GetInterpreterLocatorOptions): Promise<PythonEnvironment[]> {
public async getInterpreters(resource?: Uri, options?: GetInterpreterOptions): Promise<PythonEnvironment[]> {
const locators = this.getLocators(options);
const promises = locators.map(async (provider) => provider.getInterpreters(resource));
locators.forEach((locator) => {
Expand All @@ -266,7 +266,7 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi
*
* The locators are pulled from the registry.
*/
private getLocators(options?: GetInterpreterLocatorOptions): IInterpreterLocatorService[] {
private getLocators(options?: GetInterpreterOptions): IInterpreterLocatorService[] {
// The order of the services is important.
// The order is important because the data sources at the bottom of the list do not contain all,
// the information about the interpreters (e.g. type, environment name, etc).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { IDisposableRegistry, IPersistentState, IPersistentStateFactory } from '
import { createDeferred, Deferred } from '../../../../common/utils/async';
import { StopWatch } from '../../../../common/utils/stopWatch';
import {
GetInterpreterLocatorOptions,
GetInterpreterOptions,
IInterpreterLocatorService,
IInterpreterWatcher,
} from '../../../../interpreter/contracts';
Expand Down Expand Up @@ -109,7 +109,7 @@ export abstract class CacheableLocatorService implements IInterpreterLocatorServ
public abstract dispose(): void;

@traceDecorators.verbose('Get Interpreters in CacheableLocatorService')
public async getInterpreters(resource?: Uri, options?: GetInterpreterLocatorOptions): Promise<PythonEnvironment[]> {
public async getInterpreters(resource?: Uri, options?: GetInterpreterOptions): Promise<PythonEnvironment[]> {
const cacheKey = this.getCacheKey(resource);
let deferred = this.promisesPerResource.get(cacheKey);
if (!deferred || options?.ignoreCache) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { IFileSystem, IPlatformService } from '../../../../common/platform/types
import { IProcessServiceFactory } from '../../../../common/process/types';
import { IConfigurationService, ICurrentProcess } from '../../../../common/types';
import { StopWatch } from '../../../../common/utils/stopWatch';
import { GetInterpreterLocatorOptions, IInterpreterHelper, IPipEnvService } from '../../../../interpreter/contracts';
import { GetInterpreterOptions, IInterpreterHelper, IPipEnvService } from '../../../../interpreter/contracts';
import { IPipEnvServiceHelper } from '../../../../interpreter/locators/types';
import { IServiceContainer } from '../../../../ioc/types';
import { sendTelemetryEvent } from '../../../../telemetry';
Expand Down Expand Up @@ -65,7 +65,7 @@ export class PipEnvService extends CacheableLocatorService implements IPipEnvSer
return this.didTriggerInterpreterSuggestions ? this.configService.getSettings().pipenvPath : '';
}

public async getInterpreters(resource?: Uri, options?: GetInterpreterLocatorOptions): Promise<PythonEnvironment[]> {
public async getInterpreters(resource?: Uri, options?: GetInterpreterOptions): Promise<PythonEnvironment[]> {
if (!this.didTriggerInterpreterSuggestions) {
return [];
}
Expand Down
16 changes: 3 additions & 13 deletions src/client/pythonEnvironments/legacyIOC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
CONDA_ENV_FILE_SERVICE,
CONDA_ENV_SERVICE,
CURRENT_PATH_SERVICE,
GetInterpreterLocatorOptions,
GetInterpreterOptions,
GLOBAL_VIRTUAL_ENV_SERVICE,
IComponentAdapter,
ICondaService,
Expand Down Expand Up @@ -263,12 +263,7 @@ class ComponentAdapter implements IComponentAdapter, IExtensionSingleActivationS

public async getInterpreters(
resource?: vscode.Uri,
options?: GetInterpreterLocatorOptions,
// Currently we have no plans to support GetInterpreterLocatorOptions:
// {
// ignoreCache?: boolean
// onSuggestion?: boolean;
// }
options?: GetInterpreterOptions,
source?: PythonEnvSource[],
): Promise<PythonEnvironment[]> {
// Notify locators are locating.
Expand All @@ -287,12 +282,7 @@ class ComponentAdapter implements IComponentAdapter, IExtensionSingleActivationS

private async getInterpretersViaAPI(
resource?: vscode.Uri,
options?: GetInterpreterLocatorOptions,
// Currently we have no plans to support GetInterpreterLocatorOptions:
// {
// ignoreCache?: boolean
// onSuggestion?: boolean;
// }
options?: GetInterpreterOptions,
source?: PythonEnvSource[],
): Promise<PythonEnvironment[]> {
if (options?.onSuggestion && this.allowOnSuggestionRefresh) {
Expand Down
Loading

0 comments on commit 8a04438

Please sign in to comment.