diff --git a/src/extensionApi.ts b/src/extensionApi.ts index 337322ed..61f0b075 100644 --- a/src/extensionApi.ts +++ b/src/extensionApi.ts @@ -280,6 +280,10 @@ export class PythonEnvironmentApiImpl implements PythonEnvironmentApi { } return this.envManagers.getLastKnownEnvironment(currentScope); } + getEnvironmentSync(scope: GetEnvironmentScope): PythonEnvironment | undefined { + const currentScope = checkUri(scope) as GetEnvironmentScope; + return this.envManagers.getLastKnownEnvironment(currentScope); + } onDidChangeEnvironment: Event = this._onDidChangeEnvironment.event; async resolveEnvironment(context: ResolveEnvironmentContext): Promise { await waitForAllEnvManagers(); diff --git a/src/test/extensionApi.unit.test.ts b/src/test/extensionApi.unit.test.ts index 62459fbc..ba6e536d 100644 --- a/src/test/extensionApi.unit.test.ts +++ b/src/test/extensionApi.unit.test.ts @@ -241,3 +241,67 @@ suite('PythonEnvironmentApiImpl - package resolution', () => { assert.ok(manage.calledOnceWithExactly(environment, { install: ['example'] })); }); }); + +suite('PythonEnvironmentApiImpl - getEnvironmentSync', () => { + type ApiArgs = ConstructorParameters; + const workspaceUri = Uri.joinPath(Uri.file(process.cwd()), 'workspace'); + const environmentUri = Uri.joinPath(workspaceUri, '.venv'); + const scope = Uri.joinPath(workspaceUri, 'script.py'); + const lastKnown = { + envId: { id: 'selected', managerId: 'ms-python.python:venv' }, + name: 'selected', + displayName: 'selected', + displayPath: environmentUri.fsPath, + version: '3.12.0', + environmentPath: environmentUri, + execInfo: { run: { executable: Uri.joinPath(environmentUri, 'python').fsPath, args: [] } }, + sysPrefix: environmentUri.fsPath, + } as PythonEnvironment; + + function createApi(environment: PythonEnvironment | undefined): { + api: PythonEnvironmentApiImpl; + getLastKnownEnvironment: sinon.SinonStub; + } { + const getLastKnownEnvironment = sinon.stub().returns(environment); + const api = new PythonEnvironmentApiImpl( + { + onDidChangeActiveEnvironment: new EventEmitter().event, + onDidChangePackageProviderPackages: new EventEmitter().event, + getLastKnownEnvironment, + } as unknown as ApiArgs[0], + { + getProjects: () => [], + onDidChangeProjects: new EventEmitter().event, + } as unknown as ApiArgs[1], + {} as unknown as ApiArgs[2], + {} as unknown as ApiArgs[3], + { onDidChangeEnvironmentVariables: new EventEmitter().event } as unknown as ApiArgs[4], + ); + return { api, getLastKnownEnvironment }; + } + + teardown(() => { + sinon.restore(); + }); + + test('returns the last-known environment without waiting for manager readiness', () => { + const { api, getLastKnownEnvironment } = createApi(lastKnown); + + assert.strictEqual(api.getEnvironmentSync(scope), lastKnown); + sinon.assert.calledOnceWithExactly(getLastKnownEnvironment, scope); + }); + + test('returns the last-known global environment', () => { + const { api, getLastKnownEnvironment } = createApi(lastKnown); + + assert.strictEqual(api.getEnvironmentSync(undefined), lastKnown); + sinon.assert.calledOnceWithExactly(getLastKnownEnvironment, undefined); + }); + + test('returns undefined when no environment has been resolved', () => { + const { api, getLastKnownEnvironment } = createApi(undefined); + + assert.strictEqual(api.getEnvironmentSync(scope), undefined); + sinon.assert.calledOnceWithExactly(getLastKnownEnvironment, scope); + }); +}); diff --git a/src/types.ts b/src/types.ts index 8699ce61..e5373b06 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1127,6 +1127,14 @@ export interface PythonProjectEnvironmentApi { */ getEnvironment(scope: GetEnvironmentScope): Promise; + /** + * Synchronously retrieves the last-known Python environment within the specified scope. + * This does not trigger environment discovery or refresh. + * @param scope - The scope within which to retrieve the environment. + * @returns The last-known Python environment, or undefined if none has been resolved. + */ + getEnvironmentSync(scope: GetEnvironmentScope): PythonEnvironment | undefined; + /** * Event that is fired when the selected Python environment changes for Project, Folder or File. * @see {@link DidChangeEnvironmentEventArgs}