diff --git a/news/3 Code Health/642.md b/news/3 Code Health/642.md new file mode 100644 index 000000000000..8786ca0bf04f --- /dev/null +++ b/news/3 Code Health/642.md @@ -0,0 +1 @@ +Expose an event to notify changes to settings instead of casting settings to concrete class. diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index d890dc8c60b2..db0f5e9c55f2 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -1,9 +1,8 @@ 'use strict'; import * as child_process from 'child_process'; -import { EventEmitter } from 'events'; import * as path from 'path'; -import { ConfigurationChangeEvent, ConfigurationTarget, DiagnosticSeverity, Disposable, Uri, WorkspaceConfiguration } from 'vscode'; +import { ConfigurationChangeEvent, ConfigurationTarget, DiagnosticSeverity, Disposable, Event, EventEmitter, Uri, WorkspaceConfiguration } from 'vscode'; import '../common/extensions'; import { IInterpreterAutoSeletionProxyService } from '../interpreter/autoSelection/types'; import { sendTelemetryEvent } from '../telemetry'; @@ -30,7 +29,8 @@ import { SystemVariables } from './variables/systemVariables'; // tslint:disable:no-require-imports no-var-requires const untildify = require('untildify'); -export class PythonSettings extends EventEmitter implements IPythonSettings { +// tslint:disable-next-line:completed-docs +export class PythonSettings implements IPythonSettings { private static pythonSettings: Map = new Map(); public downloadLanguageServer = true; public jediEnabled = true; @@ -55,15 +55,18 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { public autoUpdateLanguageServer: boolean = true; public datascience!: IDataScienceSettings; + protected readonly changed = new EventEmitter(); private workspaceRoot: Uri; private disposables: Disposable[] = []; // tslint:disable-next-line:variable-name private _pythonPath = ''; private readonly workspace: IWorkspaceService; + public get onDidChange(): Event { + return this.changed.event; + } constructor(workspaceFolder: Uri | undefined, private readonly InterpreterAutoSelectionService: IInterpreterAutoSeletionProxyService, workspace?: IWorkspaceService) { - super(); this.workspace = workspace || new WorkspaceService(); this.workspaceRoot = workspaceFolder ? workspaceFolder : Uri.file(__dirname); this.initialize(); @@ -393,7 +396,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { } @debounce(1) protected debounceChangeNotification() { - this.emit('change'); + this.changed.fire(); } } diff --git a/src/client/common/types.ts b/src/client/common/types.ts index b90d9eec5ff5..7563697d85ef 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -3,7 +3,7 @@ 'use strict'; import { Socket } from 'net'; -import { ConfigurationTarget, DiagnosticSeverity, Disposable, Extension, ExtensionContext, OutputChannel, Uri, WorkspaceEdit } from 'vscode'; +import { ConfigurationTarget, DiagnosticSeverity, Disposable, Event, Extension, ExtensionContext, OutputChannel, Uri, WorkspaceEdit } from 'vscode'; import { EnvironmentVariables } from './variables/types'; export const IOutputChannel = Symbol('IOutputChannel'); export interface IOutputChannel extends OutputChannel { } @@ -160,6 +160,7 @@ export interface IPythonSettings { readonly analysis: IAnalysisSettings; readonly autoUpdateLanguageServer: boolean; readonly datascience: IDataScienceSettings; + readonly onDidChange: Event; } export interface ISortImportSettings { readonly path: string; diff --git a/src/client/datascience/datascience.ts b/src/client/datascience/datascience.ts index 06a6396c8d70..9442a101d69e 100644 --- a/src/client/datascience/datascience.ts +++ b/src/client/datascience/datascience.ts @@ -8,12 +8,12 @@ import { URL } from 'url'; import * as vscode from 'vscode'; import { IApplicationShell, ICommandManager, IDocumentManager } from '../common/application/types'; -import { PythonSettings } from '../common/configSettings'; import { PYTHON, PYTHON_LANGUAGE } from '../common/constants'; import { ContextKey } from '../common/contextKey'; import { BANNER_NAME_DS_SURVEY, IConfigurationService, + IDisposable, IDisposableRegistry, IExtensionContext, IPythonExtensionBanner @@ -30,6 +30,7 @@ export class DataScience implements IDataScience { public isDisposed: boolean = false; private readonly commandListeners: IDataScienceCommandListener[]; private readonly dataScienceSurveyBanner: IPythonExtensionBanner; + private changeHandler: IDisposable | undefined; constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer, @inject(ICommandManager) private commandManager: ICommandManager, @inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry, @@ -38,8 +39,8 @@ export class DataScience implements IDataScience { @inject(IConfigurationService) private configuration: IConfigurationService, @inject(IDocumentManager) private documentManager: IDocumentManager, @inject(IApplicationShell) private appShell: IApplicationShell) { - this.commandListeners = this.serviceContainer.getAll(IDataScienceCommandListener); - this.dataScienceSurveyBanner = this.serviceContainer.get(IPythonExtensionBanner, BANNER_NAME_DS_SURVEY); + this.commandListeners = this.serviceContainer.getAll(IDataScienceCommandListener); + this.dataScienceSurveyBanner = this.serviceContainer.get(IPythonExtensionBanner, BANNER_NAME_DS_SURVEY); } public async activate(): Promise { @@ -53,7 +54,7 @@ export class DataScience implements IDataScience { // Set our initial settings and sign up for changes this.onSettingsChanged(); - (this.configuration.getSettings() as PythonSettings).addListener('change', this.onSettingsChanged); + this.changeHandler = this.configuration.getSettings().onDidChange(this.onSettingsChanged.bind(this)); this.disposableRegistry.push(this); // Listen for active editor changes so we can detect have code cells or not @@ -62,9 +63,9 @@ export class DataScience implements IDataScience { } public async dispose() { - if (!this.isDisposed) { - this.isDisposed = true; - (this.configuration.getSettings() as PythonSettings).removeListener('change', this.onSettingsChanged); + if (this.changeHandler) { + this.changeHandler.dispose(); + this.changeHandler = undefined; } } @@ -121,13 +122,13 @@ export class DataScience implements IDataScience { switch (selection) { case localize.DataScience.jupyterSelectURILaunchLocal(): return this.setJupyterURIToLocal(); - break; + break; case localize.DataScience.jupyterSelectURISpecifyURI(): return this.selectJupyterLaunchURI(); - break; + break; default: // If user cancels quick pick we will get undefined as the selection and fall through here - break; + break; } } @@ -139,8 +140,10 @@ export class DataScience implements IDataScience { @captureTelemetry(Telemetry.SetJupyterURIToUserSpecified) private async selectJupyterLaunchURI(): Promise { // First get the proposed URI from the user - const userURI = await this.appShell.showInputBox({prompt: localize.DataScience.jupyterSelectURIPrompt(), - placeHolder: 'https://hostname:8080/?token=849d61a414abafab97bc4aab1f3547755ddc232c2b8cb7fe', validateInput: this.validateURI, ignoreFocusOut: true}); + const userURI = await this.appShell.showInputBox({ + prompt: localize.DataScience.jupyterSelectURIPrompt(), + placeHolder: 'https://hostname:8080/?token=849d61a414abafab97bc4aab1f3547755ddc232c2b8cb7fe', validateInput: this.validateURI, ignoreFocusOut: true + }); if (userURI) { await this.configuration.updateSetting('dataScience.jupyterServerURI', userURI, undefined, vscode.ConfigurationTarget.Workspace); @@ -149,8 +152,8 @@ export class DataScience implements IDataScience { private validateURI = (testURI: string): string | undefined | null => { try { - // tslint:disable-next-line:no-unused-expression - new URL(testURI); + // tslint:disable-next-line:no-unused-expression + new URL(testURI); } catch { return localize.DataScience.jupyterSelectURIInvalidURI(); } @@ -169,8 +172,7 @@ export class DataScience implements IDataScience { // Get our matching code watcher for the active document private getCurrentCodeWatcher(): ICodeWatcher | undefined { const activeEditor = vscode.window.activeTextEditor; - if (!activeEditor || !activeEditor.document) - { + if (!activeEditor || !activeEditor.document) { return undefined; } diff --git a/src/client/datascience/history.ts b/src/client/datascience/history.ts index cdf44090ca93..dc0593359a0d 100644 --- a/src/client/datascience/history.ts +++ b/src/client/datascience/history.ts @@ -21,11 +21,10 @@ import { IWorkspaceService } from '../common/application/types'; import { CancellationError } from '../common/cancellation'; -import { PythonSettings } from '../common/configSettings'; import { EXTENSION_ROOT_DIR } from '../common/constants'; import { ContextKey } from '../common/contextKey'; import { IFileSystem } from '../common/platform/types'; -import { IConfigurationService, IDisposableRegistry, ILogger } from '../common/types'; +import { IConfigurationService, IDisposable, IDisposableRegistry, ILogger } from '../common/types'; import { createDeferred } from '../common/utils/async'; import * as localize from '../common/utils/localize'; import { IInterpreterService } from '../interpreter/contracts'; @@ -54,18 +53,19 @@ export enum SysInfoReason { @injectable() export class History implements IWebPanelMessageListener, IHistory { - private disposed : boolean = false; - private webPanel : IWebPanel | undefined; + private disposed: boolean = false; + private webPanel: IWebPanel | undefined; private loadPromise: Promise; - private interpreterChangedDisposable : Disposable; - private closedEvent : EventEmitter; + private interpreterChangedDisposable: Disposable; + private closedEvent: EventEmitter; private unfinishedCells: ICell[] = []; private restartingKernel: boolean = false; private potentiallyUnfinishedStatus: Disposable[] = []; private addedSysInfo: boolean = false; private ignoreCount: number = 0; - private waitingForExportCells : boolean = false; + private waitingForExportCells: boolean = false; private jupyterServer: INotebookServer | undefined; + private changeHandler: IDisposable | undefined; constructor( @inject(IApplicationShell) private applicationShell: IApplicationShell, @@ -73,9 +73,9 @@ export class History implements IWebPanelMessageListener, IHistory { @inject(IInterpreterService) private interpreterService: IInterpreterService, @inject(IWebPanelProvider) private provider: IWebPanelProvider, @inject(IDisposableRegistry) private disposables: IDisposableRegistry, - @inject(ICodeCssGenerator) private cssGenerator : ICodeCssGenerator, - @inject(ILogger) private logger : ILogger, - @inject(IStatusProvider) private statusProvider : IStatusProvider, + @inject(ICodeCssGenerator) private cssGenerator: ICodeCssGenerator, + @inject(ILogger) private logger: ILogger, + @inject(IStatusProvider) private statusProvider: IStatusProvider, @inject(IJupyterExecution) private jupyterExecution: IJupyterExecution, @inject(IFileSystem) private fileSystem: IFileSystem, @inject(IConfigurationService) private configuration: IConfigurationService, @@ -85,7 +85,7 @@ export class History implements IWebPanelMessageListener, IHistory { // Sign up for configuration changes this.interpreterChangedDisposable = this.interpreterService.onDidChangeInterpreter(this.onInterpreterChanged); - (this.configuration.getSettings() as PythonSettings).addListener('change', this.onSettingsChanged); + this.changeHandler = this.configuration.getSettings().onDidChange(this.onSettingsChanged.bind(this)); // Create our event emitter this.closedEvent = new EventEmitter(); @@ -95,7 +95,7 @@ export class History implements IWebPanelMessageListener, IHistory { this.loadPromise = this.load(); } - public async show() : Promise { + public async show(): Promise { if (!this.disposed) { // Make sure we're loaded first await this.loadPromise; @@ -107,11 +107,11 @@ export class History implements IWebPanelMessageListener, IHistory { } } - public get closed() : Event { + public get closed(): Event { return this.closedEvent.event; } - public async addCode(code: string, file: string, line: number, editor?: TextEditor) : Promise { + public async addCode(code: string, file: string, line: number, editor?: TextEditor): Promise { // Start a status item const status = this.setStatus(localize.DataScience.executingCode()); @@ -179,7 +179,7 @@ export class History implements IWebPanelMessageListener, IHistory { // tslint:disable-next-line: no-any no-empty public postMessage(type: string, payload?: any) { if (this.webPanel) { - this.webPanel.postMessage({type: type, payload: payload}); + this.webPanel.postMessage({ type: type, payload: payload }); } } @@ -243,17 +243,20 @@ export class History implements IWebPanelMessageListener, IHistory { } } - public async dispose() { + public async dispose() { if (!this.disposed) { this.disposed = true; this.interpreterChangedDisposable.dispose(); - (this.configuration.getSettings() as PythonSettings).removeListener('change', this.onSettingsChanged); this.closedEvent.fire(this); if (this.jupyterServer) { await this.jupyterServer.shutdown(); } this.updateContexts(); } + if (this.changeHandler) { + this.changeHandler.dispose(); + this.changeHandler = undefined; + } } @captureTelemetry(Telemetry.Undo) @@ -345,7 +348,7 @@ export class History implements IWebPanelMessageListener, IHistory { } } - private async restartKernelInternal() : Promise { + private async restartKernelInternal(): Promise { this.restartingKernel = true; // First we need to finish all outstanding cells. @@ -416,7 +419,7 @@ export class History implements IWebPanelMessageListener, IHistory { } } - private setStatus = (message: string) : Disposable => { + private setStatus = (message: string): Disposable => { const result = this.statusProvider.set(message); this.potentiallyUnfinishedStatus.push(result); return result; @@ -434,13 +437,13 @@ export class History implements IWebPanelMessageListener, IHistory { copy.data.execution_count = count - this.ignoreCount; } if (this.webPanel) { - this.webPanel.postMessage({type: message, payload: copy}); + this.webPanel.postMessage({ type: message, payload: copy }); } } - private onAddCodeEvent = (cells : ICell[], editor?: TextEditor) => { + private onAddCodeEvent = (cells: ICell[], editor?: TextEditor) => { // Send each cell to the other side - cells.forEach((cell : ICell) => { + cells.forEach((cell: ICell) => { if (this.webPanel) { switch (cell.state) { case CellState.init: @@ -459,7 +462,7 @@ export class History implements IWebPanelMessageListener, IHistory { case CellState.error: case CellState.finished: // Tell the react controls we're done - this.sendCell(cell, HistoryMessages.FinishCell); + this.sendCell(cell, HistoryMessages.FinishCell); // Remove from the list of unfinished cells this.unfinishedCells = this.unfinishedCells.filter(c => c.id !== cell.id); @@ -488,7 +491,7 @@ export class History implements IWebPanelMessageListener, IHistory { const dsSettings = JSON.stringify(this.configuration.getSettings().datascience); if (this.webPanel) { - this.webPanel.postMessage({type: HistoryMessages.UpdateSettings, payload: dsSettings}); + this.webPanel.postMessage({ type: HistoryMessages.UpdateSettings, payload: dsSettings }); } } @@ -511,10 +514,10 @@ export class History implements IWebPanelMessageListener, IHistory { } private async gotoCodeInternal(file: string, line: number) { - let editor : TextEditor | undefined; + let editor: TextEditor | undefined; if (await fs.pathExists(file)) { - editor = await this.documentManager.showTextDocument(Uri.file(file), {viewColumn: ViewColumn.One}); + editor = await this.documentManager.showTextDocument(Uri.file(file), { viewColumn: ViewColumn.One }); } else { // File URI isn't going to work. Look through the active text documents editor = this.documentManager.visibleTextEditors.find(te => te.document.fileName === file); @@ -532,7 +535,7 @@ export class History implements IWebPanelMessageListener, IHistory { @captureTelemetry(Telemetry.ExportNotebook, undefined, false) // tslint:disable-next-line: no-any no-empty - private export (payload: any) { + private export(payload: any) { if (payload.contents) { // Should be an array of cells const cells = payload.contents as ICell[]; @@ -556,7 +559,7 @@ export class History implements IWebPanelMessageListener, IHistory { } } - private exportToFile = async (cells: ICell[], file : string) => { + private exportToFile = async (cells: ICell[], file: string) => { // Take the list of cells, convert them to a notebook json format and write to disk if (this.jupyterServer) { let directoryChange; @@ -569,8 +572,8 @@ export class History implements IWebPanelMessageListener, IHistory { try { // tslint:disable-next-line: no-any - await this.fileSystem.writeFile(file, JSON.stringify(notebook), {encoding: 'utf8', flag: 'w'}); - this.applicationShell.showInformationMessage(localize.DataScience.exportDialogComplete().format(file), localize.DataScience.exportOpenQuestion()).then((str : string | undefined) => { + await this.fileSystem.writeFile(file, JSON.stringify(notebook), { encoding: 'utf8', flag: 'w' }); + this.applicationShell.showInformationMessage(localize.DataScience.exportDialogComplete().format(file), localize.DataScience.exportOpenQuestion()).then((str: string | undefined) => { if (str && this.jupyterServer) { // If the user wants to, open the notebook they just generated. this.jupyterExecution.spawnNotebook(file).ignoreErrors(); @@ -583,12 +586,12 @@ export class History implements IWebPanelMessageListener, IHistory { } } - private loadJupyterServer = async (restart?: boolean) : Promise => { + private loadJupyterServer = async (restart?: boolean): Promise => { // Startup our jupyter server const settings = this.configuration.getSettings(); let serverURI: string | undefined = settings.datascience.jupyterServerURI; let workingDir: string | undefined; - const useDefaultConfig : boolean | undefined = settings.datascience.useDefaultConfigForJupyter; + const useDefaultConfig: boolean | undefined = settings.datascience.useDefaultConfigForJupyter; const status = this.setStatus(localize.DataScience.connectingToJupyter()); try { // For the local case pass in our URI as undefined, that way connect doesn't have to check the setting @@ -611,8 +614,7 @@ export class History implements IWebPanelMessageListener, IHistory { } // Calculate the working directory that we should move into when starting up our Jupyter server locally - private calculateWorkingDirectory = async (): Promise => - { + private calculateWorkingDirectory = async (): Promise => { let workingDir: string | undefined; // For a local launch calculate the working directory that we should switch into const settings = this.configuration.getSettings(); @@ -631,21 +633,21 @@ export class History implements IWebPanelMessageListener, IHistory { workingDir = workspaceFolderPath; } } else { - // fileRoot is a relative path, combine it with the workspace folder - const combinedPath = path.join(workspaceFolderPath, fileRoot); - if (await this.fileSystem.directoryExists(combinedPath)) { - // combined path exists, use it - workingDir = combinedPath; - } else { - // Combined path doesn't exist, use workspace - workingDir = workspaceFolderPath; - } + // fileRoot is a relative path, combine it with the workspace folder + const combinedPath = path.join(workspaceFolderPath, fileRoot); + if (await this.fileSystem.directoryExists(combinedPath)) { + // combined path exists, use it + workingDir = combinedPath; + } else { + // Combined path doesn't exist, use workspace + workingDir = workspaceFolderPath; + } } } return workingDir; } - private extractStreamOutput(cell: ICell) : string { + private extractStreamOutput(cell: ICell): string { let result = ''; if (cell.state === CellState.error || cell.state === CellState.finished) { const outputs = cell.data.outputs as nbformat.IOutput[]; @@ -666,7 +668,7 @@ export class History implements IWebPanelMessageListener, IHistory { return result; } - private generateSysInfoCell = async (reason: SysInfoReason) : Promise => { + private generateSysInfoCell = async (reason: SysInfoReason): Promise => { // Execute the code 'import sys\r\nsys.version' and 'import sys\r\nsys.executable' to get our // version and executable if (this.jupyterServer) { @@ -715,22 +717,22 @@ export class History implements IWebPanelMessageListener, IHistory { private async generateSysInfoMessage(reason: SysInfoReason): Promise { switch (reason) { case SysInfoReason.Start: - // Message depends upon if ipykernel is supported or not. - if (!(await this.jupyterExecution.isKernelCreateSupported())) { - return localize.DataScience.pythonVersionHeaderNoPyKernel(); - } - return localize.DataScience.pythonVersionHeader(); - break; + // Message depends upon if ipykernel is supported or not. + if (!(await this.jupyterExecution.isKernelCreateSupported())) { + return localize.DataScience.pythonVersionHeaderNoPyKernel(); + } + return localize.DataScience.pythonVersionHeader(); + break; case SysInfoReason.Restart: - return localize.DataScience.pythonRestartHeader(); - break; + return localize.DataScience.pythonRestartHeader(); + break; case SysInfoReason.Interrupt: - return localize.DataScience.pythonInterruptFailedHeader(); - break; + return localize.DataScience.pythonInterruptFailedHeader(); + break; default: - this.logger.logError('Invalid SysInfoReason'); - return ''; - break; + this.logger.logError('Invalid SysInfoReason'); + return ''; + break; } } @@ -745,7 +747,7 @@ export class History implements IWebPanelMessageListener, IHistory { return `${localize.DataScience.sysInfoURILabel()}${urlString}`; } - private addSysInfo = async (reason: SysInfoReason) : Promise => { + private addSysInfo = async (reason: SysInfoReason): Promise => { if (!this.addedSysInfo || reason === SysInfoReason.Interrupt || reason === SysInfoReason.Restart) { this.addedSysInfo = true; this.ignoreCount = 0; @@ -758,7 +760,7 @@ export class History implements IWebPanelMessageListener, IHistory { } } - private loadWebPanel = async () : Promise => { + private loadWebPanel = async (): Promise => { // Create our web panel (it's the UI that shows up for the history) // Figure out the name of our main bundle. Should be in our output directory @@ -772,7 +774,7 @@ export class History implements IWebPanelMessageListener, IHistory { this.webPanel = this.provider.create(this, localize.DataScience.historyTitle(), mainScriptPath, css); } - private load = async () : Promise => { + private load = async (): Promise => { const status = this.setStatus(localize.DataScience.startingJupyter()); // Check to see if we support ipykernel or not diff --git a/src/client/datascience/jupyter/jupyterExecution.ts b/src/client/datascience/jupyter/jupyterExecution.ts index 7ebaa718d282..1ab69a4ed7e0 100644 --- a/src/client/datascience/jupyter/jupyterExecution.ts +++ b/src/client/datascience/jupyter/jupyterExecution.ts @@ -386,7 +386,9 @@ export class JupyterExecution implements IJupyterExecution, Disposable { private findSpecPath = async (specName: string, cancelToken?: CancellationToken): Promise => { // Enumerate all specs and get path for the match const specs = await this.enumerateSpecs(cancelToken); - const match = specs.find(s => { + const match = specs! + .filter(s => s !== undefined) + .find(s => { const js = s as JupyterKernelSpec; return js && js.name === specName; }) as JupyterKernelSpec; @@ -557,7 +559,7 @@ export class JupyterExecution implements IJupyterExecution, Disposable { // Then let them run concurrently (they are file io) const specs = await Promise.all(promises); - return specs.filter(s => s); + return specs!.filter(s => s); } catch { // This is failing for some folks. In that case return nothing return []; diff --git a/src/test/datascience/dataScienceIocContainer.ts b/src/test/datascience/dataScienceIocContainer.ts index 7f189ddc8271..c9ebc6a796a5 100644 --- a/src/test/datascience/dataScienceIocContainer.ts +++ b/src/test/datascience/dataScienceIocContainer.ts @@ -152,7 +152,11 @@ import { MockJupyterManager } from './mockJupyterManager'; export class DataScienceIocContainer extends UnitTestIocContainer { - private pythonSettings: PythonSettings = new PythonSettings(undefined, new MockAutoSelectionService()); + private pythonSettings = new class extends PythonSettings { + public fireChangeEvent() { + this.changed.fire(); + } + }(undefined, new MockAutoSelectionService()); private commandManager: MockCommandManager = new MockCommandManager(); private setContexts: { [name: string]: boolean } = {}; private contextSetEvent: EventEmitter<{ name: string; value: boolean }> = new EventEmitter<{ name: string; value: boolean }>(); @@ -394,7 +398,7 @@ export class DataScienceIocContainer extends UnitTestIocContainer { } public forceSettingsChanged() { - this.pythonSettings.emit('change'); + this.pythonSettings.fireChangeEvent(); } public get mockJupyter(): MockJupyterManager | undefined { diff --git a/src/test/datascience/mockJupyterManager.ts b/src/test/datascience/mockJupyterManager.ts index dcde03799fc6..79c1e1d3a5c9 100644 --- a/src/test/datascience/mockJupyterManager.ts +++ b/src/test/datascience/mockJupyterManager.ts @@ -12,7 +12,6 @@ import { EventEmitter } from 'vscode'; import { CancellationToken } from 'vscode-jsonrpc'; import { Cancellation } from '../../client/common/cancellation'; -import { PythonSettings } from '../../client/common/configSettings'; import { ExecutionResult, IProcessServiceFactory, IPythonExecutionFactory, Output } from '../../client/common/process/types'; import { IAsyncDisposableRegistry, IConfigurationService } from '../../client/common/types'; import { EXTENSION_ROOT_DIR } from '../../client/constants'; @@ -77,7 +76,7 @@ export class MockJupyterManager implements IJupyterSessionManager { // Listen to configuration changes like the real interpreter service does so that we fire our settings changed event const configService = serviceManager.get(IConfigurationService); if (configService && configService !== null) { - (configService.getSettings() as PythonSettings).addListener('change', this.onConfigChanged); + configService.getSettings().onDidChange(this.onConfigChanged.bind(this)); } // Stick our services into the service manager