Skip to content

Commit 6e0e28d

Browse files
Fix onInterpreterChange event to only fire on interpreter change (microsoft#3847)
1 parent 33b496b commit 6e0e28d

File tree

4 files changed

+39
-24
lines changed

4 files changed

+39
-24
lines changed

news/2 Fixes/3749.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Don't restart the Jupyter server on any settings change. Also don't throw interpreter changed events on unrelated settings changes.

src/client/common/configSettings.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as child_process from 'child_process';
44
import { EventEmitter } from 'events';
55
import * as path from 'path';
66
import {
7-
ConfigurationTarget, DiagnosticSeverity, Disposable, Uri,
7+
ConfigurationChangeEvent, ConfigurationTarget, DiagnosticSeverity, Disposable, Uri,
88
workspace, WorkspaceConfiguration
99
} from 'vscode';
1010
import { sendTelemetryEvent } from '../telemetry';
@@ -348,13 +348,16 @@ export class PythonSettings extends EventEmitter implements IPythonSettings {
348348
}
349349
}
350350
protected initialize(): void {
351-
this.disposables.push(workspace.onDidChangeConfiguration(() => {
352-
const currentConfig = workspace.getConfiguration('python', this.workspaceRoot);
353-
this.update(currentConfig);
354-
355-
// If workspace config changes, then we could have a cascading effect of on change events.
356-
// Let's defer the change notification.
357-
setTimeout(() => this.emit('change'), 1);
351+
this.disposables.push(workspace.onDidChangeConfiguration((event: ConfigurationChangeEvent) => {
352+
if (event.affectsConfiguration('python'))
353+
{
354+
const currentConfig = workspace.getConfiguration('python', this.workspaceRoot);
355+
this.update(currentConfig);
356+
357+
// If workspace config changes, then we could have a cascading effect of on change events.
358+
// Let's defer the change notification.
359+
setTimeout(() => this.emit('change'), 1);
360+
}
358361
}));
359362

360363
const initialConfig = workspace.getConfiguration('python', this.workspaceRoot);

src/client/interpreter/interpreterService.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,17 @@ export class InterpreterService implements Disposable, IInterpreterService {
2727
private readonly fs: IFileSystem;
2828
private readonly persistentStateFactory: IPersistentStateFactory;
2929
private readonly helper: IInterpreterHelper;
30+
private readonly configService: IConfigurationService;
3031
private readonly didChangeInterpreterEmitter = new EventEmitter<void>();
32+
private pythonPathSetting: string = '';
3133

3234
constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {
3335
this.locator = serviceContainer.get<IInterpreterLocatorService>(IInterpreterLocatorService, INTERPRETER_LOCATOR_SERVICE);
3436
this.helper = serviceContainer.get<IInterpreterHelper>(IInterpreterHelper);
3537
this.pythonPathUpdaterService = this.serviceContainer.get<IPythonPathUpdaterServiceManager>(IPythonPathUpdaterServiceManager);
3638
this.fs = this.serviceContainer.get<IFileSystem>(IFileSystem);
3739
this.persistentStateFactory = this.serviceContainer.get<IPersistentStateFactory>(IPersistentStateFactory);
40+
this.configService = this.serviceContainer.get<IConfigurationService>(IConfigurationService);
3841
}
3942
public get hasInterpreters(): Promise<boolean> {
4043
return this.locator.hasInterpreters;
@@ -49,8 +52,9 @@ export class InterpreterService implements Disposable, IInterpreterService {
4952
const disposables = this.serviceContainer.get<Disposable[]>(IDisposableRegistry);
5053
const documentManager = this.serviceContainer.get<IDocumentManager>(IDocumentManager);
5154
disposables.push(documentManager.onDidChangeActiveTextEditor((e) => e ? this.refresh(e.document.uri) : undefined));
52-
const configService = this.serviceContainer.get<IConfigurationService>(IConfigurationService);
53-
(configService.getSettings() as PythonSettings).addListener('change', this.onConfigChanged);
55+
const pySettings = this.configService.getSettings() as PythonSettings;
56+
this.pythonPathSetting = pySettings.pythonPath;
57+
pySettings.addListener('change', this.onConfigChanged);
5458
}
5559

5660
public async getInterpreters(resource?: Uri): Promise<PythonInterpreter[]> {
@@ -253,10 +257,15 @@ export class InterpreterService implements Disposable, IInterpreterService {
253257
}
254258

255259
private onConfigChanged = () => {
256-
this.didChangeInterpreterEmitter.fire();
257-
const interpreterDisplay = this.serviceContainer.get<IInterpreterDisplay>(IInterpreterDisplay);
258-
interpreterDisplay.refresh()
259-
.catch(ex => console.error('Python Extension: display.refresh', ex));
260+
// Check if we actually changed our python path
261+
const pySettings = this.configService.getSettings() as PythonSettings;
262+
if (this.pythonPathSetting !== pySettings.pythonPath) {
263+
this.pythonPathSetting = pySettings.pythonPath;
264+
this.didChangeInterpreterEmitter.fire();
265+
const interpreterDisplay = this.serviceContainer.get<IInterpreterDisplay>(IInterpreterDisplay);
266+
interpreterDisplay.refresh()
267+
.catch(ex => console.error('Python Extension: display.refresh', ex));
268+
}
260269
}
261270
private async collectInterpreterDetails(pythonPath: string, resource: Uri | undefined) {
262271
const interpreterHelper = this.serviceContainer.get<IInterpreterHelper>(IInterpreterHelper);

src/test/interpreters/interpreterService.unit.test.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import { expect, use } from 'chai';
99
import * as chaiAsPromised from 'chai-as-promised';
10-
import { EventEmitter } from 'events';
1110
import { Container } from 'inversify';
1211
import * as path from 'path';
1312
import { SemVer } from 'semver';
@@ -17,7 +16,7 @@ import { IDocumentManager, IWorkspaceService } from '../../client/common/applica
1716
import { getArchitectureDisplayName } from '../../client/common/platform/registry';
1817
import { IFileSystem } from '../../client/common/platform/types';
1918
import { IPythonExecutionFactory, IPythonExecutionService } from '../../client/common/process/types';
20-
import { IConfigurationService, IDisposableRegistry, IPersistentStateFactory } from '../../client/common/types';
19+
import { IConfigurationService, IDisposableRegistry, IPersistentStateFactory, IPythonSettings } from '../../client/common/types';
2120
import * as EnumEx from '../../client/common/utils/enum';
2221
import { noop } from '../../client/common/utils/misc';
2322
import { Architecture } from '../../client/common/utils/platform';
@@ -37,6 +36,7 @@ import { InterpreterService } from '../../client/interpreter/interpreterService'
3736
import { IVirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs/types';
3837
import { ServiceContainer } from '../../client/ioc/container';
3938
import { ServiceManager } from '../../client/ioc/serviceManager';
39+
import { PYTHON_PATH } from '../common';
4040

4141
use(chaiAsPromised);
4242

@@ -69,6 +69,8 @@ suite('Interpreters service', () => {
6969
let persistentStateFactory: TypeMoq.IMock<IPersistentStateFactory>;
7070
let pythonExecutionFactory: TypeMoq.IMock<IPythonExecutionFactory>;
7171
let pythonExecutionService: TypeMoq.IMock<IPythonExecutionService>;
72+
let configService: TypeMoq.IMock<IConfigurationService>;
73+
let pythonSettings: TypeMoq.IMock<IPythonSettings>;
7274
type ConfigValue<T> = { key: string; defaultValue?: T; globalValue?: T; workspaceValue?: T; workspaceFolderValue?: T };
7375

7476
function setupSuite() {
@@ -88,6 +90,11 @@ suite('Interpreters service', () => {
8890
persistentStateFactory = TypeMoq.Mock.ofType<IPersistentStateFactory>();
8991
pythonExecutionFactory = TypeMoq.Mock.ofType<IPythonExecutionFactory>();
9092
pythonExecutionService = TypeMoq.Mock.ofType<IPythonExecutionService>();
93+
configService = TypeMoq.Mock.ofType<IConfigurationService>();
94+
95+
pythonSettings = TypeMoq.Mock.ofType<IPythonSettings>();
96+
pythonSettings.setup(s => s.pythonPath).returns(() => PYTHON_PATH);
97+
configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object);
9198

9299
pythonExecutionService.setup((p: any) => p.then).returns(() => undefined);
93100
workspace.setup(x => x.getConfiguration('python', TypeMoq.It.isAny())).returns(() => config.object);
@@ -113,6 +120,7 @@ suite('Interpreters service', () => {
113120
serviceManager.addSingletonInstance<IPersistentStateFactory>(IPersistentStateFactory, persistentStateFactory.object);
114121
serviceManager.addSingletonInstance<IPythonExecutionFactory>(IPythonExecutionFactory, pythonExecutionFactory.object);
115122
serviceManager.addSingletonInstance<IPythonExecutionService>(IPythonExecutionService, pythonExecutionService.object);
123+
serviceManager.addSingletonInstance<IConfigurationService>(IConfigurationService, configService.object);
116124

117125
pipenvLocator = TypeMoq.Mock.ofType<IInterpreterLocatorService>();
118126
wksLocator = TypeMoq.Mock.ofType<IInterpreterLocatorService>();
@@ -149,21 +157,18 @@ suite('Interpreters service', () => {
149157
});
150158
});
151159

152-
test('Changes to active document should invoke intrepreter.refresh method', async () => {
160+
test('Changes to active document should invoke interpreter.refresh method', async () => {
153161
const service = new InterpreterService(serviceContainer);
154-
const configService = TypeMoq.Mock.ofType<IConfigurationService>();
155162
const documentManager = TypeMoq.Mock.ofType<IDocumentManager>();
156163

157164
let activeTextEditorChangeHandler: Function | undefined;
158165
documentManager.setup(d => d.onDidChangeActiveTextEditor(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(handler => {
159166
activeTextEditorChangeHandler = handler;
160167
return { dispose: noop };
161168
});
162-
serviceManager.addSingletonInstance(IConfigurationService, configService.object);
163169
serviceManager.addSingletonInstance(IDocumentManager, documentManager.object);
164170

165171
// tslint:disable-next-line:no-any
166-
configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => new EventEmitter() as any);
167172
service.initialize();
168173
const textEditor = TypeMoq.Mock.ofType<TextEditor>();
169174
const uri = Uri.file(path.join('usr', 'file.py'));
@@ -175,21 +180,18 @@ suite('Interpreters service', () => {
175180
interpreterDisplay.verify(i => i.refresh(TypeMoq.It.isValue(uri)), TypeMoq.Times.once());
176181
});
177182

178-
test('If there is no active document then intrepreter.refresh should not be invoked', async () => {
183+
test('If there is no active document then interpreter.refresh should not be invoked', async () => {
179184
const service = new InterpreterService(serviceContainer);
180-
const configService = TypeMoq.Mock.ofType<IConfigurationService>();
181185
const documentManager = TypeMoq.Mock.ofType<IDocumentManager>();
182186

183187
let activeTextEditorChangeHandler: Function | undefined;
184188
documentManager.setup(d => d.onDidChangeActiveTextEditor(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(handler => {
185189
activeTextEditorChangeHandler = handler;
186190
return { dispose: noop };
187191
});
188-
serviceManager.addSingletonInstance(IConfigurationService, configService.object);
189192
serviceManager.addSingletonInstance(IDocumentManager, documentManager.object);
190193

191194
// tslint:disable-next-line:no-any
192-
configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => new EventEmitter() as any);
193195
service.initialize();
194196
activeTextEditorChangeHandler!();
195197

0 commit comments

Comments
 (0)