Skip to content

Commit e9870c0

Browse files
author
Kartik Raj
authored
Improve select folder quickpick UI in multiroot workspaces (#18256)
* Add interpreter path text in select folder quickpick * Add an option to clear interpreters for all folders * Fix some tests * News * Localize * Fix tests * Add test
1 parent 001103d commit e9870c0

File tree

9 files changed

+221
-65
lines changed

9 files changed

+221
-65
lines changed

news/1 Enhancements/17693.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add an option to clear interpreter setting for all workspace folders in multiroot scenario.

package.nls.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"Pylance.pylanceRevertToJedi": "Revert to Jedi",
4747
"Experiments.inGroup": "Experiment '{0}' is active",
4848
"Experiments.optedOutOf": "Experiment '{0}' is inactive",
49+
"Interpreters.clearAtWorkspace" : "Clear at workspace level",
4950
"Interpreters.RefreshingInterpreters": "Refreshing Python Interpreters",
5051
"Interpreters.entireWorkspace": "Select at workspace level",
5152
"Interpreters.pythonInterpreterPath": "Python interpreter path: {0}",
@@ -61,6 +62,7 @@
6162
"InterpreterQuickPickList.browsePath.title": "Select Python interpreter",
6263
"InterpreterQuickPickList.defaultInterpreterPath.label": "Use Python from `python.defaultInterpreterPath` setting",
6364
"diagnostics.upgradeCodeRunner": "Please update the Code Runner extension for it to be compatible with the Python extension.",
65+
"Common.clearAll" : "Clear all",
6466
"Common.bannerLabelYes": "Yes",
6567
"Common.bannerLabelNo": "No",
6668
"Common.doNotShowAgain": "Do not show again",

src/client/common/utils/localize.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export namespace Common {
9191
export const and = localize('Common.and', 'and');
9292
export const reportThisIssue = localize('Common.reportThisIssue', 'Report this issue');
9393
export const recommended = localize('Common.recommended', 'Recommended');
94+
export const clearAll = localize('Common.clearAll', 'Clear all');
9495
}
9596

9697
export namespace CommonSurvey {
@@ -282,6 +283,7 @@ export namespace Interpreters {
282283
'We noticed a new virtual environment has been created. Do you want to select it for the workspace folder?',
283284
);
284285
export const entireWorkspace = localize('Interpreters.entireWorkspace', 'Select at workspace level');
286+
export const clearAtWorkspace = localize('Interpreters.clearAtWorkspace', 'Clear at workspace level');
285287
export const selectInterpreterTip = localize(
286288
'Interpreters.selectInterpreterTip',
287289
'Tip: you can change the Python interpreter used by the Python extension by clicking on the Python version in the status bar',

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

Lines changed: 69 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ import * as path from 'path';
88
import { ConfigurationTarget, Disposable, QuickPickItem, Uri } from 'vscode';
99
import { IExtensionSingleActivationService } from '../../../../activation/types';
1010
import { IApplicationShell, ICommandManager, IWorkspaceService } from '../../../../common/application/types';
11-
import { IDisposable, Resource } from '../../../../common/types';
12-
import { Interpreters } from '../../../../common/utils/localize';
11+
import { IConfigurationService, IDisposable, IPathUtils, Resource } from '../../../../common/types';
12+
import { Common, Interpreters } from '../../../../common/utils/localize';
1313
import { IPythonPathUpdaterServiceManager } from '../../types';
14-
14+
export interface WorkspaceSelectionQuickPickItem extends QuickPickItem {
15+
uri?: Uri;
16+
}
1517
@injectable()
1618
export abstract class BaseInterpreterSelectorCommand implements IExtensionSingleActivationService, IDisposable {
1719
public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true };
@@ -21,6 +23,8 @@ export abstract class BaseInterpreterSelectorCommand implements IExtensionSingle
2123
@unmanaged() protected readonly commandManager: ICommandManager,
2224
@unmanaged() protected readonly applicationShell: IApplicationShell,
2325
@unmanaged() protected readonly workspaceService: IWorkspaceService,
26+
@unmanaged() protected readonly pathUtils: IPathUtils,
27+
@unmanaged() protected readonly configurationService: IConfigurationService,
2428
) {
2529
this.disposables.push(this);
2630
}
@@ -31,52 +35,85 @@ export abstract class BaseInterpreterSelectorCommand implements IExtensionSingle
3135

3236
public abstract activate(): Promise<void>;
3337

34-
protected async getConfigTarget(): Promise<
38+
protected async getConfigTargets(options?: {
39+
resetTarget?: boolean;
40+
}): Promise<
3541
| {
3642
folderUri: Resource;
3743
configTarget: ConfigurationTarget;
38-
}
44+
}[]
3945
| undefined
4046
> {
41-
if (
42-
!Array.isArray(this.workspaceService.workspaceFolders) ||
43-
this.workspaceService.workspaceFolders.length === 0
44-
) {
45-
return {
46-
folderUri: undefined,
47-
configTarget: ConfigurationTarget.Global,
48-
};
47+
const workspaceFolders = this.workspaceService.workspaceFolders;
48+
if (workspaceFolders === undefined || workspaceFolders.length === 0) {
49+
return [
50+
{
51+
folderUri: undefined,
52+
configTarget: ConfigurationTarget.Global,
53+
},
54+
];
4955
}
50-
if (!this.workspaceService.workspaceFile && this.workspaceService.workspaceFolders.length === 1) {
51-
return {
52-
folderUri: this.workspaceService.workspaceFolders[0].uri,
53-
configTarget: ConfigurationTarget.WorkspaceFolder,
54-
};
56+
if (!this.workspaceService.workspaceFile && workspaceFolders.length === 1) {
57+
return [
58+
{
59+
folderUri: workspaceFolders[0].uri,
60+
configTarget: ConfigurationTarget.WorkspaceFolder,
61+
},
62+
];
5563
}
5664

5765
// Ok we have multiple workspaces, get the user to pick a folder.
5866

59-
type WorkspaceSelectionQuickPickItem = QuickPickItem & { uri: Uri };
60-
const quickPickItems: WorkspaceSelectionQuickPickItem[] = [
61-
...this.workspaceService.workspaceFolders.map((w) => ({
62-
label: w.name,
63-
description: path.dirname(w.uri.fsPath),
64-
uri: w.uri,
65-
})),
67+
let quickPickItems: WorkspaceSelectionQuickPickItem[] = options?.resetTarget
68+
? [
69+
{
70+
label: Common.clearAll(),
71+
},
72+
]
73+
: [];
74+
quickPickItems.push(
75+
...workspaceFolders.map((w) => {
76+
const selectedInterpreter = this.pathUtils.getDisplayName(
77+
this.configurationService.getSettings(w.uri).pythonPath,
78+
w.uri.fsPath,
79+
);
80+
return {
81+
label: w.name,
82+
description: this.pathUtils.getDisplayName(path.dirname(w.uri.fsPath)),
83+
uri: w.uri,
84+
detail: selectedInterpreter,
85+
};
86+
}),
6687
{
67-
label: Interpreters.entireWorkspace(),
68-
uri: this.workspaceService.workspaceFolders[0].uri,
88+
label: options?.resetTarget ? Interpreters.clearAtWorkspace() : Interpreters.entireWorkspace(),
89+
uri: workspaceFolders[0].uri,
6990
},
70-
];
91+
);
7192

7293
const selection = await this.applicationShell.showQuickPick(quickPickItems, {
73-
placeHolder: 'Select the workspace to set the interpreter',
94+
placeHolder: options?.resetTarget
95+
? 'Select the workspace folder to clear the interpreter for'
96+
: 'Select the workspace folder to set the interpreter',
7497
});
7598

99+
if (selection?.label === Common.clearAll()) {
100+
const folderTargets: {
101+
folderUri: Resource;
102+
configTarget: ConfigurationTarget;
103+
}[] = workspaceFolders.map((w) => ({
104+
folderUri: w.uri,
105+
configTarget: ConfigurationTarget.WorkspaceFolder,
106+
}));
107+
return [
108+
...folderTargets,
109+
{ folderUri: workspaceFolders[0].uri, configTarget: ConfigurationTarget.Workspace },
110+
];
111+
}
112+
76113
return selection
77-
? selection.label === Interpreters.entireWorkspace()
78-
? { folderUri: selection.uri, configTarget: ConfigurationTarget.Workspace }
79-
: { folderUri: selection.uri, configTarget: ConfigurationTarget.WorkspaceFolder }
114+
? selection.label === Interpreters.entireWorkspace() || selection.label === Interpreters.clearAtWorkspace()
115+
? [{ folderUri: selection.uri, configTarget: ConfigurationTarget.Workspace }]
116+
: [{ folderUri: selection.uri, configTarget: ConfigurationTarget.WorkspaceFolder }]
80117
: undefined;
81118
}
82119
}

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

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { inject, injectable } from 'inversify';
77
import { IApplicationShell, ICommandManager, IWorkspaceService } from '../../../../common/application/types';
88
import { Commands } from '../../../../common/constants';
9+
import { IConfigurationService, IPathUtils } from '../../../../common/types';
910
import { IPythonPathUpdaterServiceManager } from '../../types';
1011
import { BaseInterpreterSelectorCommand } from './base';
1112

@@ -16,8 +17,17 @@ export class ResetInterpreterCommand extends BaseInterpreterSelectorCommand {
1617
@inject(ICommandManager) commandManager: ICommandManager,
1718
@inject(IApplicationShell) applicationShell: IApplicationShell,
1819
@inject(IWorkspaceService) workspaceService: IWorkspaceService,
20+
@inject(IPathUtils) pathUtils: IPathUtils,
21+
@inject(IConfigurationService) configurationService: IConfigurationService,
1922
) {
20-
super(pythonPathUpdaterService, commandManager, applicationShell, workspaceService);
23+
super(
24+
pythonPathUpdaterService,
25+
commandManager,
26+
applicationShell,
27+
workspaceService,
28+
pathUtils,
29+
configurationService,
30+
);
2131
}
2232

2333
public async activate() {
@@ -27,13 +37,16 @@ export class ResetInterpreterCommand extends BaseInterpreterSelectorCommand {
2737
}
2838

2939
public async resetInterpreter() {
30-
const targetConfig = await this.getConfigTarget();
31-
if (!targetConfig) {
40+
const targetConfigs = await this.getConfigTargets({ resetTarget: true });
41+
if (!targetConfigs) {
3242
return;
3343
}
34-
const configTarget = targetConfig.configTarget;
35-
const wkspace = targetConfig.folderUri;
36-
37-
await this.pythonPathUpdaterService.updatePythonPath(undefined, configTarget, 'ui', wkspace);
44+
await Promise.all(
45+
targetConfigs.map(async (targetConfig) => {
46+
const configTarget = targetConfig.configTarget;
47+
const wkspace = targetConfig.folderUri;
48+
await this.pythonPathUpdaterService.updatePythonPath(undefined, configTarget, 'ui', wkspace);
49+
}),
50+
);
3851
}
3952
}

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

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,18 +55,25 @@ export class SetInterpreterCommand extends BaseInterpreterSelectorCommand {
5555

5656
constructor(
5757
@inject(IApplicationShell) applicationShell: IApplicationShell,
58-
@inject(IPathUtils) private readonly pathUtils: IPathUtils,
58+
@inject(IPathUtils) pathUtils: IPathUtils,
5959
@inject(IPythonPathUpdaterServiceManager)
6060
pythonPathUpdaterService: IPythonPathUpdaterServiceManager,
61-
@inject(IConfigurationService) private readonly configurationService: IConfigurationService,
61+
@inject(IConfigurationService) configurationService: IConfigurationService,
6262
@inject(ICommandManager) commandManager: ICommandManager,
6363
@inject(IMultiStepInputFactory) private readonly multiStepFactory: IMultiStepInputFactory,
6464
@inject(IPlatformService) private readonly platformService: IPlatformService,
6565
@inject(IInterpreterSelector) private readonly interpreterSelector: IInterpreterSelector,
6666
@inject(IWorkspaceService) workspaceService: IWorkspaceService,
6767
@inject(IInterpreterService) private readonly interpreterService: IInterpreterService,
6868
) {
69-
super(pythonPathUpdaterService, commandManager, applicationShell, workspaceService);
69+
super(
70+
pythonPathUpdaterService,
71+
commandManager,
72+
applicationShell,
73+
workspaceService,
74+
pathUtils,
75+
configurationService,
76+
);
7077
}
7178

7279
public async activate(): Promise<void> {
@@ -302,13 +309,13 @@ export class SetInterpreterCommand extends BaseInterpreterSelectorCommand {
302309

303310
@captureTelemetry(EventName.SELECT_INTERPRETER)
304311
public async setInterpreter(): Promise<void> {
305-
const targetConfig = await this.getConfigTarget();
312+
const targetConfig = await this.getConfigTargets();
306313
if (!targetConfig) {
307314
return;
308315
}
309316

310-
const { configTarget } = targetConfig;
311-
const wkspace = targetConfig.folderUri;
317+
const { configTarget } = targetConfig[0];
318+
const wkspace = targetConfig[0].folderUri;
312319
const interpreterState: InterpreterStateArgs = { path: undefined, workspace: wkspace };
313320
const multiStep = this.multiStepFactory.create<InterpreterStateArgs>();
314321
await multiStep.run((input, s) => this._pickInterpreter(input, s), interpreterState);

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

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,25 @@
55

66
import { inject, injectable } from 'inversify';
77
import { ConfigurationTarget } from 'vscode';
8-
import {
9-
IApplicationShell,
10-
ICommandManager,
11-
IDocumentManager,
12-
IWorkspaceService,
13-
} from '../../../../common/application/types';
8+
import { IExtensionSingleActivationService } from '../../../../activation/types';
9+
import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../../../common/application/types';
1410
import { Commands } from '../../../../common/constants';
11+
import { IDisposableRegistry } from '../../../../common/types';
1512
import { IShebangCodeLensProvider } from '../../../contracts';
1613
import { IPythonPathUpdaterServiceManager } from '../../types';
17-
import { BaseInterpreterSelectorCommand } from './base';
1814

1915
@injectable()
20-
export class SetShebangInterpreterCommand extends BaseInterpreterSelectorCommand {
16+
export class SetShebangInterpreterCommand implements IExtensionSingleActivationService {
17+
public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true };
2118
constructor(
22-
@inject(IWorkspaceService) workspaceService: IWorkspaceService,
19+
@inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService,
2320
@inject(IDocumentManager) private readonly documentManager: IDocumentManager,
24-
@inject(IPythonPathUpdaterServiceManager) pythonPathUpdaterService: IPythonPathUpdaterServiceManager,
21+
@inject(IPythonPathUpdaterServiceManager)
22+
private readonly pythonPathUpdaterService: IPythonPathUpdaterServiceManager,
2523
@inject(IShebangCodeLensProvider) private readonly shebangCodeLensProvider: IShebangCodeLensProvider,
26-
@inject(ICommandManager) commandManager: ICommandManager,
27-
@inject(ICommandManager) applicationShell: IApplicationShell,
28-
) {
29-
super(pythonPathUpdaterService, commandManager, applicationShell, workspaceService);
30-
}
24+
@inject(ICommandManager) private readonly commandManager: ICommandManager,
25+
@inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry,
26+
) {}
3127

3228
public async activate() {
3329
this.disposables.push(

0 commit comments

Comments
 (0)