From e39fa719bcacf94bda5e4dd8b6c7d308573ee35d Mon Sep 17 00:00:00 2001 From: placatus Date: Wed, 6 Feb 2019 00:50:50 +0200 Subject: [PATCH 1/5] Optimize how external libs are handled and allow for custom languages: * Adding/removing extra libs does not trigger a full worker refresh * Manual control over when the extra libs are sent to the worker * Adding a new lib with the same name replaces it inplace Also included, the capability to define custom languages --- scripts/bundle.js | 13 +++- src/monaco.contribution.ts | 131 ++++++++++++++++++++++++++++++------- src/monaco.d.ts | 15 +++++ src/tsMode.ts | 35 +++------- src/tsWorker.ts | 16 +++-- src/tsconfig.esm.json | 8 ++- src/tsconfig.json | 8 ++- 7 files changed, 167 insertions(+), 59 deletions(-) diff --git a/scripts/bundle.js b/scripts/bundle.js index e195e33d..6f39f345 100644 --- a/scripts/bundle.js +++ b/scripts/bundle.js @@ -29,6 +29,8 @@ bundleOne('monaco.contribution'); bundleOne('tsMode'); bundleOne('tsWorker'); +updateImports('monaco.contribution'); + function bundleOne(moduleId, exclude) { requirejs.optimize({ baseUrl: 'release/dev/', @@ -36,7 +38,8 @@ function bundleOne(moduleId, exclude) { out: 'release/min/' + moduleId + '.js', exclude: exclude, paths: { - 'vs/language/typescript': REPO_ROOT + '/release/dev' + 'vs/language/typescript': REPO_ROOT + '/release/dev', + 'vs/basic-languages': REPO_ROOT + '/node_modules/monaco-languages/release/dev' }, optimize: 'none' }, function(buildResponse) { @@ -53,3 +56,11 @@ function bundleOne(moduleId, exclude) { fs.writeFileSync(filePath, BUNDLED_FILE_HEADER + result.code); }) } + +function updateImports(moduleId) { + console.log(`ESM: updating relative imports paths for ${moduleId}...`); + const filePath = path.join(REPO_ROOT, 'release/esm/' + moduleId + '.js'); + var fileContents = fs.readFileSync(filePath).toString(); + fileContents = fileContents.replace(/vs\/basic-languages\//g, "../../basic-languages/"); + fs.writeFileSync(filePath, fileContents); +} \ No newline at end of file diff --git a/src/monaco.contribution.ts b/src/monaco.contribution.ts index 1e122b9f..6ef44379 100644 --- a/src/monaco.contribution.ts +++ b/src/monaco.contribution.ts @@ -5,6 +5,8 @@ 'use strict'; import * as mode from './tsMode'; +import * as tsDefinitions from 'vs/basic-languages/typescript/typescript'; +import * as jsDefinitions from 'vs/basic-languages/javascript/javascript'; import Emitter = monaco.Emitter; import IEvent = monaco.IEvent; @@ -15,17 +17,20 @@ import IDisposable = monaco.IDisposable; export class LanguageServiceDefaultsImpl implements monaco.languages.typescript.LanguageServiceDefaults { private _onDidChange = new Emitter(); - private _extraLibs: { [path: string]: string }; + private _extraLibs: { [path: string]: { content: string, version: number } }; private _workerMaxIdleTime: number; private _eagerModelSync: boolean; private _compilerOptions: monaco.languages.typescript.CompilerOptions; private _diagnosticsOptions: monaco.languages.typescript.DiagnosticsOptions; + private _languageId: string; + private _eagerExtraLibSync: boolean = true; - constructor(compilerOptions: monaco.languages.typescript.CompilerOptions, diagnosticsOptions: monaco.languages.typescript.DiagnosticsOptions) { + constructor(langualgeId: string, 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._languageId = langualgeId; } get onDidChange(): IEvent { @@ -46,21 +51,44 @@ export class LanguageServiceDefaultsImpl implements monaco.languages.typescript. } if (this._extraLibs[filePath]) { - throw new Error(`${filePath} already a extra lib`); + this._extraLibs[filePath].version++; + this._extraLibs[filePath].content = content; + } else { + this._extraLibs[filePath] = { + content: content, + version: 1 + }; + } + if (this._eagerExtraLibSync) { + this.syncExtraLibs(); } - - this._extraLibs[filePath] = content; - this._onDidChange.fire(this); return { dispose: () => { - if (delete this._extraLibs[filePath]) { - this._onDidChange.fire(this); + if (delete this._extraLibs[filePath] && this._eagerExtraLibSync) { + this.syncExtraLibs(); } } }; } + async syncExtraLibs() { + try { + let worker; + // we don't care if the get language worker fails. + // This happens because the worker initialzies much slower than the addExtraLib calls + try { + worker = await getLanguageWorker(this._languageId); + } catch (ignored) { + return; + } + const client = await worker(""); + client.syncExtraLibs(this._extraLibs); + } catch (error) { + console.error(error); + } + } + getCompilerOptions(): monaco.languages.typescript.CompilerOptions { return this._compilerOptions; } @@ -98,6 +126,10 @@ export class LanguageServiceDefaultsImpl implements monaco.languages.typescript. getEagerModelSync() { return this._eagerModelSync; } + + setEagerExtraLibSync(value: boolean) { + this._eagerExtraLibSync = value; + } } //#region enums copied from typescript to prevent loading the entire typescriptServices --- @@ -138,20 +170,57 @@ enum ModuleResolutionKind { } //#endregion -const typescriptDefaults = new LanguageServiceDefaultsImpl( - { allowNonTsExtensions: true, target: ScriptTarget.Latest }, - { noSemanticValidation: false, noSyntaxValidation: false }); +const languageDefaultOptions = { + javascript: { + compilerOptions: { allowNonTsExtensions: true, allowJs: true, target: ScriptTarget.Latest }, + diagnosticsOptions: { noSemanticValidation: true, noSyntaxValidation: false }, + }, + typescript: { + compilerOptions: { allowNonTsExtensions: true, target: ScriptTarget.Latest }, + diagnosticsOptions: { noSemanticValidation: false, noSyntaxValidation: false } + } +} + +const languageDefaults: { [name: string]: LanguageServiceDefaultsImpl } = {}; -const javascriptDefaults = new LanguageServiceDefaultsImpl( - { allowNonTsExtensions: true, allowJs: true, target: ScriptTarget.Latest }, - { noSemanticValidation: true, noSyntaxValidation: false }); +function setupLanguageServiceDefaults(languageId, isTypescript) { + const languageOptions = languageDefaultOptions[isTypescript ? "typescript" : "javascript"] + languageDefaults[languageId] = new LanguageServiceDefaultsImpl(languageId, languageOptions.compilerOptions, languageOptions.diagnosticsOptions); +} + +setupLanguageServiceDefaults("typescript", true); +setupLanguageServiceDefaults("javascript", false); function getTypeScriptWorker(): Promise { - return getMode().then(mode => mode.getTypeScriptWorker()); + return getLanguageWorker("typescript"); } function getJavaScriptWorker(): Promise { - return getMode().then(mode => mode.getJavaScriptWorker()); + return getLanguageWorker("javascript"); +} + +function getLanguageWorker(languageName: string): Promise { + return getMode().then(mode => mode.getNamedLanguageWorker(languageName)); +} + +function getLanguageDefaults(languageName: string): LanguageServiceDefaultsImpl { + return languageDefaults[languageName]; +} + +function setupNamedLanguage(languageDefinition: monaco.languages.ILanguageExtensionPoint, isTypescript: boolean, registerLanguage?: boolean): void { + if (registerLanguage) { + monaco.languages.register(languageDefinition); + + const langageConfig = isTypescript ? tsDefinitions : jsDefinitions; + monaco.languages.setMonarchTokensProvider(languageDefinition.id, langageConfig.language); + monaco.languages.setLanguageConfiguration(languageDefinition.id, langageConfig.conf); + } + + setupLanguageServiceDefaults(languageDefinition.id, isTypescript); + + monaco.languages.onLanguage(languageDefinition.id, () => { + return getMode().then(mode => mode.setupNamedLanguage(languageDefinition.id, isTypescript, languageDefaults[languageDefinition.id])); + }); } // Export API @@ -162,10 +231,13 @@ function createAPI(): typeof monaco.languages.typescript { NewLineKind: NewLineKind, ScriptTarget: ScriptTarget, ModuleResolutionKind: ModuleResolutionKind, - typescriptDefaults: typescriptDefaults, - javascriptDefaults: javascriptDefaults, + typescriptDefaults: getLanguageDefaults("typescript"), + javascriptDefaults: getLanguageDefaults("javascript"), + getLanguageDefaults: getLanguageDefaults, getTypeScriptWorker: getTypeScriptWorker, - getJavaScriptWorker: getJavaScriptWorker + getJavaScriptWorker: getJavaScriptWorker, + getLanguageWorker: getLanguageWorker, + setupNamedLanguage: setupNamedLanguage } } monaco.languages.typescript = createAPI(); @@ -176,9 +248,18 @@ function getMode(): Promise { return import('./tsMode'); } -monaco.languages.onLanguage('typescript', () => { - return getMode().then(mode => mode.setupTypeScript(typescriptDefaults)); -}); -monaco.languages.onLanguage('javascript', () => { - return getMode().then(mode => mode.setupJavaScript(javascriptDefaults)); -}); +setupNamedLanguage({ + id: 'typescript', + extensions: ['.ts', '.tsx'], + aliases: ['TypeScript', 'ts', 'typescript'], + mimetypes: ['text/typescript'] +}, true); + +setupNamedLanguage({ + id: 'javascript', + extensions: ['.js', '.es6', '.jsx'], + firstLine: '^#!.*\\bnode', + filenames: ['jakefile'], + aliases: ['JavaScript', 'javascript', 'js'], + mimetypes: ['text/javascript'], +}, false); \ No newline at end of file diff --git a/src/monaco.d.ts b/src/monaco.d.ts index 2a3bab0c..44ae26d7 100644 --- a/src/monaco.d.ts +++ b/src/monaco.d.ts @@ -164,6 +164,17 @@ declare module monaco.languages.typescript { * to the worker on start or restart. */ setEagerModelSync(value: boolean): void; + + /** + * Configure if the extra libs should be eagerly synced after each addExtraLibCall. + * This is true by default + */ + setEagerExtraLibSync(value: boolean): void; + + /** + * If EagerExtraLibSync is disabled, call this to trigger the changes. + */ + syncExtraLibs(): void; } export var typescriptDefaults: LanguageServiceDefaults; @@ -171,4 +182,8 @@ declare module monaco.languages.typescript { export var getTypeScriptWorker: () => Promise; export var getJavaScriptWorker: () => Promise; + + export var getLanguageWorker: (langaugeName: string) => Promise; + export var setupNamedLanguage: (languageDefinition: languages.ILanguageExtensionPoint, isTypescript: boolean, registerLanguage?: boolean) => void; + export var getLanguageDefaults: (languageName: string) => LanguageServiceDefaults; } diff --git a/src/tsMode.ts b/src/tsMode.ts index ce9caeca..e2fdf643 100644 --- a/src/tsMode.ts +++ b/src/tsMode.ts @@ -11,40 +11,23 @@ import * as languageFeatures from './languageFeatures'; import Uri = monaco.Uri; -let javaScriptWorker: (first: Uri, ...more: Uri[]) => Promise; -let typeScriptWorker: (first: Uri, ...more: Uri[]) => Promise; +let scriptWorkerMap: { [name: string]: (first: Uri, ...more: Uri[]) => Promise } = {}; -export function setupTypeScript(defaults: LanguageServiceDefaultsImpl): void { - typeScriptWorker = setupMode( +export function setupNamedLanguage(languageName: string, isTypescript: boolean, defaults: LanguageServiceDefaultsImpl): void { + scriptWorkerMap[languageName + "Worker"] = setupMode( defaults, - 'typescript' + languageName ); } -export function setupJavaScript(defaults: LanguageServiceDefaultsImpl): void { - javaScriptWorker = setupMode( - defaults, - 'javascript' - ); -} - -export function getJavaScriptWorker(): Promise<(first: Uri, ...more: Uri[]) => Promise> { - return new Promise((resolve, reject) => { - if (!javaScriptWorker) { - return reject("JavaScript not registered!"); - } - - resolve(javaScriptWorker); - }); -} - -export function getTypeScriptWorker(): Promise<(first: Uri, ...more: Uri[]) => Promise> { +export function getNamedLanguageWorker(languageName: string): Promise<(first: Uri, ...more: Uri[]) => Promise> { + let workerName = languageName + "Worker"; return new Promise((resolve, reject) => { - if (!typeScriptWorker) { - return reject("TypeScript not registered!"); + if (!scriptWorkerMap[workerName]) { + return reject(languageName + " not registered!"); } - resolve(typeScriptWorker); + resolve(scriptWorkerMap[workerName]); }); } diff --git a/src/tsWorker.ts b/src/tsWorker.ts index ef154b80..f5668fa1 100644 --- a/src/tsWorker.ts +++ b/src/tsWorker.ts @@ -24,7 +24,7 @@ export class TypeScriptWorker implements ts.LanguageServiceHost { // --- model sync ----------------------- private _ctx: IWorkerContext; - private _extraLibs: { [fileName: string]: string } = Object.create(null); + private _extraLibs: { [path: string]: { content: string, version: number } } = Object.create(null); private _languageService = ts.createLanguageService(this); private _compilerOptions: ts.CompilerOptions; @@ -59,9 +59,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 this._extraLibs[fileName].version.toString(); } } @@ -74,7 +76,7 @@ export class TypeScriptWorker implements ts.LanguageServiceHost { } else if (fileName in this._extraLibs) { // static extra lib - text = this._extraLibs[fileName]; + text = this._extraLibs[fileName].content; } else if (fileName === DEFAULT_LIB.NAME) { text = DEFAULT_LIB.CONTENTS; @@ -196,11 +198,15 @@ export class TypeScriptWorker implements ts.LanguageServiceHost { getEmitOutput(fileName: string): Promise { return Promise.resolve(this._languageService.getEmitOutput(fileName)); } + + syncExtraLibs(extraLibs: { [path: string]: { content: string, version: number } }) { + this._extraLibs = extraLibs; + } } export interface ICreateData { compilerOptions: ts.CompilerOptions; - extraLibs: { [path: string]: string }; + extraLibs: { [path: string]: { content: string, version: number } }; } export function create(ctx: IWorkerContext, createData: ICreateData): TypeScriptWorker { diff --git a/src/tsconfig.esm.json b/src/tsconfig.esm.json index b95798a9..546491e4 100644 --- a/src/tsconfig.esm.json +++ b/src/tsconfig.esm.json @@ -9,7 +9,13 @@ "es5", "es2015.collection", "es2015.promise" - ] + ], + "baseUrl": "", + "paths": { + "vs/basic-languages/*": [ + "../node_modules/monaco-languages/release/esm/*" + ] + }, }, "include": [ "**/*.ts" diff --git a/src/tsconfig.json b/src/tsconfig.json index f4ff52fe..418cf371 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -9,7 +9,13 @@ "es5", "es2015.collection", "es2015.promise" - ] + ], + "baseUrl": "", + "paths": { + "vs/basic-languages/*": [ + "../node_modules/monaco-languages/release/dev/*" + ] + }, }, "include": [ "**/*.ts" From 357f54c6529711a79ef10530ef8692f5a06a0615 Mon Sep 17 00:00:00 2001 From: placatus Date: Fri, 22 Feb 2019 15:16:21 +0200 Subject: [PATCH 2/5] Trigger an event when the extra libs changed, redoing all colorization and diagnostics --- src/languageFeatures.ts | 7 ++++--- src/monaco.contribution.ts | 8 ++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/languageFeatures.ts b/src/languageFeatures.ts index 46248592..c8dd261b 100644 --- a/src/languageFeatures.ts +++ b/src/languageFeatures.ts @@ -134,14 +134,15 @@ export class DiagnostcsAdapter extends Adapter { } } }); - - this._disposables.push(this._defaults.onDidChange(() => { + let redoDiagosticsCallback = () => { // redo diagnostics when options change for (const model of monaco.editor.getModels()) { onModelRemoved(model); onModelAdd(model); } - })); + }; + this._disposables.push(this._defaults.onDidChange(redoDiagosticsCallback)); + this._disposables.push(this._defaults.onDidExtraLibsChange(redoDiagosticsCallback)); monaco.editor.getModels().forEach(onModelAdd); } diff --git a/src/monaco.contribution.ts b/src/monaco.contribution.ts index 6ef44379..67318e89 100644 --- a/src/monaco.contribution.ts +++ b/src/monaco.contribution.ts @@ -17,6 +17,8 @@ import IDisposable = monaco.IDisposable; export class LanguageServiceDefaultsImpl implements monaco.languages.typescript.LanguageServiceDefaults { private _onDidChange = new Emitter(); + private _onDidExtraLibsChange = new Emitter(); + private _extraLibs: { [path: string]: { content: string, version: number } }; private _workerMaxIdleTime: number; private _eagerModelSync: boolean; @@ -37,6 +39,10 @@ export class LanguageServiceDefaultsImpl implements monaco.languages.typescript. return this._onDidChange.event; } + get onDidExtraLibsChange(): IEvent { + return this._onDidExtraLibsChange.event; + } + getExtraLibs(): { [path: string]: string; } { const result = Object.create(null); for (var key in this._extraLibs) { @@ -84,6 +90,8 @@ export class LanguageServiceDefaultsImpl implements monaco.languages.typescript. } const client = await worker(""); client.syncExtraLibs(this._extraLibs); + // let all listeners know that the extra libs have changed + this._onDidExtraLibsChange.fire(this); } catch (error) { console.error(error); } From 292108c5ee680df61aa87a79319caf4713aa6e36 Mon Sep 17 00:00:00 2001 From: placatus Date: Fri, 22 Feb 2019 17:29:43 +0200 Subject: [PATCH 3/5] Fixed creating the LanguageServiceDefaultsImpl twice --- src/monaco.contribution.ts | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/monaco.contribution.ts b/src/monaco.contribution.ts index 67318e89..ee5972d2 100644 --- a/src/monaco.contribution.ts +++ b/src/monaco.contribution.ts @@ -196,8 +196,21 @@ function setupLanguageServiceDefaults(languageId, isTypescript) { languageDefaults[languageId] = new LanguageServiceDefaultsImpl(languageId, languageOptions.compilerOptions, languageOptions.diagnosticsOptions); } -setupLanguageServiceDefaults("typescript", true); -setupLanguageServiceDefaults("javascript", false); +setupNamedLanguage({ + id: 'typescript', + extensions: ['.ts', '.tsx'], + aliases: ['TypeScript', 'ts', 'typescript'], + mimetypes: ['text/typescript'] +}, true, true); + +setupNamedLanguage({ + id: 'javascript', + extensions: ['.js', '.es6', '.jsx'], + firstLine: '^#!.*\\bnode', + filenames: ['jakefile'], + aliases: ['JavaScript', 'javascript', 'js'], + mimetypes: ['text/javascript'], +}, false, true); function getTypeScriptWorker(): Promise { return getLanguageWorker("typescript"); @@ -255,19 +268,3 @@ monaco.languages.typescript = createAPI(); function getMode(): Promise { return import('./tsMode'); } - -setupNamedLanguage({ - id: 'typescript', - extensions: ['.ts', '.tsx'], - aliases: ['TypeScript', 'ts', 'typescript'], - mimetypes: ['text/typescript'] -}, true); - -setupNamedLanguage({ - id: 'javascript', - extensions: ['.js', '.es6', '.jsx'], - firstLine: '^#!.*\\bnode', - filenames: ['jakefile'], - aliases: ['JavaScript', 'javascript', 'js'], - mimetypes: ['text/javascript'], -}, false); \ No newline at end of file From 6c73d7f7082cbf05db48b5742596f8eacf579e0d Mon Sep 17 00:00:00 2001 From: placatus Date: Fri, 1 Mar 2019 00:01:35 +0200 Subject: [PATCH 4/5] Fixed typos and other improvements per code review --- scripts/bundle.js | 3 +-- src/languageFeatures.ts | 2 +- src/monaco.contribution.ts | 29 +++++++++++++++++++---------- src/monaco.d.ts | 2 +- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/scripts/bundle.js b/scripts/bundle.js index 6f39f345..bb9bfe13 100644 --- a/scripts/bundle.js +++ b/scripts/bundle.js @@ -58,9 +58,8 @@ function bundleOne(moduleId, exclude) { } function updateImports(moduleId) { - console.log(`ESM: updating relative imports paths for ${moduleId}...`); const filePath = path.join(REPO_ROOT, 'release/esm/' + moduleId + '.js'); - var fileContents = fs.readFileSync(filePath).toString(); + let fileContents = fs.readFileSync(filePath).toString(); fileContents = fileContents.replace(/vs\/basic-languages\//g, "../../basic-languages/"); fs.writeFileSync(filePath, fileContents); } \ No newline at end of file diff --git a/src/languageFeatures.ts b/src/languageFeatures.ts index c8dd261b..fed6a084 100644 --- a/src/languageFeatures.ts +++ b/src/languageFeatures.ts @@ -134,7 +134,7 @@ export class DiagnostcsAdapter extends Adapter { } } }); - let redoDiagosticsCallback = () => { + const redoDiagosticsCallback = () => { // redo diagnostics when options change for (const model of monaco.editor.getModels()) { onModelRemoved(model); diff --git a/src/monaco.contribution.ts b/src/monaco.contribution.ts index ee5972d2..336be899 100644 --- a/src/monaco.contribution.ts +++ b/src/monaco.contribution.ts @@ -27,12 +27,12 @@ export class LanguageServiceDefaultsImpl implements monaco.languages.typescript. private _languageId: string; private _eagerExtraLibSync: boolean = true; - constructor(langualgeId: string, compilerOptions: monaco.languages.typescript.CompilerOptions, diagnosticsOptions: monaco.languages.typescript.DiagnosticsOptions) { + constructor(languageId: string, 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._languageId = langualgeId; + this._languageId = languageId; } get onDidChange(): IEvent { @@ -57,12 +57,14 @@ export class LanguageServiceDefaultsImpl implements monaco.languages.typescript. } if (this._extraLibs[filePath]) { - this._extraLibs[filePath].version++; - this._extraLibs[filePath].content = content; + if(this._extraLibs[filePath].content !== content) { + this._extraLibs[filePath].version++; + this._extraLibs[filePath].content = content; + } } else { this._extraLibs[filePath] = { content: content, - version: 1 + version: 1, }; } if (this._eagerExtraLibSync) { @@ -78,12 +80,14 @@ export class LanguageServiceDefaultsImpl implements monaco.languages.typescript. }; } - async syncExtraLibs() { + async syncExtraLibs(): Promise { try { let worker; - // we don't care if the get language worker fails. - // This happens because the worker initialzies much slower than the addExtraLib calls try { + // we don't care if the get language worker fails. + // This happens if addExtraLib is called before the worker has initialized. + // however, when the worker has finished downloading and initializes, + // it does so with the latest extraLibs so no sync issue can appear worker = await getLanguageWorker(this._languageId); } catch (ignored) { return; @@ -191,8 +195,13 @@ const languageDefaultOptions = { const languageDefaults: { [name: string]: LanguageServiceDefaultsImpl } = {}; -function setupLanguageServiceDefaults(languageId, isTypescript) { - const languageOptions = languageDefaultOptions[isTypescript ? "typescript" : "javascript"] +/** + * Generate the LanguageServiceDefaults for a new langauage with the given name + * @param languageId Name of the language + * @param isTypescriptBased Whether the language inherits from a typescript base or a javascript one + */ +function setupLanguageServiceDefaults(languageId: string, isTypescriptBased: boolean) { + const languageOptions = isTypescriptBased ? languageDefaultOptions.typescript : languageDefaultOptions.javascript; languageDefaults[languageId] = new LanguageServiceDefaultsImpl(languageId, languageOptions.compilerOptions, languageOptions.diagnosticsOptions); } diff --git a/src/monaco.d.ts b/src/monaco.d.ts index 44ae26d7..cc696a82 100644 --- a/src/monaco.d.ts +++ b/src/monaco.d.ts @@ -174,7 +174,7 @@ declare module monaco.languages.typescript { /** * If EagerExtraLibSync is disabled, call this to trigger the changes. */ - syncExtraLibs(): void; + syncExtraLibs(): Promise; } export var typescriptDefaults: LanguageServiceDefaults; From 6b2271c1c1e74fd93559da537984ae0cb2a9f272 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 4 Mar 2019 15:09:02 +0100 Subject: [PATCH 5/5] Changes for PR #30 --- scripts/bundle.js | 12 +-- src/languageFeatures.ts | 7 +- src/monaco.contribution.ts | 193 ++++++++++++++----------------------- src/monaco.d.ts | 15 --- src/tsMode.ts | 35 +++++-- src/tsWorker.ts | 13 +-- src/tsconfig.esm.json | 8 +- src/tsconfig.json | 8 +- src/workerManager.ts | 18 ++++ 9 files changed, 128 insertions(+), 181 deletions(-) diff --git a/scripts/bundle.js b/scripts/bundle.js index bb9bfe13..e195e33d 100644 --- a/scripts/bundle.js +++ b/scripts/bundle.js @@ -29,8 +29,6 @@ bundleOne('monaco.contribution'); bundleOne('tsMode'); bundleOne('tsWorker'); -updateImports('monaco.contribution'); - function bundleOne(moduleId, exclude) { requirejs.optimize({ baseUrl: 'release/dev/', @@ -38,8 +36,7 @@ function bundleOne(moduleId, exclude) { out: 'release/min/' + moduleId + '.js', exclude: exclude, paths: { - 'vs/language/typescript': REPO_ROOT + '/release/dev', - 'vs/basic-languages': REPO_ROOT + '/node_modules/monaco-languages/release/dev' + 'vs/language/typescript': REPO_ROOT + '/release/dev' }, optimize: 'none' }, function(buildResponse) { @@ -56,10 +53,3 @@ function bundleOne(moduleId, exclude) { fs.writeFileSync(filePath, BUNDLED_FILE_HEADER + result.code); }) } - -function updateImports(moduleId) { - const filePath = path.join(REPO_ROOT, 'release/esm/' + moduleId + '.js'); - let fileContents = fs.readFileSync(filePath).toString(); - fileContents = fileContents.replace(/vs\/basic-languages\//g, "../../basic-languages/"); - fs.writeFileSync(filePath, fileContents); -} \ No newline at end of file diff --git a/src/languageFeatures.ts b/src/languageFeatures.ts index fed6a084..ccc50c85 100644 --- a/src/languageFeatures.ts +++ b/src/languageFeatures.ts @@ -134,15 +134,16 @@ export class DiagnostcsAdapter extends Adapter { } } }); - const redoDiagosticsCallback = () => { + + const recomputeDiagostics = () => { // redo diagnostics when options change for (const model of monaco.editor.getModels()) { onModelRemoved(model); onModelAdd(model); } }; - this._disposables.push(this._defaults.onDidChange(redoDiagosticsCallback)); - this._disposables.push(this._defaults.onDidExtraLibsChange(redoDiagosticsCallback)); + 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 336be899..95fe8950 100644 --- a/src/monaco.contribution.ts +++ b/src/monaco.contribution.ts @@ -5,8 +5,6 @@ 'use strict'; import * as mode from './tsMode'; -import * as tsDefinitions from 'vs/basic-languages/typescript/typescript'; -import * as jsDefinitions from 'vs/basic-languages/javascript/javascript'; import Emitter = monaco.Emitter; import IEvent = monaco.IEvent; @@ -14,41 +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 _onDidExtraLibsChange = new Emitter(); + private _onDidChange = new Emitter(); + private _onDidExtraLibsChange = new Emitter(); - private _extraLibs: { [path: string]: { content: string, version: number } }; + private _extraLibs: IExtraLibs; private _workerMaxIdleTime: number; private _eagerModelSync: boolean; private _compilerOptions: monaco.languages.typescript.CompilerOptions; private _diagnosticsOptions: monaco.languages.typescript.DiagnosticsOptions; - private _languageId: string; - private _eagerExtraLibSync: boolean = true; + private _onDidExtraLibsChangeTimeout: number; - constructor(languageId: string, compilerOptions: monaco.languages.typescript.CompilerOptions, diagnosticsOptions: monaco.languages.typescript.DiagnosticsOptions) { + 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._languageId = languageId; + this._onDidExtraLibsChangeTimeout = -1; } - get onDidChange(): IEvent { + get onDidChange(): IEvent { return this._onDidChange.event; } - get onDidExtraLibsChange(): IEvent { + get onDidExtraLibsChange(): IEvent { return this._onDidExtraLibsChange.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); + getExtraLibs(): IExtraLibs { + return this._extraLibs; } addExtraLib(content: string, filePath?: string): IDisposable { @@ -56,49 +58,49 @@ export class LanguageServiceDefaultsImpl implements monaco.languages.typescript. filePath = `ts:extralib-${Date.now()}`; } - if (this._extraLibs[filePath]) { - if(this._extraLibs[filePath].content !== content) { - this._extraLibs[filePath].version++; - this._extraLibs[filePath].content = content; - } - } else { - this._extraLibs[filePath] = { - content: content, - version: 1, + if (this._extraLibs[filePath] && this._extraLibs[filePath].content === content) { + // no-op, there already exists an extra lib with this content + return { + dispose: () => { } }; } - if (this._eagerExtraLibSync) { - this.syncExtraLibs(); + + let myVersion = 1; + if (this._extraLibs[filePath]) { + myVersion = this._extraLibs[filePath].version + 1; } + this._extraLibs[filePath] = { + content: content, + version: myVersion, + }; + this._fireOnDidExtraLibsChangeSoon(); + return { dispose: () => { - if (delete this._extraLibs[filePath] && this._eagerExtraLibSync) { - this.syncExtraLibs(); + let extraLib = this._extraLibs[filePath]; + if (!extraLib) { + return; + } + if (extraLib.version !== myVersion) { + return; } + + delete this._extraLibs[filePath]; + this._fireOnDidExtraLibsChangeSoon(); } }; } - async syncExtraLibs(): Promise { - try { - let worker; - try { - // we don't care if the get language worker fails. - // This happens if addExtraLib is called before the worker has initialized. - // however, when the worker has finished downloading and initializes, - // it does so with the latest extraLibs so no sync issue can appear - worker = await getLanguageWorker(this._languageId); - } catch (ignored) { - return; - } - const client = await worker(""); - client.syncExtraLibs(this._extraLibs); - // let all listeners know that the extra libs have changed - this._onDidExtraLibsChange.fire(this); - } catch (error) { - console.error(error); + 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 { @@ -107,7 +109,7 @@ export class LanguageServiceDefaultsImpl implements monaco.languages.typescript. 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 { @@ -116,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 { @@ -138,10 +140,6 @@ export class LanguageServiceDefaultsImpl implements monaco.languages.typescript. getEagerModelSync() { return this._eagerModelSync; } - - setEagerExtraLibSync(value: boolean) { - this._eagerExtraLibSync = value; - } } //#region enums copied from typescript to prevent loading the entire typescriptServices --- @@ -182,75 +180,20 @@ enum ModuleResolutionKind { } //#endregion -const languageDefaultOptions = { - javascript: { - compilerOptions: { allowNonTsExtensions: true, allowJs: true, target: ScriptTarget.Latest }, - diagnosticsOptions: { noSemanticValidation: true, noSyntaxValidation: false }, - }, - typescript: { - compilerOptions: { allowNonTsExtensions: true, target: ScriptTarget.Latest }, - diagnosticsOptions: { noSemanticValidation: false, noSyntaxValidation: false } - } -} - -const languageDefaults: { [name: string]: LanguageServiceDefaultsImpl } = {}; - -/** - * Generate the LanguageServiceDefaults for a new langauage with the given name - * @param languageId Name of the language - * @param isTypescriptBased Whether the language inherits from a typescript base or a javascript one - */ -function setupLanguageServiceDefaults(languageId: string, isTypescriptBased: boolean) { - const languageOptions = isTypescriptBased ? languageDefaultOptions.typescript : languageDefaultOptions.javascript; - languageDefaults[languageId] = new LanguageServiceDefaultsImpl(languageId, languageOptions.compilerOptions, languageOptions.diagnosticsOptions); -} +const typescriptDefaults = new LanguageServiceDefaultsImpl( + { allowNonTsExtensions: true, target: ScriptTarget.Latest }, + { noSemanticValidation: false, noSyntaxValidation: false }); -setupNamedLanguage({ - id: 'typescript', - extensions: ['.ts', '.tsx'], - aliases: ['TypeScript', 'ts', 'typescript'], - mimetypes: ['text/typescript'] -}, true, true); - -setupNamedLanguage({ - id: 'javascript', - extensions: ['.js', '.es6', '.jsx'], - firstLine: '^#!.*\\bnode', - filenames: ['jakefile'], - aliases: ['JavaScript', 'javascript', 'js'], - mimetypes: ['text/javascript'], -}, false, true); +const javascriptDefaults = new LanguageServiceDefaultsImpl( + { allowNonTsExtensions: true, allowJs: true, target: ScriptTarget.Latest }, + { noSemanticValidation: true, noSyntaxValidation: false }); function getTypeScriptWorker(): Promise { - return getLanguageWorker("typescript"); + return getMode().then(mode => mode.getTypeScriptWorker()); } function getJavaScriptWorker(): Promise { - return getLanguageWorker("javascript"); -} - -function getLanguageWorker(languageName: string): Promise { - return getMode().then(mode => mode.getNamedLanguageWorker(languageName)); -} - -function getLanguageDefaults(languageName: string): LanguageServiceDefaultsImpl { - return languageDefaults[languageName]; -} - -function setupNamedLanguage(languageDefinition: monaco.languages.ILanguageExtensionPoint, isTypescript: boolean, registerLanguage?: boolean): void { - if (registerLanguage) { - monaco.languages.register(languageDefinition); - - const langageConfig = isTypescript ? tsDefinitions : jsDefinitions; - monaco.languages.setMonarchTokensProvider(languageDefinition.id, langageConfig.language); - monaco.languages.setLanguageConfiguration(languageDefinition.id, langageConfig.conf); - } - - setupLanguageServiceDefaults(languageDefinition.id, isTypescript); - - monaco.languages.onLanguage(languageDefinition.id, () => { - return getMode().then(mode => mode.setupNamedLanguage(languageDefinition.id, isTypescript, languageDefaults[languageDefinition.id])); - }); + return getMode().then(mode => mode.getJavaScriptWorker()); } // Export API @@ -261,13 +204,10 @@ function createAPI(): typeof monaco.languages.typescript { NewLineKind: NewLineKind, ScriptTarget: ScriptTarget, ModuleResolutionKind: ModuleResolutionKind, - typescriptDefaults: getLanguageDefaults("typescript"), - javascriptDefaults: getLanguageDefaults("javascript"), - getLanguageDefaults: getLanguageDefaults, + typescriptDefaults: typescriptDefaults, + javascriptDefaults: javascriptDefaults, getTypeScriptWorker: getTypeScriptWorker, - getJavaScriptWorker: getJavaScriptWorker, - getLanguageWorker: getLanguageWorker, - setupNamedLanguage: setupNamedLanguage + getJavaScriptWorker: getJavaScriptWorker } } monaco.languages.typescript = createAPI(); @@ -277,3 +217,10 @@ monaco.languages.typescript = createAPI(); function getMode(): Promise { return import('./tsMode'); } + +monaco.languages.onLanguage('typescript', () => { + return getMode().then(mode => mode.setupTypeScript(typescriptDefaults)); +}); +monaco.languages.onLanguage('javascript', () => { + return getMode().then(mode => mode.setupJavaScript(javascriptDefaults)); +}); diff --git a/src/monaco.d.ts b/src/monaco.d.ts index cc696a82..2a3bab0c 100644 --- a/src/monaco.d.ts +++ b/src/monaco.d.ts @@ -164,17 +164,6 @@ declare module monaco.languages.typescript { * to the worker on start or restart. */ setEagerModelSync(value: boolean): void; - - /** - * Configure if the extra libs should be eagerly synced after each addExtraLibCall. - * This is true by default - */ - setEagerExtraLibSync(value: boolean): void; - - /** - * If EagerExtraLibSync is disabled, call this to trigger the changes. - */ - syncExtraLibs(): Promise; } export var typescriptDefaults: LanguageServiceDefaults; @@ -182,8 +171,4 @@ declare module monaco.languages.typescript { export var getTypeScriptWorker: () => Promise; export var getJavaScriptWorker: () => Promise; - - export var getLanguageWorker: (langaugeName: string) => Promise; - export var setupNamedLanguage: (languageDefinition: languages.ILanguageExtensionPoint, isTypescript: boolean, registerLanguage?: boolean) => void; - export var getLanguageDefaults: (languageName: string) => LanguageServiceDefaults; } diff --git a/src/tsMode.ts b/src/tsMode.ts index e2fdf643..ce9caeca 100644 --- a/src/tsMode.ts +++ b/src/tsMode.ts @@ -11,23 +11,40 @@ import * as languageFeatures from './languageFeatures'; import Uri = monaco.Uri; -let scriptWorkerMap: { [name: string]: (first: Uri, ...more: Uri[]) => Promise } = {}; +let javaScriptWorker: (first: Uri, ...more: Uri[]) => Promise; +let typeScriptWorker: (first: Uri, ...more: Uri[]) => Promise; -export function setupNamedLanguage(languageName: string, isTypescript: boolean, defaults: LanguageServiceDefaultsImpl): void { - scriptWorkerMap[languageName + "Worker"] = setupMode( +export function setupTypeScript(defaults: LanguageServiceDefaultsImpl): void { + typeScriptWorker = setupMode( defaults, - languageName + 'typescript' ); } -export function getNamedLanguageWorker(languageName: string): Promise<(first: Uri, ...more: Uri[]) => Promise> { - let workerName = languageName + "Worker"; +export function setupJavaScript(defaults: LanguageServiceDefaultsImpl): void { + javaScriptWorker = setupMode( + defaults, + 'javascript' + ); +} + +export function getJavaScriptWorker(): Promise<(first: Uri, ...more: Uri[]) => Promise> { + return new Promise((resolve, reject) => { + if (!javaScriptWorker) { + return reject("JavaScript not registered!"); + } + + resolve(javaScriptWorker); + }); +} + +export function getTypeScriptWorker(): Promise<(first: Uri, ...more: Uri[]) => Promise> { return new Promise((resolve, reject) => { - if (!scriptWorkerMap[workerName]) { - return reject(languageName + " not registered!"); + if (!typeScriptWorker) { + return reject("TypeScript not registered!"); } - resolve(scriptWorkerMap[workerName]); + resolve(typeScriptWorker); }); } diff --git a/src/tsWorker.ts b/src/tsWorker.ts index f5668fa1..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: { [path: string]: { content: string, version: number } } = Object.create(null); + private _extraLibs: IExtraLibs = Object.create(null); private _languageService = ts.createLanguageService(this); private _compilerOptions: ts.CompilerOptions; @@ -62,8 +63,8 @@ export class TypeScriptWorker implements ts.LanguageServiceHost { } else if (this.isDefaultLibFileName(fileName)) { // default lib is static return '1'; - } else if(fileName in this._extraLibs) { - return this._extraLibs[fileName].version.toString(); + } else if (fileName in this._extraLibs) { + return String(this._extraLibs[fileName].version); } } @@ -75,7 +76,7 @@ export class TypeScriptWorker implements ts.LanguageServiceHost { text = model.getValue(); } else if (fileName in this._extraLibs) { - // static extra lib + // extra lib text = this._extraLibs[fileName].content; } else if (fileName === DEFAULT_LIB.NAME) { @@ -199,14 +200,14 @@ export class TypeScriptWorker implements ts.LanguageServiceHost { return Promise.resolve(this._languageService.getEmitOutput(fileName)); } - syncExtraLibs(extraLibs: { [path: string]: { content: string, version: number } }) { + updateExtraLibs(extraLibs: IExtraLibs) { this._extraLibs = extraLibs; } } export interface ICreateData { compilerOptions: ts.CompilerOptions; - extraLibs: { [path: string]: { content: string, version: number } }; + extraLibs: IExtraLibs; } export function create(ctx: IWorkerContext, createData: ICreateData): TypeScriptWorker { diff --git a/src/tsconfig.esm.json b/src/tsconfig.esm.json index 546491e4..b95798a9 100644 --- a/src/tsconfig.esm.json +++ b/src/tsconfig.esm.json @@ -9,13 +9,7 @@ "es5", "es2015.collection", "es2015.promise" - ], - "baseUrl": "", - "paths": { - "vs/basic-languages/*": [ - "../node_modules/monaco-languages/release/esm/*" - ] - }, + ] }, "include": [ "**/*.ts" diff --git a/src/tsconfig.json b/src/tsconfig.json index 418cf371..f4ff52fe 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -9,13 +9,7 @@ "es5", "es2015.collection", "es2015.promise" - ], - "baseUrl": "", - "paths": { - "vs/basic-languages/*": [ - "../node_modules/monaco-languages/release/dev/*" - ] - }, + ] }, "include": [ "**/*.ts" 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;