diff --git a/src/languageFeatures.ts b/src/languageFeatures.ts index 46248592..ccc50c85 100644 --- a/src/languageFeatures.ts +++ b/src/languageFeatures.ts @@ -135,13 +135,15 @@ export class DiagnostcsAdapter extends Adapter { } }); - this._disposables.push(this._defaults.onDidChange(() => { + const recomputeDiagostics = () => { // redo diagnostics when options change for (const model of monaco.editor.getModels()) { onModelRemoved(model); onModelAdd(model); } - })); + }; + this._disposables.push(this._defaults.onDidChange(recomputeDiagostics)); + this._disposables.push(this._defaults.onDidExtraLibsChange(recomputeDiagostics)); monaco.editor.getModels().forEach(onModelAdd); } diff --git a/src/monaco.contribution.ts b/src/monaco.contribution.ts index d5cf4f67..62da8db9 100644 --- a/src/monaco.contribution.ts +++ b/src/monaco.contribution.ts @@ -12,32 +12,45 @@ import IDisposable = monaco.IDisposable; // --- TypeScript configuration and defaults --------- +export interface IExtraLib { + content: string; + version: number; +} + +export interface IExtraLibs { + [path: string]: IExtraLib; +} + export class LanguageServiceDefaultsImpl implements monaco.languages.typescript.LanguageServiceDefaults { - private _onDidChange = new Emitter(); - private _extraLibs: { [path: string]: string }; + private _onDidChange = new Emitter(); + private _onDidExtraLibsChange = new Emitter(); + + private _extraLibs: IExtraLibs; private _workerMaxIdleTime: number; private _eagerModelSync: boolean; private _compilerOptions: monaco.languages.typescript.CompilerOptions; private _diagnosticsOptions: monaco.languages.typescript.DiagnosticsOptions; + private _onDidExtraLibsChangeTimeout: number; constructor(compilerOptions: monaco.languages.typescript.CompilerOptions, diagnosticsOptions: monaco.languages.typescript.DiagnosticsOptions) { this._extraLibs = Object.create(null); this._workerMaxIdleTime = 2 * 60 * 1000; this.setCompilerOptions(compilerOptions); this.setDiagnosticsOptions(diagnosticsOptions); + this._onDidExtraLibsChangeTimeout = -1; } - get onDidChange(): IEvent { + get onDidChange(): IEvent { return this._onDidChange.event; } - getExtraLibs(): { [path: string]: string; } { - const result = Object.create(null); - for (var key in this._extraLibs) { - result[key] = this._extraLibs[key]; - } - return Object.freeze(result); + get onDidExtraLibsChange(): IEvent { + return this._onDidExtraLibsChange.event; + } + + getExtraLibs(): IExtraLibs { + return this._extraLibs; } addExtraLib(content: string, filePath?: string): IDisposable { @@ -45,29 +58,58 @@ export class LanguageServiceDefaultsImpl implements monaco.languages.typescript. filePath = `ts:extralib-${Math.random().toString(36).substring(2, 15)}`; } + if (this._extraLibs[filePath] && this._extraLibs[filePath].content === content) { + // no-op, there already exists an extra lib with this content + return { + dispose: () => { } + }; + } + + let myVersion = 1; if (this._extraLibs[filePath]) { - throw new Error(`${filePath} already a extra lib`); + myVersion = this._extraLibs[filePath].version + 1; } - this._extraLibs[filePath] = content; - this._onDidChange.fire(this); + this._extraLibs[filePath] = { + content: content, + version: myVersion, + }; + this._fireOnDidExtraLibsChangeSoon(); return { dispose: () => { - if (delete this._extraLibs[filePath]) { - this._onDidChange.fire(this); + let extraLib = this._extraLibs[filePath]; + if (!extraLib) { + return; + } + if (extraLib.version !== myVersion) { + return; } + + delete this._extraLibs[filePath]; + this._fireOnDidExtraLibsChangeSoon(); } }; } + private _fireOnDidExtraLibsChangeSoon(): void { + if (this._onDidExtraLibsChangeTimeout !== -1) { + // already scheduled + return; + } + this._onDidExtraLibsChangeTimeout = setTimeout(() => { + this._onDidExtraLibsChangeTimeout = -1; + this._onDidExtraLibsChange.fire(undefined); + }, 0); + } + getCompilerOptions(): monaco.languages.typescript.CompilerOptions { return this._compilerOptions; } setCompilerOptions(options: monaco.languages.typescript.CompilerOptions): void { this._compilerOptions = options || Object.create(null); - this._onDidChange.fire(this); + this._onDidChange.fire(undefined); } getDiagnosticsOptions(): monaco.languages.typescript.DiagnosticsOptions { @@ -76,7 +118,7 @@ export class LanguageServiceDefaultsImpl implements monaco.languages.typescript. setDiagnosticsOptions(options: monaco.languages.typescript.DiagnosticsOptions): void { this._diagnosticsOptions = options || Object.create(null); - this._onDidChange.fire(this); + this._onDidChange.fire(undefined); } setMaximumWorkerIdleTime(value: number): void { diff --git a/src/tsWorker.ts b/src/tsWorker.ts index ef154b80..fd2b280a 100644 --- a/src/tsWorker.ts +++ b/src/tsWorker.ts @@ -6,6 +6,7 @@ import * as ts from './lib/typescriptServices'; import { lib_dts, lib_es6_dts } from './lib/lib'; +import { IExtraLibs } from './monaco.contribution'; import IWorkerContext = monaco.worker.IWorkerContext; @@ -24,7 +25,7 @@ export class TypeScriptWorker implements ts.LanguageServiceHost { // --- model sync ----------------------- private _ctx: IWorkerContext; - private _extraLibs: { [fileName: string]: string } = Object.create(null); + private _extraLibs: IExtraLibs = Object.create(null); private _languageService = ts.createLanguageService(this); private _compilerOptions: ts.CompilerOptions; @@ -59,9 +60,11 @@ export class TypeScriptWorker implements ts.LanguageServiceHost { let model = this._getModel(fileName); if (model) { return model.version.toString(); - } else if (this.isDefaultLibFileName(fileName) || fileName in this._extraLibs) { - // extra lib and default lib are static + } else if (this.isDefaultLibFileName(fileName)) { + // default lib is static return '1'; + } else if (fileName in this._extraLibs) { + return String(this._extraLibs[fileName].version); } } @@ -73,8 +76,8 @@ export class TypeScriptWorker implements ts.LanguageServiceHost { text = model.getValue(); } else if (fileName in this._extraLibs) { - // static extra lib - text = this._extraLibs[fileName]; + // extra lib + text = this._extraLibs[fileName].content; } else if (fileName === DEFAULT_LIB.NAME) { text = DEFAULT_LIB.CONTENTS; @@ -196,11 +199,15 @@ export class TypeScriptWorker implements ts.LanguageServiceHost { getEmitOutput(fileName: string): Promise { return Promise.resolve(this._languageService.getEmitOutput(fileName)); } + + updateExtraLibs(extraLibs: IExtraLibs) { + this._extraLibs = extraLibs; + } } export interface ICreateData { compilerOptions: ts.CompilerOptions; - extraLibs: { [path: string]: string }; + extraLibs: IExtraLibs; } export function create(ctx: IWorkerContext, createData: ICreateData): TypeScriptWorker { diff --git a/src/workerManager.ts b/src/workerManager.ts index a99bc28a..666c9fc9 100644 --- a/src/workerManager.ts +++ b/src/workerManager.ts @@ -17,6 +17,8 @@ export class WorkerManager { private _idleCheckInterval: number; private _lastUsedTime: number; private _configChangeListener: IDisposable; + private _updateExtraLibsToken: number; + private _extraLibsChangeListener: IDisposable; private _worker: monaco.editor.MonacoWebWorker; private _client: Promise; @@ -28,6 +30,8 @@ export class WorkerManager { this._idleCheckInterval = setInterval(() => this._checkIfIdle(), 30 * 1000); this._lastUsedTime = 0; this._configChangeListener = this._defaults.onDidChange(() => this._stopWorker()); + this._updateExtraLibsToken = 0; + this._extraLibsChangeListener = this._defaults.onDidExtraLibsChange(() => this._updateExtraLibs()); } private _stopWorker(): void { @@ -41,9 +45,23 @@ export class WorkerManager { dispose(): void { clearInterval(this._idleCheckInterval); this._configChangeListener.dispose(); + this._extraLibsChangeListener.dispose(); this._stopWorker(); } + private async _updateExtraLibs(): Promise { + if (!this._worker) { + return; + } + const myToken = ++this._updateExtraLibsToken; + const proxy = await this._worker.getProxy(); + if (this._updateExtraLibsToken !== myToken) { + // avoid multiple calls + return; + } + proxy.updateExtraLibs(this._defaults.getExtraLibs()); + } + private _checkIfIdle(): void { if (!this._worker) { return;