Skip to content

Commit 5cdbc60

Browse files
authored
fix: ensure interpreter change event is raised when using environments extension (#24838)
Fixes microsoft/pylance-release#6968
1 parent 60d0473 commit 5cdbc60

File tree

4 files changed

+74
-7
lines changed

4 files changed

+74
-7
lines changed

Diff for: src/client/envExt/api.internal.ts

+49-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
import { Terminal, Uri } from 'vscode';
4+
import { EventEmitter, Terminal, Uri, Disposable, ConfigurationTarget } from 'vscode';
55
import { getExtension } from '../common/vscodeApis/extensionsApi';
66
import {
77
GetEnvironmentScope,
@@ -10,8 +10,10 @@ import {
1010
PythonEnvironmentApi,
1111
PythonProcess,
1212
RefreshEnvironmentsScope,
13+
DidChangeEnvironmentEventArgs,
1314
} from './types';
1415
import { executeCommand } from '../common/vscodeApis/commandApis';
16+
import { IInterpreterPathService } from '../common/types';
1517

1618
export const ENVS_EXTENSION_ID = 'ms-python.vscode-python-envs';
1719

@@ -24,6 +26,17 @@ export function useEnvExtension(): boolean {
2426
return _useExt;
2527
}
2628

29+
const onDidChangeEnvironmentEnvExtEmitter: EventEmitter<DidChangeEnvironmentEventArgs> = new EventEmitter<
30+
DidChangeEnvironmentEventArgs
31+
>();
32+
export function onDidChangeEnvironmentEnvExt(
33+
listener: (e: DidChangeEnvironmentEventArgs) => unknown,
34+
thisArgs?: unknown,
35+
disposables?: Disposable[],
36+
): Disposable {
37+
return onDidChangeEnvironmentEnvExtEmitter.event(listener, thisArgs, disposables);
38+
}
39+
2740
let _extApi: PythonEnvironmentApi | undefined;
2841
export async function getEnvExtApi(): Promise<PythonEnvironmentApi> {
2942
if (_extApi) {
@@ -33,14 +46,15 @@ export async function getEnvExtApi(): Promise<PythonEnvironmentApi> {
3346
if (!extension) {
3447
throw new Error('Python Environments extension not found.');
3548
}
36-
if (extension?.isActive) {
37-
_extApi = extension.exports as PythonEnvironmentApi;
38-
return _extApi;
49+
if (!extension?.isActive) {
50+
await extension.activate();
3951
}
4052

41-
await extension.activate();
42-
4353
_extApi = extension.exports as PythonEnvironmentApi;
54+
_extApi.onDidChangeEnvironment((e) => {
55+
onDidChangeEnvironmentEnvExtEmitter.fire(e);
56+
});
57+
4458
return _extApi;
4559
}
4660

@@ -106,3 +120,32 @@ export async function clearCache(): Promise<void> {
106120
await executeCommand('python-envs.clearCache');
107121
}
108122
}
123+
124+
export function registerEnvExtFeatures(
125+
disposables: Disposable[],
126+
interpreterPathService: IInterpreterPathService,
127+
): void {
128+
if (useEnvExtension()) {
129+
disposables.push(
130+
onDidChangeEnvironmentEnvExt(async (e: DidChangeEnvironmentEventArgs) => {
131+
const previousPath = interpreterPathService.get(e.uri);
132+
133+
if (previousPath !== e.new?.environmentPath.fsPath) {
134+
if (e.uri) {
135+
await interpreterPathService.update(
136+
e.uri,
137+
ConfigurationTarget.WorkspaceFolder,
138+
e.new?.environmentPath.fsPath,
139+
);
140+
} else {
141+
await interpreterPathService.update(
142+
undefined,
143+
ConfigurationTarget.Global,
144+
e.new?.environmentPath.fsPath,
145+
);
146+
}
147+
}
148+
}),
149+
);
150+
}
151+
}

Diff for: src/client/environmentApi.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { PythonEnvInfo, PythonEnvKind, PythonEnvType } from './pythonEnvironment
1111
import { getEnvPath } from './pythonEnvironments/base/info/env';
1212
import { IDiscoveryAPI, ProgressReportStage } from './pythonEnvironments/base/locator';
1313
import { IPythonExecutionFactory } from './common/process/types';
14-
import { traceError, traceVerbose } from './logging';
14+
import { traceError, traceInfo, traceVerbose } from './logging';
1515
import { isParentPath, normCasePath } from './common/platform/fs-paths';
1616
import { sendTelemetryEvent } from './telemetry';
1717
import { EventName } from './telemetry/constants';
@@ -42,7 +42,13 @@ type ActiveEnvironmentChangeEvent = {
4242
};
4343

4444
const onDidActiveInterpreterChangedEvent = new EventEmitter<ActiveEnvironmentPathChangeEvent>();
45+
const previousEnvMap = new Map<string, string>();
4546
export function reportActiveInterpreterChanged(e: ActiveEnvironmentChangeEvent): void {
47+
const oldPath = previousEnvMap.get(e.resource?.uri.fsPath ?? '');
48+
if (oldPath === e.path) {
49+
return;
50+
}
51+
previousEnvMap.set(e.resource?.uri.fsPath ?? '', e.path);
4652
onDidActiveInterpreterChangedEvent.fire({ id: getEnvID(e.path), path: e.path, resource: e.resource });
4753
reportActiveInterpreterChangedDeprecated({ path: e.path, resource: e.resource?.uri });
4854
}
@@ -172,6 +178,13 @@ export function buildEnvironmentApi(
172178
}
173179

174180
disposables.push(
181+
onDidActiveInterpreterChangedEvent.event((e) => {
182+
let scope = 'global';
183+
if (e.resource) {
184+
scope = e.resource instanceof Uri ? e.resource.fsPath : e.resource.uri.fsPath;
185+
}
186+
traceInfo(`Active interpreter [${scope}]: `, e.path);
187+
}),
175188
discoveryApi.onProgress((e) => {
176189
if (e.stage === ProgressReportStage.discoveryFinished) {
177190
knownCache = initKnownCache();

Diff for: src/client/extensionActivation.ts

+2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import { registerTriggerForTerminalREPL } from './terminals/codeExecution/termin
5656
import { registerPythonStartup } from './terminals/pythonStartup';
5757
import { registerPixiFeatures } from './pythonEnvironments/common/environmentManagers/pixi';
5858
import { registerCustomTerminalLinkProvider } from './terminals/pythonStartupLinkProvider';
59+
import { registerEnvExtFeatures } from './envExt/api.internal';
5960

6061
export async function activateComponents(
6162
// `ext` is passed to any extra activation funcs.
@@ -101,6 +102,7 @@ export function activateFeatures(ext: ExtensionState, _components: Components):
101102
const interpreterService: IInterpreterService = ext.legacyIOC.serviceContainer.get<IInterpreterService>(
102103
IInterpreterService,
103104
);
105+
registerEnvExtFeatures(ext.disposables, interpreterPathService);
104106
const pathUtils = ext.legacyIOC.serviceContainer.get<IPathUtils>(IPathUtils);
105107
registerPixiFeatures(ext.disposables);
106108
registerAllCreateEnvironmentFeatures(

Diff for: src/test/common/persistentState.unit.test.ts

+9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import { assert, expect } from 'chai';
77
import * as TypeMoq from 'typemoq';
8+
import * as sinon from 'sinon';
89
import { Memento } from 'vscode';
910
import { ICommandManager } from '../../client/common/application/types';
1011
import { Commands } from '../../client/common/constants';
@@ -17,17 +18,25 @@ import {
1718
import { IDisposable } from '../../client/common/types';
1819
import { sleep } from '../core';
1920
import { MockMemento } from '../mocks/mementos';
21+
import * as apiInt from '../../client/envExt/api.internal';
2022

2123
suite('Persistent State', () => {
2224
let cmdManager: TypeMoq.IMock<ICommandManager>;
2325
let persistentStateFactory: PersistentStateFactory;
2426
let workspaceMemento: Memento;
2527
let globalMemento: Memento;
28+
let useEnvExtensionStub: sinon.SinonStub;
2629
setup(() => {
2730
cmdManager = TypeMoq.Mock.ofType<ICommandManager>();
2831
workspaceMemento = new MockMemento();
2932
globalMemento = new MockMemento();
3033
persistentStateFactory = new PersistentStateFactory(globalMemento, workspaceMemento, cmdManager.object);
34+
35+
useEnvExtensionStub = sinon.stub(apiInt, 'useEnvExtension');
36+
useEnvExtensionStub.returns(false);
37+
});
38+
teardown(() => {
39+
sinon.restore();
3140
});
3241

3342
test('Global states created are restored on invoking clean storage command', async () => {

0 commit comments

Comments
 (0)