diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts index 57ae9187cdc2..e4daeee640c9 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts @@ -92,8 +92,8 @@ export class CustomVirtualEnvironmentLocator extends FSWatchingLocator { } protected async initResources(): Promise { - this.disposables.push(onDidChangePythonSetting(VENVPATH_SETTING_KEY, () => this.emitter.fire({}))); - this.disposables.push(onDidChangePythonSetting(VENVFOLDERS_SETTING_KEY, () => this.emitter.fire({}))); + this.disposables.push(onDidChangePythonSetting(VENVPATH_SETTING_KEY, () => this.fire())); + this.disposables.push(onDidChangePythonSetting(VENVFOLDERS_SETTING_KEY, () => this.fire())); } // eslint-disable-next-line class-methods-use-this diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/customWorkspaceLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/customWorkspaceLocator.ts new file mode 100644 index 000000000000..8a2b857d496a --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/lowLevel/customWorkspaceLocator.ts @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { PythonEnvKind } from '../../info'; +import { BasicEnvInfo, IPythonEnvsIterator } from '../../locator'; +import { FSWatchingLocator } from './fsWatchingLocator'; +import { getPythonSetting, onDidChangePythonSetting } from '../../../common/externalDependencies'; +import '../../../../common/extensions'; +import { traceVerbose } from '../../../../logging'; +import { DEFAULT_INTERPRETER_SETTING } from '../../../../common/constants'; + +export const DEFAULT_INTERPRETER_PATH_SETTING_KEY = 'defaultInterpreterPath'; + +/** + * Finds and resolves custom virtual environments that users have provided. + */ +export class CustomWorkspaceLocator extends FSWatchingLocator { + public readonly providerId: string = 'custom-workspace-locator'; + + constructor(private readonly root: string) { + super( + () => [], + async () => PythonEnvKind.Unknown, + ); + } + + protected async initResources(): Promise { + this.disposables.push( + onDidChangePythonSetting(DEFAULT_INTERPRETER_PATH_SETTING_KEY, () => this.fire(), this.root), + ); + } + + // eslint-disable-next-line class-methods-use-this + protected doIterEnvs(): IPythonEnvsIterator { + const iterator = async function* (root: string) { + traceVerbose('Searching for custom workspace envs'); + const filename = getPythonSetting(DEFAULT_INTERPRETER_PATH_SETTING_KEY, root); + if (!filename || filename === DEFAULT_INTERPRETER_SETTING) { + // If the user has not set a custom interpreter, our job is done. + return; + } + yield { kind: PythonEnvKind.Unknown, executablePath: filename }; + traceVerbose(`Finished searching for custom workspace envs`); + }; + return iterator(this.root); + } +} diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts index 0eb1d125200c..7565913f0a72 100644 --- a/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts +++ b/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts @@ -128,6 +128,10 @@ export abstract class FSWatchingLocator extends LazyResourceBasedLocator { watchableRoots.forEach((root) => this.startWatchers(root)); } + protected fire(args = {}): void { + this.emitter.fire({ ...args, providerId: this.providerId }); + } + private startWatchers(root: string): void { const opts = this.creationOptions; if (isWatchingAFile(opts)) { diff --git a/src/client/pythonEnvironments/common/externalDependencies.ts b/src/client/pythonEnvironments/common/externalDependencies.ts index ecb6f2212aba..b64d47f42269 100644 --- a/src/client/pythonEnvironments/common/externalDependencies.ts +++ b/src/client/pythonEnvironments/common/externalDependencies.ts @@ -175,8 +175,9 @@ export async function* getSubDirs( * Returns the value for setting `python.`. * @param name The name of the setting. */ -export function getPythonSetting(name: string): T | undefined { - const settings = internalServiceContainer.get(IConfigurationService).getSettings(); +export function getPythonSetting(name: string, root?: string): T | undefined { + const resource = root ? vscode.Uri.file(root) : undefined; + const settings = internalServiceContainer.get(IConfigurationService).getSettings(resource); // eslint-disable-next-line @typescript-eslint/no-explicit-any return (settings as any)[name]; } @@ -186,9 +187,10 @@ export function getPythonSetting(name: string): T | undefined { * @param name The name of the setting. * @param callback The listener function to be called when the setting changes. */ -export function onDidChangePythonSetting(name: string, callback: () => void): IDisposable { +export function onDidChangePythonSetting(name: string, callback: () => void, root?: string): IDisposable { return vscode.workspace.onDidChangeConfiguration((event: vscode.ConfigurationChangeEvent) => { - if (event.affectsConfiguration(`python.${name}`)) { + const scope = root ? vscode.Uri.file(root) : undefined; + if (event.affectsConfiguration(`python.${name}`, scope)) { callback(); } }); diff --git a/src/client/pythonEnvironments/index.ts b/src/client/pythonEnvironments/index.ts index 8065811a8a62..5a5fceffa693 100644 --- a/src/client/pythonEnvironments/index.ts +++ b/src/client/pythonEnvironments/index.ts @@ -37,6 +37,7 @@ import { EnvsCollectionService } from './base/locators/composite/envsCollectionS import { IDisposable } from '../common/types'; import { traceError } from '../logging'; import { ActiveStateLocator } from './base/locators/lowLevel/activeStateLocator'; +import { CustomWorkspaceLocator } from './base/locators/lowLevel/customWorkspaceLocator'; /** * Set up the Python environments component (during extension activation).' @@ -182,7 +183,11 @@ function watchRoots(args: WatchRootsArgs): IDisposable { function createWorkspaceLocator(ext: ExtensionState): WorkspaceLocators { const locators = new WorkspaceLocators(watchRoots, [ - (root: vscode.Uri) => [new WorkspaceVirtualEnvironmentLocator(root.fsPath), new PoetryLocator(root.fsPath)], + (root: vscode.Uri) => [ + new WorkspaceVirtualEnvironmentLocator(root.fsPath), + new PoetryLocator(root.fsPath), + new CustomWorkspaceLocator(root.fsPath), + ], // Add an ILocator factory func here for each kind of workspace-rooted locator. ]); ext.disposables.push(locators); diff --git a/src/test/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.unit.test.ts b/src/test/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.unit.test.ts index b1925e284426..43effcfa4538 100644 --- a/src/test/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.unit.test.ts +++ b/src/test/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.unit.test.ts @@ -213,7 +213,7 @@ suite('CustomVirtualEnvironment Locator', () => { test('onChanged fires if venvPath setting changes', async () => { const events: PythonEnvsChangedEvent[] = []; - const expected: PythonEnvsChangedEvent[] = [{}]; + const expected: PythonEnvsChangedEvent[] = [{ providerId: locator.providerId }]; locator.onChanged((e) => events.push(e)); await getEnvs(locator.iterEnvs()); @@ -228,7 +228,7 @@ suite('CustomVirtualEnvironment Locator', () => { test('onChanged fires if venvFolders setting changes', async () => { const events: PythonEnvsChangedEvent[] = []; - const expected: PythonEnvsChangedEvent[] = [{}]; + const expected: PythonEnvsChangedEvent[] = [{ providerId: locator.providerId }]; locator.onChanged((e) => events.push(e)); await getEnvs(locator.iterEnvs());