Skip to content

Commit 473de33

Browse files
author
Kartik Raj
authored
Ensure Python environment defined in python.defaultInterpreterPath is returned via discovery API (#22389)
Closes #22268 Even when it is not the active interpreter for any workspace, it's still a known environment which users could consider selecting.
1 parent 7aefb21 commit 473de33

File tree

6 files changed

+67
-9
lines changed

6 files changed

+67
-9
lines changed

src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ export class CustomVirtualEnvironmentLocator extends FSWatchingLocator {
9292
}
9393

9494
protected async initResources(): Promise<void> {
95-
this.disposables.push(onDidChangePythonSetting(VENVPATH_SETTING_KEY, () => this.emitter.fire({})));
96-
this.disposables.push(onDidChangePythonSetting(VENVFOLDERS_SETTING_KEY, () => this.emitter.fire({})));
95+
this.disposables.push(onDidChangePythonSetting(VENVPATH_SETTING_KEY, () => this.fire()));
96+
this.disposables.push(onDidChangePythonSetting(VENVFOLDERS_SETTING_KEY, () => this.fire()));
9797
}
9898

9999
// eslint-disable-next-line class-methods-use-this
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { PythonEnvKind } from '../../info';
5+
import { BasicEnvInfo, IPythonEnvsIterator } from '../../locator';
6+
import { FSWatchingLocator } from './fsWatchingLocator';
7+
import { getPythonSetting, onDidChangePythonSetting } from '../../../common/externalDependencies';
8+
import '../../../../common/extensions';
9+
import { traceVerbose } from '../../../../logging';
10+
import { DEFAULT_INTERPRETER_SETTING } from '../../../../common/constants';
11+
12+
export const DEFAULT_INTERPRETER_PATH_SETTING_KEY = 'defaultInterpreterPath';
13+
14+
/**
15+
* Finds and resolves custom virtual environments that users have provided.
16+
*/
17+
export class CustomWorkspaceLocator extends FSWatchingLocator {
18+
public readonly providerId: string = 'custom-workspace-locator';
19+
20+
constructor(private readonly root: string) {
21+
super(
22+
() => [],
23+
async () => PythonEnvKind.Unknown,
24+
);
25+
}
26+
27+
protected async initResources(): Promise<void> {
28+
this.disposables.push(
29+
onDidChangePythonSetting(DEFAULT_INTERPRETER_PATH_SETTING_KEY, () => this.fire(), this.root),
30+
);
31+
}
32+
33+
// eslint-disable-next-line class-methods-use-this
34+
protected doIterEnvs(): IPythonEnvsIterator<BasicEnvInfo> {
35+
const iterator = async function* (root: string) {
36+
traceVerbose('Searching for custom workspace envs');
37+
const filename = getPythonSetting<string>(DEFAULT_INTERPRETER_PATH_SETTING_KEY, root);
38+
if (!filename || filename === DEFAULT_INTERPRETER_SETTING) {
39+
// If the user has not set a custom interpreter, our job is done.
40+
return;
41+
}
42+
yield { kind: PythonEnvKind.Unknown, executablePath: filename };
43+
traceVerbose(`Finished searching for custom workspace envs`);
44+
};
45+
return iterator(this.root);
46+
}
47+
}

src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ export abstract class FSWatchingLocator extends LazyResourceBasedLocator {
128128
watchableRoots.forEach((root) => this.startWatchers(root));
129129
}
130130

131+
protected fire(args = {}): void {
132+
this.emitter.fire({ ...args, providerId: this.providerId });
133+
}
134+
131135
private startWatchers(root: string): void {
132136
const opts = this.creationOptions;
133137
if (isWatchingAFile(opts)) {

src/client/pythonEnvironments/common/externalDependencies.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,9 @@ export async function* getSubDirs(
175175
* Returns the value for setting `python.<name>`.
176176
* @param name The name of the setting.
177177
*/
178-
export function getPythonSetting<T>(name: string): T | undefined {
179-
const settings = internalServiceContainer.get<IConfigurationService>(IConfigurationService).getSettings();
178+
export function getPythonSetting<T>(name: string, root?: string): T | undefined {
179+
const resource = root ? vscode.Uri.file(root) : undefined;
180+
const settings = internalServiceContainer.get<IConfigurationService>(IConfigurationService).getSettings(resource);
180181
// eslint-disable-next-line @typescript-eslint/no-explicit-any
181182
return (settings as any)[name];
182183
}
@@ -186,9 +187,10 @@ export function getPythonSetting<T>(name: string): T | undefined {
186187
* @param name The name of the setting.
187188
* @param callback The listener function to be called when the setting changes.
188189
*/
189-
export function onDidChangePythonSetting(name: string, callback: () => void): IDisposable {
190+
export function onDidChangePythonSetting(name: string, callback: () => void, root?: string): IDisposable {
190191
return vscode.workspace.onDidChangeConfiguration((event: vscode.ConfigurationChangeEvent) => {
191-
if (event.affectsConfiguration(`python.${name}`)) {
192+
const scope = root ? vscode.Uri.file(root) : undefined;
193+
if (event.affectsConfiguration(`python.${name}`, scope)) {
192194
callback();
193195
}
194196
});

src/client/pythonEnvironments/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { EnvsCollectionService } from './base/locators/composite/envsCollectionS
3737
import { IDisposable } from '../common/types';
3838
import { traceError } from '../logging';
3939
import { ActiveStateLocator } from './base/locators/lowLevel/activeStateLocator';
40+
import { CustomWorkspaceLocator } from './base/locators/lowLevel/customWorkspaceLocator';
4041

4142
/**
4243
* Set up the Python environments component (during extension activation).'
@@ -182,7 +183,11 @@ function watchRoots(args: WatchRootsArgs): IDisposable {
182183

183184
function createWorkspaceLocator(ext: ExtensionState): WorkspaceLocators {
184185
const locators = new WorkspaceLocators(watchRoots, [
185-
(root: vscode.Uri) => [new WorkspaceVirtualEnvironmentLocator(root.fsPath), new PoetryLocator(root.fsPath)],
186+
(root: vscode.Uri) => [
187+
new WorkspaceVirtualEnvironmentLocator(root.fsPath),
188+
new PoetryLocator(root.fsPath),
189+
new CustomWorkspaceLocator(root.fsPath),
190+
],
186191
// Add an ILocator factory func here for each kind of workspace-rooted locator.
187192
]);
188193
ext.disposables.push(locators);

src/test/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.unit.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ suite('CustomVirtualEnvironment Locator', () => {
213213

214214
test('onChanged fires if venvPath setting changes', async () => {
215215
const events: PythonEnvsChangedEvent[] = [];
216-
const expected: PythonEnvsChangedEvent[] = [{}];
216+
const expected: PythonEnvsChangedEvent[] = [{ providerId: locator.providerId }];
217217
locator.onChanged((e) => events.push(e));
218218

219219
await getEnvs(locator.iterEnvs());
@@ -228,7 +228,7 @@ suite('CustomVirtualEnvironment Locator', () => {
228228

229229
test('onChanged fires if venvFolders setting changes', async () => {
230230
const events: PythonEnvsChangedEvent[] = [];
231-
const expected: PythonEnvsChangedEvent[] = [{}];
231+
const expected: PythonEnvsChangedEvent[] = [{ providerId: locator.providerId }];
232232
locator.onChanged((e) => events.push(e));
233233

234234
await getEnvs(locator.iterEnvs());

0 commit comments

Comments
 (0)