diff --git a/src/client/pythonEnvironments/base/locators/common/pythonWatcher.ts b/src/client/pythonEnvironments/base/locators/common/pythonWatcher.ts index 0bdad132f7cbc..bf35780ff2492 100644 --- a/src/client/pythonEnvironments/base/locators/common/pythonWatcher.ts +++ b/src/client/pythonEnvironments/base/locators/common/pythonWatcher.ts @@ -10,6 +10,7 @@ import { FileChangeType } from '../../../../common/platform/fileSystemWatcher'; export interface PythonWorkspaceEnvEvent { type: FileChangeType; workspaceFolder: WorkspaceFolder; + executable: string; } export interface PythonGlobalEnvEvent { @@ -94,7 +95,7 @@ class PythonWatcherImpl implements PythonWatcher { private fireWorkspaceEvent(type: FileChangeType, wf: WorkspaceFolder, uri: Uri) { const uriWorkspace = getWorkspaceFolder(uri); if (uriWorkspace && arePathsSame(uriWorkspace.uri.fsPath, wf.uri.fsPath)) { - this._onDidWorkspaceEnvChanged.fire({ type, workspaceFolder: wf }); + this._onDidWorkspaceEnvChanged.fire({ type, workspaceFolder: wf, executable: uri.fsPath }); } } diff --git a/src/client/pythonEnvironments/nativeAPI.ts b/src/client/pythonEnvironments/nativeAPI.ts index a3eaae8ec9c93..66e2a57489c80 100644 --- a/src/client/pythonEnvironments/nativeAPI.ts +++ b/src/client/pythonEnvironments/nativeAPI.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import * as path from 'path'; -import { Disposable, Event, EventEmitter, Uri } from 'vscode'; +import { Disposable, Event, EventEmitter, Uri, WorkspaceFolder, WorkspaceFoldersChangeEvent } from 'vscode'; import { PythonEnvInfo, PythonEnvKind, PythonEnvType, PythonVersion } from './base/info'; import { GetRefreshEnvironmentsOptions, @@ -13,23 +13,17 @@ import { TriggerRefreshOptions, } from './base/locator'; import { PythonEnvCollectionChangedEvent } from './base/watcher'; -import { - isNativeEnvInfo, - NativeEnvInfo, - NativeEnvManagerInfo, - NativePythonFinder, -} from './base/locators/common/nativePythonFinder'; +import { isNativeEnvInfo, NativeEnvInfo, NativePythonFinder } from './base/locators/common/nativePythonFinder'; import { createDeferred, Deferred } from '../common/utils/async'; import { Architecture } from '../common/utils/platform'; import { parseVersion } from './base/info/pythonVersion'; import { cache } from '../common/utils/decorators'; -import { traceError, traceLog, traceWarn } from '../logging'; +import { traceError, traceLog } from '../logging'; import { StopWatch } from '../common/utils/stopWatch'; import { FileChangeType } from '../common/platform/fileSystemWatcher'; import { categoryToKind } from './base/locators/common/nativePythonUtils'; -import { createPythonWatcher } from './base/locators/common/pythonWatcher'; -import { Conda } from './common/environmentManagers/conda'; -import { setPyEnvBinary } from './common/environmentManagers/pyenv'; +import { createPythonWatcher, PythonWorkspaceEnvEvent } from './base/locators/common/pythonWatcher'; +import { getWorkspaceFolders, onDidChangeWorkspaceFolders } from '../common/vscodeApis/workspaceApis'; function makeExecutablePath(prefix?: string): string { if (!prefix) { @@ -251,10 +245,44 @@ class NativePythonEnvironments implements IDiscoveryAPI, Disposable { setImmediate(async () => { try { for await (const native of this.finder.refresh()) { - if (isNativeEnvInfo(native)) { - this.processEnv(native); - } else { - this.processEnvManager(native); + if (!isNativeEnvInfo(native) || !validEnv(native)) { + // eslint-disable-next-line no-continue + continue; + } + try { + const envPath = native.executable ?? native.prefix; + const version = native.version ? parseVersion(native.version) : undefined; + + if (categoryToKind(native.kind) === PythonEnvKind.Conda && !native.executable) { + // This is a conda env without python, no point trying to resolve this. + // There is nothing to resolve + this.addEnv(native); + } else if ( + envPath && + (!version || version.major < 0 || version.minor < 0 || version.micro < 0) + ) { + // We have a path, but no version info, try to resolve the environment. + this.finder + .resolve(envPath) + .then((env) => { + if (env) { + this.addEnv(env); + } + }) + .ignoreErrors(); + } else if ( + envPath && + version && + version.major >= 0 && + version.minor >= 0 && + version.micro >= 0 + ) { + this.addEnv(native); + } else { + traceError(`Failed to process environment: ${JSON.stringify(native)}`); + } + } catch (err) { + traceError(`Failed to process environment: ${err}`); } } this._refreshPromise?.resolve(); @@ -271,62 +299,11 @@ class NativePythonEnvironments implements IDiscoveryAPI, Disposable { return this._refreshPromise?.promise; } - private processEnv(native: NativeEnvInfo): void { - if (!validEnv(native)) { - return; - } - - try { - const envPath = native.executable ?? native.prefix; - const version = native.version ? parseVersion(native.version) : undefined; - - if (categoryToKind(native.kind) === PythonEnvKind.Conda && !native.executable) { - // This is a conda env without python, no point trying to resolve this. - // There is nothing to resolve - this.addEnv(native); - } else if (envPath && (!version || version.major < 0 || version.minor < 0 || version.micro < 0)) { - // We have a path, but no version info, try to resolve the environment. - this.finder - .resolve(envPath) - .then((env) => { - if (env) { - this.addEnv(env); - } - }) - .ignoreErrors(); - } else if (envPath && version && version.major >= 0 && version.minor >= 0 && version.micro >= 0) { - this.addEnv(native); - } else { - traceError(`Failed to process environment: ${JSON.stringify(native)}`); - } - } catch (err) { - traceError(`Failed to process environment: ${err}`); - } - } - - // eslint-disable-next-line class-methods-use-this - private processEnvManager(native: NativeEnvManagerInfo) { - const tool = native.tool.toLowerCase(); - switch (tool) { - case 'conda': - traceLog(`Conda environment manager found at: ${native.executable}`); - Conda.setConda(native.executable); - break; - case 'pyenv': - traceLog(`Pyenv environment manager found at: ${native.executable}`); - setPyEnvBinary(native.executable); - break; - default: - traceWarn(`Unknown environment manager: ${native.tool}`); - break; - } - } - getEnvs(_query?: PythonLocatorQuery): PythonEnvInfo[] { return this._envs; } - addEnv(native: NativeEnvInfo): void { + private addEnv(native: NativeEnvInfo): PythonEnvInfo | undefined { const info = toPythonEnvInfo(native); if (!info) { return; @@ -340,6 +317,19 @@ class NativePythonEnvironments implements IDiscoveryAPI, Disposable { this._envs.push(info); this._onChanged.fire({ type: FileChangeType.Created, new: info }); } + + return info; + } + + private removeEnv(env: PythonEnvInfo | string): void { + if (typeof env === 'string') { + const old = this._envs.find((item) => item.executable.filename === env); + this._envs = this._envs.filter((item) => item.executable.filename !== env); + this._onChanged.fire({ type: FileChangeType.Deleted, old }); + return; + } + this._envs = this._envs.filter((item) => item.executable.filename !== env.executable.filename); + this._onChanged.fire({ type: FileChangeType.Deleted, old: env }); } @cache(30_000, true) @@ -349,17 +339,7 @@ class NativePythonEnvironments implements IDiscoveryAPI, Disposable { } const native = await this.finder.resolve(envPath); if (native) { - const env = toPythonEnvInfo(native); - if (env) { - const old = this._envs.find((item) => item.executable.filename === env.executable.filename); - if (old) { - this._envs = this._envs.filter((item) => item.executable.filename !== env.executable.filename); - this._envs.push(env); - this._onChanged.fire({ type: FileChangeType.Changed, old, new: env }); - } - } - - return env; + return this.addEnv(native); } return undefined; } @@ -367,19 +347,30 @@ class NativePythonEnvironments implements IDiscoveryAPI, Disposable { private initializeWatcher(): void { const watcher = createPythonWatcher(); this._disposables.push( - watcher, watcher.onDidGlobalEnvChanged((e) => this.pathEventHandler(e.type, e.uri)), + watcher.onDidWorkspaceEnvChanged(async (e) => { + await this.workspaceEventHandler(e); + }), + onDidChangeWorkspaceFolders((e: WorkspaceFoldersChangeEvent) => { + e.removed.forEach((wf) => watcher.unwatchWorkspace(wf)); + e.added.forEach((wf) => watcher.watchWorkspace(wf)); + }), + watcher, ); + + getWorkspaceFolders()?.forEach((wf) => watcher.watchWorkspace(wf)); } private pathEventHandler(e: FileChangeType, uri: Uri): void {} - private workspaceEventHandler(e: FileChangeType, uri: string): void { - if (e === FileChangeType.Deleted) { - this._envs = this._envs.filter((item) => item.location !== uri); - this._onChanged.fire({ type: FileChangeType.Deleted, old: undefined, new: undefined }); + private async workspaceEventHandler(e: PythonWorkspaceEnvEvent): Promise { + if (e.type === FileChangeType.Created || e. === FileChangeType.Changed) { + const native = await this.finder.resolve(executable); + if (native) { + this.addEnv(native); + } } else { - this.triggerRefresh().ignoreErrors(); + this.removeEnv(executable); } } }