From 0b344d31d05fd40f83d2e70ab1d45cef6acf72f0 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 7 Sep 2020 13:12:30 +0200 Subject: [PATCH] Run prettier --- .vscode/settings.json | 2 +- LICENSE.md | 42 +- README.md | 76 +- monaco.d.ts | 2 - scripts/bundle.js | 124 ++- scripts/dts.js | 5 +- scripts/release.js | 14 +- src/fillers/monaco-editor-core-amd.ts | 4 +- src/fillers/vscode-nls.ts | 14 +- src/json.worker.ts | 2 +- src/jsonMode.ts | 266 +++--- src/jsonWorker.ts | 267 +++--- src/languageFeatures.ts | 1258 ++++++++++++++----------- src/monaco.contribution.ts | 384 ++++---- src/tokenization.ts | 336 ++++--- src/tsconfig.esm.json | 28 +- src/tsconfig.json | 28 +- src/workerManager.ts | 178 ++-- test/index.html | 244 ++--- 19 files changed, 1814 insertions(+), 1460 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 1dc255b6..e25fee75 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,4 +6,4 @@ "**/release": true, "**/out": true } -} \ No newline at end of file +} diff --git a/LICENSE.md b/LICENSE.md index f8a94f6e..5ae193c9 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,21 +1,21 @@ -The MIT License (MIT) - -Copyright (c) Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +The MIT License (MIT) + +Copyright (c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 516d0e82..60fa1e16 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,39 @@ -# Monaco JSON - -JSON language plugin for the Monaco Editor. It provides the following features when editing JSON files: -* Code completion, based on JSON schemas or by looking at similar objects in the same file -* Hovers, based on JSON schemas -* Validation: Syntax errors and schema validation -* Formatting -* Document Symbols -* Syntax highlighting -* Color decorators for all properties matching a schema containing `format: "color-hex"'` (non-standard schema extension) - -Schemas can be provided by configuration. See [here](https://github.com/Microsoft/monaco-json/blob/master/monaco.d.ts) -for the API that the JSON plugin offers to configure the JSON language support. - -Internally the JSON plugin uses the [vscode-json-languageservice](https://github.com/Microsoft/vscode-json-languageservice) -node module, providing the implementation of the features listed above. The same module is also used -in [Visual Studio Code](https://github.com/Microsoft/vscode) to power the JSON editing experience. - -## Issues - -Please file issues concerning `monaco-json` in the [`monaco-editor` repository](https://github.com/Microsoft/monaco-editor/issues). - -## Installing - -This npm module is bundled and distributed in the [monaco-editor](https://www.npmjs.com/package/monaco-editor) npm module. - -## Development - -* `git clone https://github.com/Microsoft/monaco-json` -* `npm install .` -* compile with `npm run compile` -* watch with `npm run watch` -* `npm run prepublishOnly` -* open `$/monaco-json/test/index.html` in your favorite browser. - -## License -[MIT](https://github.com/Microsoft/monaco-json/blob/master/LICENSE.md) +# Monaco JSON + +JSON language plugin for the Monaco Editor. It provides the following features when editing JSON files: + +- Code completion, based on JSON schemas or by looking at similar objects in the same file +- Hovers, based on JSON schemas +- Validation: Syntax errors and schema validation +- Formatting +- Document Symbols +- Syntax highlighting +- Color decorators for all properties matching a schema containing `format: "color-hex"'` (non-standard schema extension) + +Schemas can be provided by configuration. See [here](https://github.com/Microsoft/monaco-json/blob/master/monaco.d.ts) +for the API that the JSON plugin offers to configure the JSON language support. + +Internally the JSON plugin uses the [vscode-json-languageservice](https://github.com/Microsoft/vscode-json-languageservice) +node module, providing the implementation of the features listed above. The same module is also used +in [Visual Studio Code](https://github.com/Microsoft/vscode) to power the JSON editing experience. + +## Issues + +Please file issues concerning `monaco-json` in the [`monaco-editor` repository](https://github.com/Microsoft/monaco-editor/issues). + +## Installing + +This npm module is bundled and distributed in the [monaco-editor](https://www.npmjs.com/package/monaco-editor) npm module. + +## Development + +- `git clone https://github.com/Microsoft/monaco-json` +- `npm install .` +- compile with `npm run compile` +- watch with `npm run watch` +- `npm run prepublishOnly` +- open `$/monaco-json/test/index.html` in your favorite browser. + +## License + +[MIT](https://github.com/Microsoft/monaco-json/blob/master/LICENSE.md) diff --git a/monaco.d.ts b/monaco.d.ts index d84bc268..664598f7 100644 --- a/monaco.d.ts +++ b/monaco.d.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ declare namespace monaco.languages.json { - export interface DiagnosticsOptions { /** * If set, the validator will be enabled and perform syntax validation as well as schema based validation. @@ -87,5 +86,4 @@ declare namespace monaco.languages.json { setModeConfiguration(modeConfiguration: ModeConfiguration): void; } export const jsonDefaults: LanguageServiceDefaults; - } diff --git a/scripts/bundle.js b/scripts/bundle.js index 5ff3e7ac..ce748480 100644 --- a/scripts/bundle.js +++ b/scripts/bundle.js @@ -1,7 +1,7 @@ const requirejs = require('requirejs'); const path = require('path'); const fs = require('fs'); -const Terser = require("terser"); +const Terser = require('terser'); const helpers = require('monaco-plugin-helpers'); const REPO_ROOT = path.resolve(__dirname, '..'); @@ -25,52 +25,80 @@ bundleOne('jsonMode'); bundleOne('jsonWorker'); function bundleOne(moduleId) { - requirejs.optimize({ - baseUrl: 'out/amd/', - name: 'vs/language/json/' + moduleId, - out: 'release/dev/' + moduleId + '.js', - paths: { - 'vs/language/json': REPO_ROOT + '/out/amd', - 'vs/language/json/fillers/monaco-editor-core': REPO_ROOT + '/out/amd/fillers/monaco-editor-core-amd', + requirejs.optimize( + { + baseUrl: 'out/amd/', + name: 'vs/language/json/' + moduleId, + out: 'release/dev/' + moduleId + '.js', + paths: { + 'vs/language/json': REPO_ROOT + '/out/amd', + 'vs/language/json/fillers/monaco-editor-core': + REPO_ROOT + '/out/amd/fillers/monaco-editor-core-amd' + }, + optimize: 'none', + packages: [ + { + name: 'vscode-json-languageservice', + location: path.join( + REPO_ROOT, + 'node_modules/vscode-json-languageservice/lib/umd' + ), + main: 'jsonLanguageService' + }, + { + name: 'vscode-languageserver-types', + location: path.join( + REPO_ROOT, + 'node_modules/vscode-languageserver-types/lib/umd' + ), + main: 'main' + }, + { + name: 'vscode-languageserver-textdocument', + location: path.join( + REPO_ROOT, + 'node_modules/vscode-languageserver-textdocument/lib/umd' + ), + main: 'main' + }, + { + name: 'jsonc-parser', + location: path.join(REPO_ROOT, 'node_modules/jsonc-parser/lib/umd'), + main: 'main' + }, + { + name: 'vscode-uri', + location: path.join(REPO_ROOT, 'node_modules/vscode-uri/lib/umd'), + main: 'index' + }, + { + name: 'vscode-nls', + location: path.join(REPO_ROOT, '/out/amd/fillers'), + main: 'vscode-nls' + } + ] }, - optimize: 'none', - packages: [{ - name: 'vscode-json-languageservice', - location: path.join(REPO_ROOT, 'node_modules/vscode-json-languageservice/lib/umd'), - main: 'jsonLanguageService' - }, { - name: 'vscode-languageserver-types', - location: path.join(REPO_ROOT, 'node_modules/vscode-languageserver-types/lib/umd'), - main: 'main' - }, { - name: 'vscode-languageserver-textdocument', - location: path.join(REPO_ROOT, 'node_modules/vscode-languageserver-textdocument/lib/umd'), - main: 'main' - }, { - name: 'jsonc-parser', - location: path.join(REPO_ROOT, 'node_modules/jsonc-parser/lib/umd'), - main: 'main' - }, { - name: 'vscode-uri', - location: path.join(REPO_ROOT, 'node_modules/vscode-uri/lib/umd'), - main: 'index' - }, { - name: 'vscode-nls', - location: path.join(REPO_ROOT, '/out/amd/fillers'), - main: 'vscode-nls' - }] - }, async function (buildResponse) { - const devFilePath = path.join(REPO_ROOT, 'release/dev/' + moduleId + '.js'); - const minFilePath = path.join(REPO_ROOT, 'release/min/' + moduleId + '.js'); - const fileContents = fs.readFileSync(devFilePath).toString(); - console.log(`Minifying ${devFilePath}...`); - const result = await Terser.minify(fileContents, { - output: { - comments: 'some' - } - }); - console.log(`Done minifying ${devFilePath}.`); - try { fs.mkdirSync(path.join(REPO_ROOT, 'release/min')) } catch (err) { } - fs.writeFileSync(minFilePath, BUNDLED_FILE_HEADER + result.code); - }) + async function (buildResponse) { + const devFilePath = path.join( + REPO_ROOT, + 'release/dev/' + moduleId + '.js' + ); + const minFilePath = path.join( + REPO_ROOT, + 'release/min/' + moduleId + '.js' + ); + const fileContents = fs.readFileSync(devFilePath).toString(); + console.log(`Minifying ${devFilePath}...`); + const result = await Terser.minify(fileContents, { + output: { + comments: 'some' + } + }); + console.log(`Done minifying ${devFilePath}.`); + try { + fs.mkdirSync(path.join(REPO_ROOT, 'release/min')); + } catch (err) {} + fs.writeFileSync(minFilePath, BUNDLED_FILE_HEADER + result.code); + } + ); } diff --git a/scripts/dts.js b/scripts/dts.js index bcb06084..eb65bd8c 100644 --- a/scripts/dts.js +++ b/scripts/dts.js @@ -10,7 +10,10 @@ const REPO_ROOT = path.join(__dirname, '../'); const SRC_PATH = path.join(REPO_ROOT, 'out/amd/monaco.contribution.d.ts'); const DST_PATH = path.join(REPO_ROOT, 'monaco.d.ts'); -const lines = fs.readFileSync(SRC_PATH).toString().split(/\r\n|\r|\n/); +const lines = fs + .readFileSync(SRC_PATH) + .toString() + .split(/\r\n|\r|\n/); let result = [ `/*---------------------------------------------------------------------------------------------`, ` * Copyright (c) Microsoft Corporation. All rights reserved.`, diff --git a/scripts/release.js b/scripts/release.js index f4698b6c..b73abf08 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -12,19 +12,13 @@ helpers.packageESM({ repoRoot: REPO_ROOT, esmSource: 'out/esm', esmDestination: 'release/esm', - entryPoints: [ - 'monaco.contribution.js', - 'jsonMode.js', - 'json.worker.js' - ], + entryPoints: ['monaco.contribution.js', 'jsonMode.js', 'json.worker.js'], resolveAlias: { - 'vscode-nls': path.join(REPO_ROOT, "out/esm/fillers/vscode-nls.js") + 'vscode-nls': path.join(REPO_ROOT, 'out/esm/fillers/vscode-nls.js') }, - resolveSkip: [ - 'monaco-editor-core' - ], + resolveSkip: ['monaco-editor-core'], destinationFolderSimplification: { - 'node_modules': '_deps', + node_modules: '_deps', 'jsonc-parser/lib/esm': 'jsonc-parser', 'vscode-languageserver-types/lib/esm': 'vscode-languageserver-types', 'vscode-uri/lib/esm': 'vscode-uri', diff --git a/src/fillers/monaco-editor-core-amd.ts b/src/fillers/monaco-editor-core-amd.ts index bbc7e81a..59874efb 100644 --- a/src/fillers/monaco-editor-core-amd.ts +++ b/src/fillers/monaco-editor-core-amd.ts @@ -7,6 +7,6 @@ declare var define; -define([], function() { - return (self).monaco; +define([], function () { + return (self).monaco; }); diff --git a/src/fillers/vscode-nls.ts b/src/fillers/vscode-nls.ts index 80916897..09cb22e0 100644 --- a/src/fillers/vscode-nls.ts +++ b/src/fillers/vscode-nls.ts @@ -20,7 +20,7 @@ export interface LoadFunc { } function format(message: string, args: any[]): string { - let result:string; + let result: string; if (args.length === 0) { result = message; @@ -33,14 +33,18 @@ function format(message: string, args: any[]): string { return result; } -function localize(key: string | LocalizeInfo, message: string, ...args: any[]): string { +function localize( + key: string | LocalizeInfo, + message: string, + ...args: any[] +): string { return format(message, args); } export function loadMessageBundle(file?: string): LocalizeFunc { - return localize; + return localize; } export function config(opt?: Options | string): LoadFunc { - return loadMessageBundle; -} \ No newline at end of file + return loadMessageBundle; +} diff --git a/src/json.worker.ts b/src/json.worker.ts index e5bf0460..be7295be 100644 --- a/src/json.worker.ts +++ b/src/json.worker.ts @@ -9,6 +9,6 @@ import { JSONWorker } from './jsonWorker'; self.onmessage = () => { // ignore the first message worker.initialize((ctx, createData) => { - return new JSONWorker(ctx, createData) + return new JSONWorker(ctx, createData); }); }; diff --git a/src/jsonMode.ts b/src/jsonMode.ts index f24dec70..8cb276f8 100644 --- a/src/jsonMode.ts +++ b/src/jsonMode.ts @@ -1,109 +1,157 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { WorkerManager } from './workerManager'; -import type { JSONWorker } from './jsonWorker'; -import { LanguageServiceDefaults } from './monaco.contribution'; -import * as languageFeatures from './languageFeatures'; -import { createTokenizationSupport } from './tokenization'; -import { Uri, IDisposable, languages } from './fillers/monaco-editor-core' - -export function setupMode(defaults: LanguageServiceDefaults): IDisposable { - - const disposables: IDisposable[] = []; - const providers: IDisposable[] = []; - - const client = new WorkerManager(defaults); - disposables.push(client); - - const worker: languageFeatures.WorkerAccessor = (...uris: Uri[]): Promise => { - return client.getLanguageServiceWorker(...uris); - }; - - - function registerProviders(): void { - const { languageId, modeConfiguration } = defaults; - - disposeAll(providers); - - if (modeConfiguration.documentFormattingEdits) { - providers.push(languages.registerDocumentFormattingEditProvider(languageId, new languageFeatures.DocumentFormattingEditProvider(worker))); - } - if (modeConfiguration.documentRangeFormattingEdits) { - providers.push(languages.registerDocumentRangeFormattingEditProvider(languageId, new languageFeatures.DocumentRangeFormattingEditProvider(worker))); - } - if (modeConfiguration.completionItems) { - providers.push(languages.registerCompletionItemProvider(languageId, new languageFeatures.CompletionAdapter(worker))); - } - if (modeConfiguration.hovers) { - providers.push(languages.registerHoverProvider(languageId, new languageFeatures.HoverAdapter(worker))); - } - if (modeConfiguration.documentSymbols) { - providers.push(languages.registerDocumentSymbolProvider(languageId, new languageFeatures.DocumentSymbolAdapter(worker))); - } - if (modeConfiguration.tokens) { - providers.push(languages.setTokensProvider(languageId, createTokenizationSupport(true))); - } - if (modeConfiguration.colors) { - providers.push(languages.registerColorProvider(languageId, new languageFeatures.DocumentColorAdapter(worker))); - } - if (modeConfiguration.foldingRanges) { - providers.push(languages.registerFoldingRangeProvider(languageId, new languageFeatures.FoldingRangeAdapter(worker))); - } - if (modeConfiguration.diagnostics) { - providers.push(new languageFeatures.DiagnosticsAdapter(languageId, worker, defaults)); - } - if (modeConfiguration.selectionRanges) { - providers.push(languages.registerSelectionRangeProvider(languageId, new languageFeatures.SelectionRangeAdapter(worker))); - } - } - - registerProviders(); - - disposables.push(languages.setLanguageConfiguration(defaults.languageId, richEditConfiguration)); - - let modeConfiguration = defaults.modeConfiguration; - defaults.onDidChange((newDefaults) => { - if (newDefaults.modeConfiguration !== modeConfiguration) { - modeConfiguration = newDefaults.modeConfiguration; - registerProviders(); - } - }); - - disposables.push(asDisposable(providers)); - - return asDisposable(disposables); -} - -function asDisposable(disposables: IDisposable[]): IDisposable { - return { dispose: () => disposeAll(disposables) }; -} - -function disposeAll(disposables: IDisposable[]) { - while (disposables.length) { - disposables.pop().dispose(); - } -} - -const richEditConfiguration: languages.LanguageConfiguration = { - wordPattern: /(-?\d*\.\d\w*)|([^\[\{\]\}\:\"\,\s]+)/g, - - comments: { - lineComment: '//', - blockComment: ['/*', '*/'] - }, - - brackets: [ - ['{', '}'], - ['[', ']'] - ], - - autoClosingPairs: [ - { open: '{', close: '}', notIn: ['string'] }, - { open: '[', close: ']', notIn: ['string'] }, - { open: '"', close: '"', notIn: ['string'] } - ] -}; - +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { WorkerManager } from './workerManager'; +import type { JSONWorker } from './jsonWorker'; +import { LanguageServiceDefaults } from './monaco.contribution'; +import * as languageFeatures from './languageFeatures'; +import { createTokenizationSupport } from './tokenization'; +import { Uri, IDisposable, languages } from './fillers/monaco-editor-core'; + +export function setupMode(defaults: LanguageServiceDefaults): IDisposable { + const disposables: IDisposable[] = []; + const providers: IDisposable[] = []; + + const client = new WorkerManager(defaults); + disposables.push(client); + + const worker: languageFeatures.WorkerAccessor = ( + ...uris: Uri[] + ): Promise => { + return client.getLanguageServiceWorker(...uris); + }; + + function registerProviders(): void { + const { languageId, modeConfiguration } = defaults; + + disposeAll(providers); + + if (modeConfiguration.documentFormattingEdits) { + providers.push( + languages.registerDocumentFormattingEditProvider( + languageId, + new languageFeatures.DocumentFormattingEditProvider(worker) + ) + ); + } + if (modeConfiguration.documentRangeFormattingEdits) { + providers.push( + languages.registerDocumentRangeFormattingEditProvider( + languageId, + new languageFeatures.DocumentRangeFormattingEditProvider(worker) + ) + ); + } + if (modeConfiguration.completionItems) { + providers.push( + languages.registerCompletionItemProvider( + languageId, + new languageFeatures.CompletionAdapter(worker) + ) + ); + } + if (modeConfiguration.hovers) { + providers.push( + languages.registerHoverProvider( + languageId, + new languageFeatures.HoverAdapter(worker) + ) + ); + } + if (modeConfiguration.documentSymbols) { + providers.push( + languages.registerDocumentSymbolProvider( + languageId, + new languageFeatures.DocumentSymbolAdapter(worker) + ) + ); + } + if (modeConfiguration.tokens) { + providers.push( + languages.setTokensProvider(languageId, createTokenizationSupport(true)) + ); + } + if (modeConfiguration.colors) { + providers.push( + languages.registerColorProvider( + languageId, + new languageFeatures.DocumentColorAdapter(worker) + ) + ); + } + if (modeConfiguration.foldingRanges) { + providers.push( + languages.registerFoldingRangeProvider( + languageId, + new languageFeatures.FoldingRangeAdapter(worker) + ) + ); + } + if (modeConfiguration.diagnostics) { + providers.push( + new languageFeatures.DiagnosticsAdapter(languageId, worker, defaults) + ); + } + if (modeConfiguration.selectionRanges) { + providers.push( + languages.registerSelectionRangeProvider( + languageId, + new languageFeatures.SelectionRangeAdapter(worker) + ) + ); + } + } + + registerProviders(); + + disposables.push( + languages.setLanguageConfiguration( + defaults.languageId, + richEditConfiguration + ) + ); + + let modeConfiguration = defaults.modeConfiguration; + defaults.onDidChange((newDefaults) => { + if (newDefaults.modeConfiguration !== modeConfiguration) { + modeConfiguration = newDefaults.modeConfiguration; + registerProviders(); + } + }); + + disposables.push(asDisposable(providers)); + + return asDisposable(disposables); +} + +function asDisposable(disposables: IDisposable[]): IDisposable { + return { dispose: () => disposeAll(disposables) }; +} + +function disposeAll(disposables: IDisposable[]) { + while (disposables.length) { + disposables.pop().dispose(); + } +} + +const richEditConfiguration: languages.LanguageConfiguration = { + wordPattern: /(-?\d*\.\d\w*)|([^\[\{\]\}\:\"\,\s]+)/g, + + comments: { + lineComment: '//', + blockComment: ['/*', '*/'] + }, + + brackets: [ + ['{', '}'], + ['[', ']'] + ], + + autoClosingPairs: [ + { open: '{', close: '}', notIn: ['string'] }, + { open: '[', close: ']', notIn: ['string'] }, + { open: '"', close: '"', notIn: ['string'] } + ] +}; diff --git a/src/jsonWorker.ts b/src/jsonWorker.ts index 51d677ff..edbd901e 100644 --- a/src/jsonWorker.ts +++ b/src/jsonWorker.ts @@ -1,108 +1,159 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as jsonService from 'vscode-json-languageservice'; -import type { worker } from './fillers/monaco-editor-core' - -let defaultSchemaRequestService; -if (typeof fetch !== 'undefined') { - defaultSchemaRequestService = function (url) { return fetch(url).then(response => response.text()) }; -} - -export class JSONWorker { - - private _ctx: worker.IWorkerContext; - private _languageService: jsonService.LanguageService; - private _languageSettings: jsonService.LanguageSettings; - private _languageId: string; - - constructor(ctx: worker.IWorkerContext, createData: ICreateData) { - this._ctx = ctx; - this._languageSettings = createData.languageSettings; - this._languageId = createData.languageId; - this._languageService = jsonService.getLanguageService({ - schemaRequestService: createData.enableSchemaRequest && defaultSchemaRequestService - }); - this._languageService.configure(this._languageSettings); - } - - async doValidation(uri: string): Promise { - let document = this._getTextDocument(uri); - if (document) { - let jsonDocument = this._languageService.parseJSONDocument(document); - return this._languageService.doValidation(document, jsonDocument); - } - return Promise.resolve([]); - } - async doComplete(uri: string, position: jsonService.Position): Promise { - let document = this._getTextDocument(uri); - let jsonDocument = this._languageService.parseJSONDocument(document); - return this._languageService.doComplete(document, position, jsonDocument); - } - async doResolve(item: jsonService.CompletionItem): Promise { - return this._languageService.doResolve(item); - } - async doHover(uri: string, position: jsonService.Position): Promise { - let document = this._getTextDocument(uri); - let jsonDocument = this._languageService.parseJSONDocument(document); - return this._languageService.doHover(document, position, jsonDocument); - } - async format(uri: string, range: jsonService.Range, options: jsonService.FormattingOptions): Promise { - let document = this._getTextDocument(uri); - let textEdits = this._languageService.format(document, range, options); - return Promise.resolve(textEdits); - } - async resetSchema(uri: string): Promise { - return Promise.resolve(this._languageService.resetSchema(uri)); - } - async findDocumentSymbols(uri: string): Promise { - let document = this._getTextDocument(uri); - let jsonDocument = this._languageService.parseJSONDocument(document); - let symbols = this._languageService.findDocumentSymbols(document, jsonDocument); - return Promise.resolve(symbols); - } - async findDocumentColors(uri: string): Promise { - let document = this._getTextDocument(uri); - let jsonDocument = this._languageService.parseJSONDocument(document); - let colorSymbols = this._languageService.findDocumentColors(document, jsonDocument); - return Promise.resolve(colorSymbols); - } - async getColorPresentations(uri: string, color: jsonService.Color, range: jsonService.Range): Promise { - let document = this._getTextDocument(uri); - let jsonDocument = this._languageService.parseJSONDocument(document); - let colorPresentations = this._languageService.getColorPresentations(document, jsonDocument, color, range); - return Promise.resolve(colorPresentations); - } - async getFoldingRanges(uri: string, context?: { rangeLimit?: number; }): Promise { - let document = this._getTextDocument(uri); - let ranges = this._languageService.getFoldingRanges(document, context); - return Promise.resolve(ranges); - } - async getSelectionRanges(uri: string, positions: jsonService.Position[]): Promise { - let document = this._getTextDocument(uri); - let jsonDocument = this._languageService.parseJSONDocument(document); - let ranges = this._languageService.getSelectionRanges(document, positions, jsonDocument); - return Promise.resolve(ranges); - } - private _getTextDocument(uri: string): jsonService.TextDocument { - let models = this._ctx.getMirrorModels(); - for (let model of models) { - if (model.uri.toString() === uri) { - return jsonService.TextDocument.create(uri, this._languageId, model.version, model.getValue()); - } - } - return null; - } -} - -export interface ICreateData { - languageId: string; - languageSettings: jsonService.LanguageSettings; - enableSchemaRequest: boolean; -} - -export function create(ctx: worker.IWorkerContext, createData: ICreateData): JSONWorker { - return new JSONWorker(ctx, createData); -} +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as jsonService from 'vscode-json-languageservice'; +import type { worker } from './fillers/monaco-editor-core'; + +let defaultSchemaRequestService; +if (typeof fetch !== 'undefined') { + defaultSchemaRequestService = function (url) { + return fetch(url).then((response) => response.text()); + }; +} + +export class JSONWorker { + private _ctx: worker.IWorkerContext; + private _languageService: jsonService.LanguageService; + private _languageSettings: jsonService.LanguageSettings; + private _languageId: string; + + constructor(ctx: worker.IWorkerContext, createData: ICreateData) { + this._ctx = ctx; + this._languageSettings = createData.languageSettings; + this._languageId = createData.languageId; + this._languageService = jsonService.getLanguageService({ + schemaRequestService: + createData.enableSchemaRequest && defaultSchemaRequestService + }); + this._languageService.configure(this._languageSettings); + } + + async doValidation(uri: string): Promise { + let document = this._getTextDocument(uri); + if (document) { + let jsonDocument = this._languageService.parseJSONDocument(document); + return this._languageService.doValidation(document, jsonDocument); + } + return Promise.resolve([]); + } + async doComplete( + uri: string, + position: jsonService.Position + ): Promise { + let document = this._getTextDocument(uri); + let jsonDocument = this._languageService.parseJSONDocument(document); + return this._languageService.doComplete(document, position, jsonDocument); + } + async doResolve( + item: jsonService.CompletionItem + ): Promise { + return this._languageService.doResolve(item); + } + async doHover( + uri: string, + position: jsonService.Position + ): Promise { + let document = this._getTextDocument(uri); + let jsonDocument = this._languageService.parseJSONDocument(document); + return this._languageService.doHover(document, position, jsonDocument); + } + async format( + uri: string, + range: jsonService.Range, + options: jsonService.FormattingOptions + ): Promise { + let document = this._getTextDocument(uri); + let textEdits = this._languageService.format(document, range, options); + return Promise.resolve(textEdits); + } + async resetSchema(uri: string): Promise { + return Promise.resolve(this._languageService.resetSchema(uri)); + } + async findDocumentSymbols( + uri: string + ): Promise { + let document = this._getTextDocument(uri); + let jsonDocument = this._languageService.parseJSONDocument(document); + let symbols = this._languageService.findDocumentSymbols( + document, + jsonDocument + ); + return Promise.resolve(symbols); + } + async findDocumentColors( + uri: string + ): Promise { + let document = this._getTextDocument(uri); + let jsonDocument = this._languageService.parseJSONDocument(document); + let colorSymbols = this._languageService.findDocumentColors( + document, + jsonDocument + ); + return Promise.resolve(colorSymbols); + } + async getColorPresentations( + uri: string, + color: jsonService.Color, + range: jsonService.Range + ): Promise { + let document = this._getTextDocument(uri); + let jsonDocument = this._languageService.parseJSONDocument(document); + let colorPresentations = this._languageService.getColorPresentations( + document, + jsonDocument, + color, + range + ); + return Promise.resolve(colorPresentations); + } + async getFoldingRanges( + uri: string, + context?: { rangeLimit?: number } + ): Promise { + let document = this._getTextDocument(uri); + let ranges = this._languageService.getFoldingRanges(document, context); + return Promise.resolve(ranges); + } + async getSelectionRanges( + uri: string, + positions: jsonService.Position[] + ): Promise { + let document = this._getTextDocument(uri); + let jsonDocument = this._languageService.parseJSONDocument(document); + let ranges = this._languageService.getSelectionRanges( + document, + positions, + jsonDocument + ); + return Promise.resolve(ranges); + } + private _getTextDocument(uri: string): jsonService.TextDocument { + let models = this._ctx.getMirrorModels(); + for (let model of models) { + if (model.uri.toString() === uri) { + return jsonService.TextDocument.create( + uri, + this._languageId, + model.version, + model.getValue() + ); + } + } + return null; + } +} + +export interface ICreateData { + languageId: string; + languageSettings: jsonService.LanguageSettings; + enableSchemaRequest: boolean; +} + +export function create( + ctx: worker.IWorkerContext, + createData: ICreateData +): JSONWorker { + return new JSONWorker(ctx, createData); +} diff --git a/src/languageFeatures.ts b/src/languageFeatures.ts index 2946e7ab..55252c38 100644 --- a/src/languageFeatures.ts +++ b/src/languageFeatures.ts @@ -1,541 +1,717 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { LanguageServiceDefaults } from './monaco.contribution'; -import type { JSONWorker } from './jsonWorker'; -import { Uri, Position, Range, IRange, CancellationToken, IDisposable, editor, languages, MarkerSeverity, IMarkdownString } from './fillers/monaco-editor-core' -import * as jsonService from 'vscode-json-languageservice'; - - -export interface WorkerAccessor { - (...more: Uri[]): Promise -} - -// --- diagnostics --- --- - -export class DiagnosticsAdapter { - - private _disposables: IDisposable[] = []; - private _listener: { [uri: string]: IDisposable } = Object.create(null); - - constructor(private _languageId: string, private _worker: WorkerAccessor, defaults: LanguageServiceDefaults) { - const onModelAdd = (model: editor.IModel): void => { - let modeId = model.getModeId(); - if (modeId !== this._languageId) { - return; - } - - let handle: number; - this._listener[model.uri.toString()] = model.onDidChangeContent(() => { - clearTimeout(handle); - handle = setTimeout(() => this._doValidate(model.uri, modeId), 500); - }); - - this._doValidate(model.uri, modeId); - }; - - const onModelRemoved = (model: editor.IModel): void => { - editor.setModelMarkers(model, this._languageId, []); - let uriStr = model.uri.toString(); - let listener = this._listener[uriStr]; - if (listener) { - listener.dispose(); - delete this._listener[uriStr]; - } - }; - - this._disposables.push(editor.onDidCreateModel(onModelAdd)); - this._disposables.push(editor.onWillDisposeModel(model => { - onModelRemoved(model); - this._resetSchema(model.uri); - })); - this._disposables.push(editor.onDidChangeModelLanguage(event => { - onModelRemoved(event.model); - onModelAdd(event.model); - this._resetSchema(event.model.uri); - })); - - this._disposables.push(defaults.onDidChange(_ => { - editor.getModels().forEach(model => { - if (model.getModeId() === this._languageId) { - onModelRemoved(model); - onModelAdd(model); - } - }); - })); - - this._disposables.push({ - dispose: () => { - editor.getModels().forEach(onModelRemoved); - for (let key in this._listener) { - this._listener[key].dispose(); - } - } - }); - - editor.getModels().forEach(onModelAdd); - } - - public dispose(): void { - this._disposables.forEach(d => d && d.dispose()); - this._disposables = []; - } - - private _resetSchema(resource: Uri): void { - this._worker().then(worker => { - worker.resetSchema(resource.toString()); - }); - } - - private _doValidate(resource: Uri, languageId: string): void { - this._worker(resource).then(worker => { - return worker.doValidation(resource.toString()).then(diagnostics => { - const markers = diagnostics.map(d => toDiagnostics(resource, d)); - let model = editor.getModel(resource); - if (model && model.getModeId() === languageId) { - editor.setModelMarkers(model, languageId, markers); - } - }); - }).then(undefined, err => { - console.error(err); - }); - } -} - - -function toSeverity(lsSeverity: number): MarkerSeverity { - switch (lsSeverity) { - case jsonService.DiagnosticSeverity.Error: return MarkerSeverity.Error; - case jsonService.DiagnosticSeverity.Warning: return MarkerSeverity.Warning; - case jsonService.DiagnosticSeverity.Information: return MarkerSeverity.Info; - case jsonService.DiagnosticSeverity.Hint: return MarkerSeverity.Hint; - default: - return MarkerSeverity.Info; - } -} - -function toDiagnostics(resource: Uri, diag: jsonService.Diagnostic): editor.IMarkerData { - let code = typeof diag.code === 'number' ? String(diag.code) : diag.code; - - return { - severity: toSeverity(diag.severity), - startLineNumber: diag.range.start.line + 1, - startColumn: diag.range.start.character + 1, - endLineNumber: diag.range.end.line + 1, - endColumn: diag.range.end.character + 1, - message: diag.message, - code: code, - source: diag.source - }; -} - -// --- completion ------ - -function fromPosition(position: Position): jsonService.Position { - if (!position) { - return void 0; - } - return { character: position.column - 1, line: position.lineNumber - 1 }; -} - -function fromRange(range: IRange): jsonService.Range { - if (!range) { - return void 0; - } - return { start: { line: range.startLineNumber - 1, character: range.startColumn - 1 }, end: { line: range.endLineNumber - 1, character: range.endColumn - 1 } }; -} -function toRange(range: jsonService.Range): Range { - if (!range) { - return void 0; - } - return new Range(range.start.line + 1, range.start.character + 1, range.end.line + 1, range.end.character + 1); -} - -function toCompletionItemKind(kind: number): languages.CompletionItemKind { - let mItemKind = languages.CompletionItemKind; - - switch (kind) { - case jsonService.CompletionItemKind.Text: return mItemKind.Text; - case jsonService.CompletionItemKind.Method: return mItemKind.Method; - case jsonService.CompletionItemKind.Function: return mItemKind.Function; - case jsonService.CompletionItemKind.Constructor: return mItemKind.Constructor; - case jsonService.CompletionItemKind.Field: return mItemKind.Field; - case jsonService.CompletionItemKind.Variable: return mItemKind.Variable; - case jsonService.CompletionItemKind.Class: return mItemKind.Class; - case jsonService.CompletionItemKind.Interface: return mItemKind.Interface; - case jsonService.CompletionItemKind.Module: return mItemKind.Module; - case jsonService.CompletionItemKind.Property: return mItemKind.Property; - case jsonService.CompletionItemKind.Unit: return mItemKind.Unit; - case jsonService.CompletionItemKind.Value: return mItemKind.Value; - case jsonService.CompletionItemKind.Enum: return mItemKind.Enum; - case jsonService.CompletionItemKind.Keyword: return mItemKind.Keyword; - case jsonService.CompletionItemKind.Snippet: return mItemKind.Snippet; - case jsonService.CompletionItemKind.Color: return mItemKind.Color; - case jsonService.CompletionItemKind.File: return mItemKind.File; - case jsonService.CompletionItemKind.Reference: return mItemKind.Reference; - } - return mItemKind.Property; -} - -function fromCompletionItemKind(kind: languages.CompletionItemKind): jsonService.CompletionItemKind { - let mItemKind = languages.CompletionItemKind; - - switch (kind) { - case mItemKind.Text: return jsonService.CompletionItemKind.Text; - case mItemKind.Method: return jsonService.CompletionItemKind.Method; - case mItemKind.Function: return jsonService.CompletionItemKind.Function; - case mItemKind.Constructor: return jsonService.CompletionItemKind.Constructor; - case mItemKind.Field: return jsonService.CompletionItemKind.Field; - case mItemKind.Variable: return jsonService.CompletionItemKind.Variable; - case mItemKind.Class: return jsonService.CompletionItemKind.Class; - case mItemKind.Interface: return jsonService.CompletionItemKind.Interface; - case mItemKind.Module: return jsonService.CompletionItemKind.Module; - case mItemKind.Property: return jsonService.CompletionItemKind.Property; - case mItemKind.Unit: return jsonService.CompletionItemKind.Unit; - case mItemKind.Value: return jsonService.CompletionItemKind.Value; - case mItemKind.Enum: return jsonService.CompletionItemKind.Enum; - case mItemKind.Keyword: return jsonService.CompletionItemKind.Keyword; - case mItemKind.Snippet: return jsonService.CompletionItemKind.Snippet; - case mItemKind.Color: return jsonService.CompletionItemKind.Color; - case mItemKind.File: return jsonService.CompletionItemKind.File; - case mItemKind.Reference: return jsonService.CompletionItemKind.Reference; - } - return jsonService.CompletionItemKind.Property; -} - -function toTextEdit(textEdit: jsonService.TextEdit): editor.ISingleEditOperation { - if (!textEdit) { - return void 0; - } - return { - range: toRange(textEdit.range), - text: textEdit.newText - } -} - -export class CompletionAdapter implements languages.CompletionItemProvider { - - constructor(private _worker: WorkerAccessor) { - } - - public get triggerCharacters(): string[] { - return [' ', ':']; - } - - provideCompletionItems(model: editor.IReadOnlyModel, position: Position, context: languages.CompletionContext, token: CancellationToken): Promise { - const resource = model.uri; - - return this._worker(resource).then(worker => { - return worker.doComplete(resource.toString(), fromPosition(position)); - }).then(info => { - if (!info) { - return; - } - const wordInfo = model.getWordUntilPosition(position); - const wordRange = new Range(position.lineNumber, wordInfo.startColumn, position.lineNumber, wordInfo.endColumn); - - let items: languages.CompletionItem[] = info.items.map(entry => { - let item: languages.CompletionItem = { - label: entry.label, - insertText: entry.insertText || entry.label, - sortText: entry.sortText, - filterText: entry.filterText, - documentation: entry.documentation, - detail: entry.detail, - range: wordRange, - kind: toCompletionItemKind(entry.kind), - }; - if (entry.textEdit) { - item.range = toRange(entry.textEdit.range); - item.insertText = entry.textEdit.newText; - } - if (entry.additionalTextEdits) { - item.additionalTextEdits = entry.additionalTextEdits.map(toTextEdit) - } - if (entry.insertTextFormat === jsonService.InsertTextFormat.Snippet) { - item.insertTextRules = languages.CompletionItemInsertTextRule.InsertAsSnippet; - } - return item; - }); - - return { - isIncomplete: info.isIncomplete, - suggestions: items - }; - }); - } -} - -function isMarkupContent(thing: any): thing is jsonService.MarkupContent { - return thing && typeof thing === 'object' && typeof (thing).kind === 'string'; -} - -function toMarkdownString(entry: jsonService.MarkupContent | jsonService.MarkedString): IMarkdownString { - if (typeof entry === 'string') { - return { - value: entry - }; - } - if (isMarkupContent(entry)) { - if (entry.kind === 'plaintext') { - return { - value: entry.value.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&') - }; - } - return { - value: entry.value - }; - } - - return { value: '```' + entry.language + '\n' + entry.value + '\n```\n' }; -} - -function toMarkedStringArray(contents: jsonService.MarkupContent | jsonService.MarkedString | jsonService.MarkedString[]): IMarkdownString[] { - if (!contents) { - return void 0; - } - if (Array.isArray(contents)) { - return contents.map(toMarkdownString); - } - return [toMarkdownString(contents)]; -} - - -// --- hover ------ - -export class HoverAdapter implements languages.HoverProvider { - - constructor(private _worker: WorkerAccessor) { - } - - provideHover(model: editor.IReadOnlyModel, position: Position, token: CancellationToken): Promise { - let resource = model.uri; - - return this._worker(resource).then(worker => { - return worker.doHover(resource.toString(), fromPosition(position)); - }).then(info => { - if (!info) { - return; - } - return { - range: toRange(info.range), - contents: toMarkedStringArray(info.contents) - }; - }); - } -} - -// --- definition ------ - -function toLocation(location: jsonService.Location): languages.Location { - return { - uri: Uri.parse(location.uri), - range: toRange(location.range) - }; -} - - -// --- document symbols ------ - -function toSymbolKind(kind: jsonService.SymbolKind): languages.SymbolKind { - let mKind = languages.SymbolKind; - - switch (kind) { - case jsonService.SymbolKind.File: return mKind.Array; - case jsonService.SymbolKind.Module: return mKind.Module; - case jsonService.SymbolKind.Namespace: return mKind.Namespace; - case jsonService.SymbolKind.Package: return mKind.Package; - case jsonService.SymbolKind.Class: return mKind.Class; - case jsonService.SymbolKind.Method: return mKind.Method; - case jsonService.SymbolKind.Property: return mKind.Property; - case jsonService.SymbolKind.Field: return mKind.Field; - case jsonService.SymbolKind.Constructor: return mKind.Constructor; - case jsonService.SymbolKind.Enum: return mKind.Enum; - case jsonService.SymbolKind.Interface: return mKind.Interface; - case jsonService.SymbolKind.Function: return mKind.Function; - case jsonService.SymbolKind.Variable: return mKind.Variable; - case jsonService.SymbolKind.Constant: return mKind.Constant; - case jsonService.SymbolKind.String: return mKind.String; - case jsonService.SymbolKind.Number: return mKind.Number; - case jsonService.SymbolKind.Boolean: return mKind.Boolean; - case jsonService.SymbolKind.Array: return mKind.Array; - } - return mKind.Function; -} - - -export class DocumentSymbolAdapter implements languages.DocumentSymbolProvider { - - constructor(private _worker: WorkerAccessor) { - } - - public provideDocumentSymbols(model: editor.IReadOnlyModel, token: CancellationToken): Promise { - const resource = model.uri; - - return this._worker(resource).then(worker => worker.findDocumentSymbols(resource.toString())).then(items => { - if (!items) { - return; - } - return items.map(item => ({ - name: item.name, - detail: '', - containerName: item.containerName, - kind: toSymbolKind(item.kind), - range: toRange(item.location.range), - selectionRange: toRange(item.location.range), - tags: [] - })); - }); - } -} - - -function fromFormattingOptions(options: languages.FormattingOptions): jsonService.FormattingOptions { - return { - tabSize: options.tabSize, - insertSpaces: options.insertSpaces - }; -} - -export class DocumentFormattingEditProvider implements languages.DocumentFormattingEditProvider { - - constructor(private _worker: WorkerAccessor) { - } - - public provideDocumentFormattingEdits(model: editor.IReadOnlyModel, options: languages.FormattingOptions, token: CancellationToken): Promise { - const resource = model.uri; - - return this._worker(resource).then(worker => { - return worker.format(resource.toString(), null, fromFormattingOptions(options)).then(edits => { - if (!edits || edits.length === 0) { - return; - } - return edits.map(toTextEdit); - }); - }); - } -} - -export class DocumentRangeFormattingEditProvider implements languages.DocumentRangeFormattingEditProvider { - - constructor(private _worker: WorkerAccessor) { - } - - public provideDocumentRangeFormattingEdits(model: editor.IReadOnlyModel, range: Range, options: languages.FormattingOptions, token: CancellationToken): Promise { - const resource = model.uri; - - return this._worker(resource).then(worker => { - return worker.format(resource.toString(), fromRange(range), fromFormattingOptions(options)).then(edits => { - if (!edits || edits.length === 0) { - return; - } - return edits.map(toTextEdit); - }); - }); - } -} - -export class DocumentColorAdapter implements languages.DocumentColorProvider { - - constructor(private _worker: WorkerAccessor) { - } - - public provideDocumentColors(model: editor.IReadOnlyModel, token: CancellationToken): Promise { - const resource = model.uri; - - return this._worker(resource).then(worker => worker.findDocumentColors(resource.toString())).then(infos => { - if (!infos) { - return; - } - return infos.map(item => ({ - color: item.color, - range: toRange(item.range) - })); - }); - } - - public provideColorPresentations(model: editor.IReadOnlyModel, info: languages.IColorInformation, token: CancellationToken): Promise { - const resource = model.uri; - - return this._worker(resource).then(worker => worker.getColorPresentations(resource.toString(), info.color, fromRange(info.range))).then(presentations => { - if (!presentations) { - return; - } - return presentations.map(presentation => { - let item: languages.IColorPresentation = { - label: presentation.label, - }; - if (presentation.textEdit) { - item.textEdit = toTextEdit(presentation.textEdit) - } - if (presentation.additionalTextEdits) { - item.additionalTextEdits = presentation.additionalTextEdits.map(toTextEdit) - } - return item; - }); - }); - } -} - -export class FoldingRangeAdapter implements languages.FoldingRangeProvider { - - constructor(private _worker: WorkerAccessor) { - } - - public provideFoldingRanges(model: editor.IReadOnlyModel, context: languages.FoldingContext, token: CancellationToken): Promise { - const resource = model.uri; - - return this._worker(resource).then(worker => worker.getFoldingRanges(resource.toString(), context)).then(ranges => { - if (!ranges) { - return; - } - return ranges.map(range => { - let result: languages.FoldingRange = { - start: range.startLine + 1, - end: range.endLine + 1 - }; - if (typeof range.kind !== 'undefined') { - result.kind = toFoldingRangeKind(range.kind); - } - return result; - }); - }); - } - -} - -function toFoldingRangeKind(kind: jsonService.FoldingRangeKind): languages.FoldingRangeKind { - switch (kind) { - case jsonService.FoldingRangeKind.Comment: return languages.FoldingRangeKind.Comment; - case jsonService.FoldingRangeKind.Imports: return languages.FoldingRangeKind.Imports; - case jsonService.FoldingRangeKind.Region: return languages.FoldingRangeKind.Region; - } - return void 0; -} - -export class SelectionRangeAdapter implements languages.SelectionRangeProvider { - - constructor(private _worker: WorkerAccessor) { - } - - public provideSelectionRanges(model: editor.IReadOnlyModel, positions: Position[], token: CancellationToken): Promise { - const resource = model.uri; - - return this._worker(resource).then(worker => worker.getSelectionRanges(resource.toString(), positions.map(fromPosition))).then(selectionRanges => { - if (!selectionRanges) { - return; - } - return selectionRanges.map(selectionRange => { - const result: languages.SelectionRange[] = []; - while (selectionRange) { - result.push({ range: toRange(selectionRange.range) }); - selectionRange = selectionRange.parent; - } - return result; - }); - }); - } - -} +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { LanguageServiceDefaults } from './monaco.contribution'; +import type { JSONWorker } from './jsonWorker'; +import { + Uri, + Position, + Range, + IRange, + CancellationToken, + IDisposable, + editor, + languages, + MarkerSeverity, + IMarkdownString +} from './fillers/monaco-editor-core'; +import * as jsonService from 'vscode-json-languageservice'; + +export interface WorkerAccessor { + (...more: Uri[]): Promise; +} + +// --- diagnostics --- --- + +export class DiagnosticsAdapter { + private _disposables: IDisposable[] = []; + private _listener: { [uri: string]: IDisposable } = Object.create(null); + + constructor( + private _languageId: string, + private _worker: WorkerAccessor, + defaults: LanguageServiceDefaults + ) { + const onModelAdd = (model: editor.IModel): void => { + let modeId = model.getModeId(); + if (modeId !== this._languageId) { + return; + } + + let handle: number; + this._listener[model.uri.toString()] = model.onDidChangeContent(() => { + clearTimeout(handle); + handle = setTimeout(() => this._doValidate(model.uri, modeId), 500); + }); + + this._doValidate(model.uri, modeId); + }; + + const onModelRemoved = (model: editor.IModel): void => { + editor.setModelMarkers(model, this._languageId, []); + let uriStr = model.uri.toString(); + let listener = this._listener[uriStr]; + if (listener) { + listener.dispose(); + delete this._listener[uriStr]; + } + }; + + this._disposables.push(editor.onDidCreateModel(onModelAdd)); + this._disposables.push( + editor.onWillDisposeModel((model) => { + onModelRemoved(model); + this._resetSchema(model.uri); + }) + ); + this._disposables.push( + editor.onDidChangeModelLanguage((event) => { + onModelRemoved(event.model); + onModelAdd(event.model); + this._resetSchema(event.model.uri); + }) + ); + + this._disposables.push( + defaults.onDidChange((_) => { + editor.getModels().forEach((model) => { + if (model.getModeId() === this._languageId) { + onModelRemoved(model); + onModelAdd(model); + } + }); + }) + ); + + this._disposables.push({ + dispose: () => { + editor.getModels().forEach(onModelRemoved); + for (let key in this._listener) { + this._listener[key].dispose(); + } + } + }); + + editor.getModels().forEach(onModelAdd); + } + + public dispose(): void { + this._disposables.forEach((d) => d && d.dispose()); + this._disposables = []; + } + + private _resetSchema(resource: Uri): void { + this._worker().then((worker) => { + worker.resetSchema(resource.toString()); + }); + } + + private _doValidate(resource: Uri, languageId: string): void { + this._worker(resource) + .then((worker) => { + return worker.doValidation(resource.toString()).then((diagnostics) => { + const markers = diagnostics.map((d) => toDiagnostics(resource, d)); + let model = editor.getModel(resource); + if (model && model.getModeId() === languageId) { + editor.setModelMarkers(model, languageId, markers); + } + }); + }) + .then(undefined, (err) => { + console.error(err); + }); + } +} + +function toSeverity(lsSeverity: number): MarkerSeverity { + switch (lsSeverity) { + case jsonService.DiagnosticSeverity.Error: + return MarkerSeverity.Error; + case jsonService.DiagnosticSeverity.Warning: + return MarkerSeverity.Warning; + case jsonService.DiagnosticSeverity.Information: + return MarkerSeverity.Info; + case jsonService.DiagnosticSeverity.Hint: + return MarkerSeverity.Hint; + default: + return MarkerSeverity.Info; + } +} + +function toDiagnostics( + resource: Uri, + diag: jsonService.Diagnostic +): editor.IMarkerData { + let code = + typeof diag.code === 'number' ? String(diag.code) : diag.code; + + return { + severity: toSeverity(diag.severity), + startLineNumber: diag.range.start.line + 1, + startColumn: diag.range.start.character + 1, + endLineNumber: diag.range.end.line + 1, + endColumn: diag.range.end.character + 1, + message: diag.message, + code: code, + source: diag.source + }; +} + +// --- completion ------ + +function fromPosition(position: Position): jsonService.Position { + if (!position) { + return void 0; + } + return { character: position.column - 1, line: position.lineNumber - 1 }; +} + +function fromRange(range: IRange): jsonService.Range { + if (!range) { + return void 0; + } + return { + start: { + line: range.startLineNumber - 1, + character: range.startColumn - 1 + }, + end: { line: range.endLineNumber - 1, character: range.endColumn - 1 } + }; +} +function toRange(range: jsonService.Range): Range { + if (!range) { + return void 0; + } + return new Range( + range.start.line + 1, + range.start.character + 1, + range.end.line + 1, + range.end.character + 1 + ); +} + +function toCompletionItemKind(kind: number): languages.CompletionItemKind { + let mItemKind = languages.CompletionItemKind; + + switch (kind) { + case jsonService.CompletionItemKind.Text: + return mItemKind.Text; + case jsonService.CompletionItemKind.Method: + return mItemKind.Method; + case jsonService.CompletionItemKind.Function: + return mItemKind.Function; + case jsonService.CompletionItemKind.Constructor: + return mItemKind.Constructor; + case jsonService.CompletionItemKind.Field: + return mItemKind.Field; + case jsonService.CompletionItemKind.Variable: + return mItemKind.Variable; + case jsonService.CompletionItemKind.Class: + return mItemKind.Class; + case jsonService.CompletionItemKind.Interface: + return mItemKind.Interface; + case jsonService.CompletionItemKind.Module: + return mItemKind.Module; + case jsonService.CompletionItemKind.Property: + return mItemKind.Property; + case jsonService.CompletionItemKind.Unit: + return mItemKind.Unit; + case jsonService.CompletionItemKind.Value: + return mItemKind.Value; + case jsonService.CompletionItemKind.Enum: + return mItemKind.Enum; + case jsonService.CompletionItemKind.Keyword: + return mItemKind.Keyword; + case jsonService.CompletionItemKind.Snippet: + return mItemKind.Snippet; + case jsonService.CompletionItemKind.Color: + return mItemKind.Color; + case jsonService.CompletionItemKind.File: + return mItemKind.File; + case jsonService.CompletionItemKind.Reference: + return mItemKind.Reference; + } + return mItemKind.Property; +} + +function fromCompletionItemKind( + kind: languages.CompletionItemKind +): jsonService.CompletionItemKind { + let mItemKind = languages.CompletionItemKind; + + switch (kind) { + case mItemKind.Text: + return jsonService.CompletionItemKind.Text; + case mItemKind.Method: + return jsonService.CompletionItemKind.Method; + case mItemKind.Function: + return jsonService.CompletionItemKind.Function; + case mItemKind.Constructor: + return jsonService.CompletionItemKind.Constructor; + case mItemKind.Field: + return jsonService.CompletionItemKind.Field; + case mItemKind.Variable: + return jsonService.CompletionItemKind.Variable; + case mItemKind.Class: + return jsonService.CompletionItemKind.Class; + case mItemKind.Interface: + return jsonService.CompletionItemKind.Interface; + case mItemKind.Module: + return jsonService.CompletionItemKind.Module; + case mItemKind.Property: + return jsonService.CompletionItemKind.Property; + case mItemKind.Unit: + return jsonService.CompletionItemKind.Unit; + case mItemKind.Value: + return jsonService.CompletionItemKind.Value; + case mItemKind.Enum: + return jsonService.CompletionItemKind.Enum; + case mItemKind.Keyword: + return jsonService.CompletionItemKind.Keyword; + case mItemKind.Snippet: + return jsonService.CompletionItemKind.Snippet; + case mItemKind.Color: + return jsonService.CompletionItemKind.Color; + case mItemKind.File: + return jsonService.CompletionItemKind.File; + case mItemKind.Reference: + return jsonService.CompletionItemKind.Reference; + } + return jsonService.CompletionItemKind.Property; +} + +function toTextEdit( + textEdit: jsonService.TextEdit +): editor.ISingleEditOperation { + if (!textEdit) { + return void 0; + } + return { + range: toRange(textEdit.range), + text: textEdit.newText + }; +} + +export class CompletionAdapter implements languages.CompletionItemProvider { + constructor(private _worker: WorkerAccessor) {} + + public get triggerCharacters(): string[] { + return [' ', ':']; + } + + provideCompletionItems( + model: editor.IReadOnlyModel, + position: Position, + context: languages.CompletionContext, + token: CancellationToken + ): Promise { + const resource = model.uri; + + return this._worker(resource) + .then((worker) => { + return worker.doComplete(resource.toString(), fromPosition(position)); + }) + .then((info) => { + if (!info) { + return; + } + const wordInfo = model.getWordUntilPosition(position); + const wordRange = new Range( + position.lineNumber, + wordInfo.startColumn, + position.lineNumber, + wordInfo.endColumn + ); + + let items: languages.CompletionItem[] = info.items.map((entry) => { + let item: languages.CompletionItem = { + label: entry.label, + insertText: entry.insertText || entry.label, + sortText: entry.sortText, + filterText: entry.filterText, + documentation: entry.documentation, + detail: entry.detail, + range: wordRange, + kind: toCompletionItemKind(entry.kind) + }; + if (entry.textEdit) { + item.range = toRange(entry.textEdit.range); + item.insertText = entry.textEdit.newText; + } + if (entry.additionalTextEdits) { + item.additionalTextEdits = entry.additionalTextEdits.map( + toTextEdit + ); + } + if (entry.insertTextFormat === jsonService.InsertTextFormat.Snippet) { + item.insertTextRules = + languages.CompletionItemInsertTextRule.InsertAsSnippet; + } + return item; + }); + + return { + isIncomplete: info.isIncomplete, + suggestions: items + }; + }); + } +} + +function isMarkupContent(thing: any): thing is jsonService.MarkupContent { + return ( + thing && + typeof thing === 'object' && + typeof (thing).kind === 'string' + ); +} + +function toMarkdownString( + entry: jsonService.MarkupContent | jsonService.MarkedString +): IMarkdownString { + if (typeof entry === 'string') { + return { + value: entry + }; + } + if (isMarkupContent(entry)) { + if (entry.kind === 'plaintext') { + return { + value: entry.value.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&') + }; + } + return { + value: entry.value + }; + } + + return { value: '```' + entry.language + '\n' + entry.value + '\n```\n' }; +} + +function toMarkedStringArray( + contents: + | jsonService.MarkupContent + | jsonService.MarkedString + | jsonService.MarkedString[] +): IMarkdownString[] { + if (!contents) { + return void 0; + } + if (Array.isArray(contents)) { + return contents.map(toMarkdownString); + } + return [toMarkdownString(contents)]; +} + +// --- hover ------ + +export class HoverAdapter implements languages.HoverProvider { + constructor(private _worker: WorkerAccessor) {} + + provideHover( + model: editor.IReadOnlyModel, + position: Position, + token: CancellationToken + ): Promise { + let resource = model.uri; + + return this._worker(resource) + .then((worker) => { + return worker.doHover(resource.toString(), fromPosition(position)); + }) + .then((info) => { + if (!info) { + return; + } + return { + range: toRange(info.range), + contents: toMarkedStringArray(info.contents) + }; + }); + } +} + +// --- definition ------ + +function toLocation(location: jsonService.Location): languages.Location { + return { + uri: Uri.parse(location.uri), + range: toRange(location.range) + }; +} + +// --- document symbols ------ + +function toSymbolKind(kind: jsonService.SymbolKind): languages.SymbolKind { + let mKind = languages.SymbolKind; + + switch (kind) { + case jsonService.SymbolKind.File: + return mKind.Array; + case jsonService.SymbolKind.Module: + return mKind.Module; + case jsonService.SymbolKind.Namespace: + return mKind.Namespace; + case jsonService.SymbolKind.Package: + return mKind.Package; + case jsonService.SymbolKind.Class: + return mKind.Class; + case jsonService.SymbolKind.Method: + return mKind.Method; + case jsonService.SymbolKind.Property: + return mKind.Property; + case jsonService.SymbolKind.Field: + return mKind.Field; + case jsonService.SymbolKind.Constructor: + return mKind.Constructor; + case jsonService.SymbolKind.Enum: + return mKind.Enum; + case jsonService.SymbolKind.Interface: + return mKind.Interface; + case jsonService.SymbolKind.Function: + return mKind.Function; + case jsonService.SymbolKind.Variable: + return mKind.Variable; + case jsonService.SymbolKind.Constant: + return mKind.Constant; + case jsonService.SymbolKind.String: + return mKind.String; + case jsonService.SymbolKind.Number: + return mKind.Number; + case jsonService.SymbolKind.Boolean: + return mKind.Boolean; + case jsonService.SymbolKind.Array: + return mKind.Array; + } + return mKind.Function; +} + +export class DocumentSymbolAdapter implements languages.DocumentSymbolProvider { + constructor(private _worker: WorkerAccessor) {} + + public provideDocumentSymbols( + model: editor.IReadOnlyModel, + token: CancellationToken + ): Promise { + const resource = model.uri; + + return this._worker(resource) + .then((worker) => worker.findDocumentSymbols(resource.toString())) + .then((items) => { + if (!items) { + return; + } + return items.map((item) => ({ + name: item.name, + detail: '', + containerName: item.containerName, + kind: toSymbolKind(item.kind), + range: toRange(item.location.range), + selectionRange: toRange(item.location.range), + tags: [] + })); + }); + } +} + +function fromFormattingOptions( + options: languages.FormattingOptions +): jsonService.FormattingOptions { + return { + tabSize: options.tabSize, + insertSpaces: options.insertSpaces + }; +} + +export class DocumentFormattingEditProvider + implements languages.DocumentFormattingEditProvider { + constructor(private _worker: WorkerAccessor) {} + + public provideDocumentFormattingEdits( + model: editor.IReadOnlyModel, + options: languages.FormattingOptions, + token: CancellationToken + ): Promise { + const resource = model.uri; + + return this._worker(resource).then((worker) => { + return worker + .format(resource.toString(), null, fromFormattingOptions(options)) + .then((edits) => { + if (!edits || edits.length === 0) { + return; + } + return edits.map(toTextEdit); + }); + }); + } +} + +export class DocumentRangeFormattingEditProvider + implements languages.DocumentRangeFormattingEditProvider { + constructor(private _worker: WorkerAccessor) {} + + public provideDocumentRangeFormattingEdits( + model: editor.IReadOnlyModel, + range: Range, + options: languages.FormattingOptions, + token: CancellationToken + ): Promise { + const resource = model.uri; + + return this._worker(resource).then((worker) => { + return worker + .format( + resource.toString(), + fromRange(range), + fromFormattingOptions(options) + ) + .then((edits) => { + if (!edits || edits.length === 0) { + return; + } + return edits.map(toTextEdit); + }); + }); + } +} + +export class DocumentColorAdapter implements languages.DocumentColorProvider { + constructor(private _worker: WorkerAccessor) {} + + public provideDocumentColors( + model: editor.IReadOnlyModel, + token: CancellationToken + ): Promise { + const resource = model.uri; + + return this._worker(resource) + .then((worker) => worker.findDocumentColors(resource.toString())) + .then((infos) => { + if (!infos) { + return; + } + return infos.map((item) => ({ + color: item.color, + range: toRange(item.range) + })); + }); + } + + public provideColorPresentations( + model: editor.IReadOnlyModel, + info: languages.IColorInformation, + token: CancellationToken + ): Promise { + const resource = model.uri; + + return this._worker(resource) + .then((worker) => + worker.getColorPresentations( + resource.toString(), + info.color, + fromRange(info.range) + ) + ) + .then((presentations) => { + if (!presentations) { + return; + } + return presentations.map((presentation) => { + let item: languages.IColorPresentation = { + label: presentation.label + }; + if (presentation.textEdit) { + item.textEdit = toTextEdit(presentation.textEdit); + } + if (presentation.additionalTextEdits) { + item.additionalTextEdits = presentation.additionalTextEdits.map( + toTextEdit + ); + } + return item; + }); + }); + } +} + +export class FoldingRangeAdapter implements languages.FoldingRangeProvider { + constructor(private _worker: WorkerAccessor) {} + + public provideFoldingRanges( + model: editor.IReadOnlyModel, + context: languages.FoldingContext, + token: CancellationToken + ): Promise { + const resource = model.uri; + + return this._worker(resource) + .then((worker) => worker.getFoldingRanges(resource.toString(), context)) + .then((ranges) => { + if (!ranges) { + return; + } + return ranges.map((range) => { + let result: languages.FoldingRange = { + start: range.startLine + 1, + end: range.endLine + 1 + }; + if (typeof range.kind !== 'undefined') { + result.kind = toFoldingRangeKind( + range.kind + ); + } + return result; + }); + }); + } +} + +function toFoldingRangeKind( + kind: jsonService.FoldingRangeKind +): languages.FoldingRangeKind { + switch (kind) { + case jsonService.FoldingRangeKind.Comment: + return languages.FoldingRangeKind.Comment; + case jsonService.FoldingRangeKind.Imports: + return languages.FoldingRangeKind.Imports; + case jsonService.FoldingRangeKind.Region: + return languages.FoldingRangeKind.Region; + } + return void 0; +} + +export class SelectionRangeAdapter implements languages.SelectionRangeProvider { + constructor(private _worker: WorkerAccessor) {} + + public provideSelectionRanges( + model: editor.IReadOnlyModel, + positions: Position[], + token: CancellationToken + ): Promise { + const resource = model.uri; + + return this._worker(resource) + .then((worker) => + worker.getSelectionRanges( + resource.toString(), + positions.map(fromPosition) + ) + ) + .then((selectionRanges) => { + if (!selectionRanges) { + return; + } + return selectionRanges.map((selectionRange) => { + const result: languages.SelectionRange[] = []; + while (selectionRange) { + result.push({ range: toRange(selectionRange.range) }); + selectionRange = selectionRange.parent; + } + return result; + }); + }); + } +} diff --git a/src/monaco.contribution.ts b/src/monaco.contribution.ts index 6167958b..df17e778 100644 --- a/src/monaco.contribution.ts +++ b/src/monaco.contribution.ts @@ -1,185 +1,199 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as mode from './jsonMode'; -import { Emitter, IEvent, languages } from './fillers/monaco-editor-core' - -// --- JSON configuration and defaults --------- - -export interface DiagnosticsOptions { - /** - * If set, the validator will be enabled and perform syntax validation as well as schema based validation. - */ - readonly validate?: boolean; - /** - * If set, comments are tolerated. If set to false, syntax errors will be emitted for comments. - */ - readonly allowComments?: boolean; - /** - * A list of known schemas and/or associations of schemas to file names. - */ - readonly schemas?: { - /** - * The URI of the schema, which is also the identifier of the schema. - */ - readonly uri: string; - /** - * A list of file names that are associated to the schema. The '*' wildcard can be used. For example '*.schema.json', 'package.json' - */ - readonly fileMatch?: string[]; - /** - * The schema for the given URI. - */ - readonly schema?: any; - }[]; - /** - * If set, the schema service would load schema content on-demand with 'fetch' if available - */ - readonly enableSchemaRequest?: boolean; -} - -export interface ModeConfiguration { - /** - * Defines whether the built-in documentFormattingEdit provider is enabled. - */ - readonly documentFormattingEdits?: boolean; - - /** - * Defines whether the built-in documentRangeFormattingEdit provider is enabled. - */ - readonly documentRangeFormattingEdits?: boolean; - - /** - * Defines whether the built-in completionItemProvider is enabled. - */ - readonly completionItems?: boolean; - - /** - * Defines whether the built-in hoverProvider is enabled. - */ - readonly hovers?: boolean; - - /** - * Defines whether the built-in documentSymbolProvider is enabled. - */ - readonly documentSymbols?: boolean; - - /** - * Defines whether the built-in tokens provider is enabled. - */ - readonly tokens?: boolean; - - /** - * Defines whether the built-in color provider is enabled. - */ - readonly colors?: boolean; - - /** - * Defines whether the built-in foldingRange provider is enabled. - */ - readonly foldingRanges?: boolean; - - /** - * Defines whether the built-in diagnostic provider is enabled. - */ - readonly diagnostics?: boolean; - - /** - * Defines whether the built-in selection range provider is enabled. - */ - readonly selectionRanges?: boolean; - -} - -export interface LanguageServiceDefaults { - readonly languageId: string; - readonly onDidChange: IEvent; - readonly diagnosticsOptions: DiagnosticsOptions; - readonly modeConfiguration: ModeConfiguration; - setDiagnosticsOptions(options: DiagnosticsOptions): void; - setModeConfiguration(modeConfiguration: ModeConfiguration): void; -} - -class LanguageServiceDefaultsImpl implements LanguageServiceDefaults { - - private _onDidChange = new Emitter(); - private _diagnosticsOptions: DiagnosticsOptions; - private _modeConfiguration: ModeConfiguration; - private _languageId: string; - - constructor(languageId: string, diagnosticsOptions: DiagnosticsOptions, modeConfiguration: ModeConfiguration) { - this._languageId = languageId; - this.setDiagnosticsOptions(diagnosticsOptions); - this.setModeConfiguration(modeConfiguration); - } - - get onDidChange(): IEvent { - return this._onDidChange.event; - } - - get languageId(): string { - return this._languageId; - } - - get modeConfiguration(): ModeConfiguration { - return this._modeConfiguration; - } - - get diagnosticsOptions(): DiagnosticsOptions { - return this._diagnosticsOptions; - } - - setDiagnosticsOptions(options: DiagnosticsOptions): void { - this._diagnosticsOptions = options || Object.create(null); - this._onDidChange.fire(this); - } - - setModeConfiguration(modeConfiguration: ModeConfiguration): void { - this._modeConfiguration = modeConfiguration || Object.create(null); - this._onDidChange.fire(this); - }; -} - -const diagnosticDefault: Required = { - validate: true, - allowComments: true, - schemas: [], - enableSchemaRequest: false -}; - -const modeConfigurationDefault: Required = { - documentFormattingEdits: true, - documentRangeFormattingEdits: true, - completionItems: true, - hovers: true, - documentSymbols: true, - tokens: true, - colors: true, - foldingRanges: true, - diagnostics: true, - selectionRanges: true -} - -export const jsonDefaults: LanguageServiceDefaults = new LanguageServiceDefaultsImpl('json', diagnosticDefault, modeConfigurationDefault); - -// export to the global based API -(languages).json = { jsonDefaults }; - -// --- Registration to monaco editor --- - -function getMode(): Promise { - return import('./jsonMode'); -} - -languages.register({ - id: 'json', - extensions: ['.json', '.bowerrc', '.jshintrc', '.jscsrc', '.eslintrc', '.babelrc', '.har'], - aliases: ['JSON', 'json'], - mimetypes: ['application/json'], -}); - -languages.onLanguage('json', () => { - getMode().then(mode => mode.setupMode(jsonDefaults)); -}); +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as mode from './jsonMode'; +import { Emitter, IEvent, languages } from './fillers/monaco-editor-core'; + +// --- JSON configuration and defaults --------- + +export interface DiagnosticsOptions { + /** + * If set, the validator will be enabled and perform syntax validation as well as schema based validation. + */ + readonly validate?: boolean; + /** + * If set, comments are tolerated. If set to false, syntax errors will be emitted for comments. + */ + readonly allowComments?: boolean; + /** + * A list of known schemas and/or associations of schemas to file names. + */ + readonly schemas?: { + /** + * The URI of the schema, which is also the identifier of the schema. + */ + readonly uri: string; + /** + * A list of file names that are associated to the schema. The '*' wildcard can be used. For example '*.schema.json', 'package.json' + */ + readonly fileMatch?: string[]; + /** + * The schema for the given URI. + */ + readonly schema?: any; + }[]; + /** + * If set, the schema service would load schema content on-demand with 'fetch' if available + */ + readonly enableSchemaRequest?: boolean; +} + +export interface ModeConfiguration { + /** + * Defines whether the built-in documentFormattingEdit provider is enabled. + */ + readonly documentFormattingEdits?: boolean; + + /** + * Defines whether the built-in documentRangeFormattingEdit provider is enabled. + */ + readonly documentRangeFormattingEdits?: boolean; + + /** + * Defines whether the built-in completionItemProvider is enabled. + */ + readonly completionItems?: boolean; + + /** + * Defines whether the built-in hoverProvider is enabled. + */ + readonly hovers?: boolean; + + /** + * Defines whether the built-in documentSymbolProvider is enabled. + */ + readonly documentSymbols?: boolean; + + /** + * Defines whether the built-in tokens provider is enabled. + */ + readonly tokens?: boolean; + + /** + * Defines whether the built-in color provider is enabled. + */ + readonly colors?: boolean; + + /** + * Defines whether the built-in foldingRange provider is enabled. + */ + readonly foldingRanges?: boolean; + + /** + * Defines whether the built-in diagnostic provider is enabled. + */ + readonly diagnostics?: boolean; + + /** + * Defines whether the built-in selection range provider is enabled. + */ + readonly selectionRanges?: boolean; +} + +export interface LanguageServiceDefaults { + readonly languageId: string; + readonly onDidChange: IEvent; + readonly diagnosticsOptions: DiagnosticsOptions; + readonly modeConfiguration: ModeConfiguration; + setDiagnosticsOptions(options: DiagnosticsOptions): void; + setModeConfiguration(modeConfiguration: ModeConfiguration): void; +} + +class LanguageServiceDefaultsImpl implements LanguageServiceDefaults { + private _onDidChange = new Emitter(); + private _diagnosticsOptions: DiagnosticsOptions; + private _modeConfiguration: ModeConfiguration; + private _languageId: string; + + constructor( + languageId: string, + diagnosticsOptions: DiagnosticsOptions, + modeConfiguration: ModeConfiguration + ) { + this._languageId = languageId; + this.setDiagnosticsOptions(diagnosticsOptions); + this.setModeConfiguration(modeConfiguration); + } + + get onDidChange(): IEvent { + return this._onDidChange.event; + } + + get languageId(): string { + return this._languageId; + } + + get modeConfiguration(): ModeConfiguration { + return this._modeConfiguration; + } + + get diagnosticsOptions(): DiagnosticsOptions { + return this._diagnosticsOptions; + } + + setDiagnosticsOptions(options: DiagnosticsOptions): void { + this._diagnosticsOptions = options || Object.create(null); + this._onDidChange.fire(this); + } + + setModeConfiguration(modeConfiguration: ModeConfiguration): void { + this._modeConfiguration = modeConfiguration || Object.create(null); + this._onDidChange.fire(this); + } +} + +const diagnosticDefault: Required = { + validate: true, + allowComments: true, + schemas: [], + enableSchemaRequest: false +}; + +const modeConfigurationDefault: Required = { + documentFormattingEdits: true, + documentRangeFormattingEdits: true, + completionItems: true, + hovers: true, + documentSymbols: true, + tokens: true, + colors: true, + foldingRanges: true, + diagnostics: true, + selectionRanges: true +}; + +export const jsonDefaults: LanguageServiceDefaults = new LanguageServiceDefaultsImpl( + 'json', + diagnosticDefault, + modeConfigurationDefault +); + +// export to the global based API +(languages).json = { jsonDefaults }; + +// --- Registration to monaco editor --- + +function getMode(): Promise { + return import('./jsonMode'); +} + +languages.register({ + id: 'json', + extensions: [ + '.json', + '.bowerrc', + '.jshintrc', + '.jscsrc', + '.eslintrc', + '.babelrc', + '.har' + ], + aliases: ['JSON', 'json'], + mimetypes: ['application/json'] +}); + +languages.onLanguage('json', () => { + getMode().then((mode) => mode.setupMode(jsonDefaults)); +}); diff --git a/src/tokenization.ts b/src/tokenization.ts index db2d972c..9cdbf11b 100644 --- a/src/tokenization.ts +++ b/src/tokenization.ts @@ -4,13 +4,22 @@ *--------------------------------------------------------------------------------------------*/ import * as json from 'jsonc-parser'; -import { languages } from './fillers/monaco-editor-core' - -export function createTokenizationSupport(supportComments: boolean): languages.TokensProvider { - return { - getInitialState: () => new JSONState(null, null, false), - tokenize: (line, state, offsetDelta?, stopAtOffset?) => tokenize(supportComments, line, state, offsetDelta, stopAtOffset) - }; +import { languages } from './fillers/monaco-editor-core'; + +export function createTokenizationSupport( + supportComments: boolean +): languages.TokensProvider { + return { + getInitialState: () => new JSONState(null, null, false), + tokenize: (line, state, offsetDelta?, stopAtOffset?) => + tokenize( + supportComments, + line, + state, + offsetDelta, + stopAtOffset + ) + }; } export const TOKEN_DELIM_OBJECT = 'delimiter.bracket.json'; @@ -26,155 +35,170 @@ export const TOKEN_COMMENT_BLOCK = 'comment.block.json'; export const TOKEN_COMMENT_LINE = 'comment.line.json'; class JSONState implements languages.IState { - - private _state: languages.IState; - - public scanError: json.ScanError; - public lastWasColon: boolean; - - constructor(state: languages.IState, scanError: json.ScanError, lastWasColon: boolean) { - this._state = state; - this.scanError = scanError; - this.lastWasColon = lastWasColon; - } - - public clone(): JSONState { - return new JSONState(this._state, this.scanError, this.lastWasColon); - } - - public equals(other: languages.IState): boolean { - if (other === this) { - return true; - } - if (!other || !(other instanceof JSONState)) { - return false; - } - return this.scanError === (other).scanError && - this.lastWasColon === (other).lastWasColon; - } - - public getStateData(): languages.IState { - return this._state; - } - - public setStateData(state: languages.IState): void { - this._state = state; - } + private _state: languages.IState; + + public scanError: json.ScanError; + public lastWasColon: boolean; + + constructor( + state: languages.IState, + scanError: json.ScanError, + lastWasColon: boolean + ) { + this._state = state; + this.scanError = scanError; + this.lastWasColon = lastWasColon; + } + + public clone(): JSONState { + return new JSONState(this._state, this.scanError, this.lastWasColon); + } + + public equals(other: languages.IState): boolean { + if (other === this) { + return true; + } + if (!other || !(other instanceof JSONState)) { + return false; + } + return ( + this.scanError === (other).scanError && + this.lastWasColon === (other).lastWasColon + ); + } + + public getStateData(): languages.IState { + return this._state; + } + + public setStateData(state: languages.IState): void { + this._state = state; + } } -function tokenize(comments: boolean, line: string, state: JSONState, offsetDelta: number = 0, stopAtOffset?: number): languages.ILineTokens { - - // handle multiline strings and block comments - var numberOfInsertedCharacters = 0, - adjustOffset = false; - - switch (state.scanError) { - case json.ScanError.UnexpectedEndOfString: - line = '"' + line; - numberOfInsertedCharacters = 1; - break; - case json.ScanError.UnexpectedEndOfComment: - line = '/*' + line; - numberOfInsertedCharacters = 2; - break; - } - - var scanner = json.createScanner(line), - kind: json.SyntaxKind, - ret: languages.ILineTokens, - lastWasColon = state.lastWasColon; - - ret = { - tokens: [], - endState: state.clone() - }; - - while (true) { - - var offset = offsetDelta + scanner.getPosition(), - type = ''; - - kind = scanner.scan(); - if (kind === json.SyntaxKind.EOF) { - break; - } - - // Check that the scanner has advanced - if (offset === offsetDelta + scanner.getPosition()) { - throw new Error('Scanner did not advance, next 3 characters are: ' + line.substr(scanner.getPosition(), 3)); - } - - // In case we inserted /* or " character, we need to - // adjust the offset of all tokens (except the first) - if (adjustOffset) { - offset -= numberOfInsertedCharacters; - } - adjustOffset = numberOfInsertedCharacters > 0; - - - // brackets and type - switch (kind) { - case json.SyntaxKind.OpenBraceToken: - type = TOKEN_DELIM_OBJECT; - lastWasColon = false; - break; - case json.SyntaxKind.CloseBraceToken: - type = TOKEN_DELIM_OBJECT; - lastWasColon = false; - break; - case json.SyntaxKind.OpenBracketToken: - type = TOKEN_DELIM_ARRAY; - lastWasColon = false; - break; - case json.SyntaxKind.CloseBracketToken: - type = TOKEN_DELIM_ARRAY; - lastWasColon = false; - break; - case json.SyntaxKind.ColonToken: - type = TOKEN_DELIM_COLON; - lastWasColon = true; - break; - case json.SyntaxKind.CommaToken: - type = TOKEN_DELIM_COMMA; - lastWasColon = false; - break; - case json.SyntaxKind.TrueKeyword: - case json.SyntaxKind.FalseKeyword: - type = TOKEN_VALUE_BOOLEAN; - lastWasColon = false; - break; - case json.SyntaxKind.NullKeyword: - type = TOKEN_VALUE_NULL; - lastWasColon = false; - break; - case json.SyntaxKind.StringLiteral: - type = lastWasColon ? TOKEN_VALUE_STRING : TOKEN_PROPERTY_NAME; - lastWasColon = false; - break; - case json.SyntaxKind.NumericLiteral: - type = TOKEN_VALUE_NUMBER; - lastWasColon = false; - break; - } - - // comments, iff enabled - if (comments) { - switch (kind) { - case json.SyntaxKind.LineCommentTrivia: - type = TOKEN_COMMENT_LINE; - break; - case json.SyntaxKind.BlockCommentTrivia: - type = TOKEN_COMMENT_BLOCK; - break; - } - } - - ret.endState = new JSONState(state.getStateData(), scanner.getTokenError(), lastWasColon); - ret.tokens.push({ - startIndex: offset, - scopes: type - }); - } - - return ret; +function tokenize( + comments: boolean, + line: string, + state: JSONState, + offsetDelta: number = 0, + stopAtOffset?: number +): languages.ILineTokens { + // handle multiline strings and block comments + var numberOfInsertedCharacters = 0, + adjustOffset = false; + + switch (state.scanError) { + case json.ScanError.UnexpectedEndOfString: + line = '"' + line; + numberOfInsertedCharacters = 1; + break; + case json.ScanError.UnexpectedEndOfComment: + line = '/*' + line; + numberOfInsertedCharacters = 2; + break; + } + + var scanner = json.createScanner(line), + kind: json.SyntaxKind, + ret: languages.ILineTokens, + lastWasColon = state.lastWasColon; + + ret = { + tokens: [], + endState: state.clone() + }; + + while (true) { + var offset = offsetDelta + scanner.getPosition(), + type = ''; + + kind = scanner.scan(); + if (kind === json.SyntaxKind.EOF) { + break; + } + + // Check that the scanner has advanced + if (offset === offsetDelta + scanner.getPosition()) { + throw new Error( + 'Scanner did not advance, next 3 characters are: ' + + line.substr(scanner.getPosition(), 3) + ); + } + + // In case we inserted /* or " character, we need to + // adjust the offset of all tokens (except the first) + if (adjustOffset) { + offset -= numberOfInsertedCharacters; + } + adjustOffset = numberOfInsertedCharacters > 0; + + // brackets and type + switch (kind) { + case json.SyntaxKind.OpenBraceToken: + type = TOKEN_DELIM_OBJECT; + lastWasColon = false; + break; + case json.SyntaxKind.CloseBraceToken: + type = TOKEN_DELIM_OBJECT; + lastWasColon = false; + break; + case json.SyntaxKind.OpenBracketToken: + type = TOKEN_DELIM_ARRAY; + lastWasColon = false; + break; + case json.SyntaxKind.CloseBracketToken: + type = TOKEN_DELIM_ARRAY; + lastWasColon = false; + break; + case json.SyntaxKind.ColonToken: + type = TOKEN_DELIM_COLON; + lastWasColon = true; + break; + case json.SyntaxKind.CommaToken: + type = TOKEN_DELIM_COMMA; + lastWasColon = false; + break; + case json.SyntaxKind.TrueKeyword: + case json.SyntaxKind.FalseKeyword: + type = TOKEN_VALUE_BOOLEAN; + lastWasColon = false; + break; + case json.SyntaxKind.NullKeyword: + type = TOKEN_VALUE_NULL; + lastWasColon = false; + break; + case json.SyntaxKind.StringLiteral: + type = lastWasColon ? TOKEN_VALUE_STRING : TOKEN_PROPERTY_NAME; + lastWasColon = false; + break; + case json.SyntaxKind.NumericLiteral: + type = TOKEN_VALUE_NUMBER; + lastWasColon = false; + break; + } + + // comments, iff enabled + if (comments) { + switch (kind) { + case json.SyntaxKind.LineCommentTrivia: + type = TOKEN_COMMENT_LINE; + break; + case json.SyntaxKind.BlockCommentTrivia: + type = TOKEN_COMMENT_BLOCK; + break; + } + } + + ret.endState = new JSONState( + state.getStateData(), + scanner.getTokenError(), + lastWasColon + ); + ret.tokens.push({ + startIndex: offset, + scopes: type + }); + } + + return ret; } diff --git a/src/tsconfig.esm.json b/src/tsconfig.esm.json index 779a13ce..eb9d7013 100644 --- a/src/tsconfig.esm.json +++ b/src/tsconfig.esm.json @@ -1,16 +1,16 @@ { - "compilerOptions": { - "declaration": true, - "module": "esnext", - "moduleResolution": "node", - "outDir": "../out/esm", - "target": "es5", - "lib": [ - "dom", - "es5", - "es2015.collection", - "es2015.promise", - "es2015.iterable" - ] - } + "compilerOptions": { + "declaration": true, + "module": "esnext", + "moduleResolution": "node", + "outDir": "../out/esm", + "target": "es5", + "lib": [ + "dom", + "es5", + "es2015.collection", + "es2015.promise", + "es2015.iterable" + ] + } } diff --git a/src/tsconfig.json b/src/tsconfig.json index 5daa1b31..6b43d988 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -1,16 +1,16 @@ { - "compilerOptions": { - "module": "amd", - "moduleResolution": "node", - "outDir": "../out/amd", - "declaration": true, - "target": "es5", - "lib": [ - "dom", - "es5", - "es2015.collection", - "es2015.promise", - "es2015.iterable" - ] - } + "compilerOptions": { + "module": "amd", + "moduleResolution": "node", + "outDir": "../out/amd", + "declaration": true, + "target": "es5", + "lib": [ + "dom", + "es5", + "es2015.collection", + "es2015.promise", + "es2015.iterable" + ] + } } diff --git a/src/workerManager.ts b/src/workerManager.ts index fd4030ef..90b184a1 100644 --- a/src/workerManager.ts +++ b/src/workerManager.ts @@ -1,87 +1,91 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { LanguageServiceDefaults } from './monaco.contribution'; -import type { JSONWorker } from './jsonWorker'; -import { IDisposable, Uri, editor } from './fillers/monaco-editor-core' - -const STOP_WHEN_IDLE_FOR = 2 * 60 * 1000; // 2min - -export class WorkerManager { - - private _defaults: LanguageServiceDefaults; - private _idleCheckInterval: number; - private _lastUsedTime: number; - private _configChangeListener: IDisposable; - - private _worker: editor.MonacoWebWorker; - private _client: Promise; - - constructor(defaults: LanguageServiceDefaults) { - this._defaults = defaults; - this._worker = null; - this._idleCheckInterval = setInterval(() => this._checkIfIdle(), 30 * 1000); - this._lastUsedTime = 0; - this._configChangeListener = this._defaults.onDidChange(() => this._stopWorker()); - } - - private _stopWorker(): void { - if (this._worker) { - this._worker.dispose(); - this._worker = null; - } - this._client = null; - } - - dispose(): void { - clearInterval(this._idleCheckInterval); - this._configChangeListener.dispose(); - this._stopWorker(); - } - - private _checkIfIdle(): void { - if (!this._worker) { - return; - } - let timePassedSinceLastUsed = Date.now() - this._lastUsedTime; - if (timePassedSinceLastUsed > STOP_WHEN_IDLE_FOR) { - this._stopWorker(); - } - } - - private _getClient(): Promise { - this._lastUsedTime = Date.now(); - - if (!this._client) { - this._worker = editor.createWebWorker({ - - // module that exports the create() method and returns a `JSONWorker` instance - moduleId: 'vs/language/json/jsonWorker', - - label: this._defaults.languageId, - - // passed in to the create() method - createData: { - languageSettings: this._defaults.diagnosticsOptions, - languageId: this._defaults.languageId, - enableSchemaRequest: this._defaults.diagnosticsOptions.enableSchemaRequest - } - }); - - this._client = >this._worker.getProxy(); - } - - return this._client; - } - - getLanguageServiceWorker(...resources: Uri[]): Promise { - let _client: JSONWorker; - return this._getClient().then((client) => { - _client = client - }).then(_ => { - return this._worker.withSyncedResources(resources) - }).then(_ => _client); - } -} +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { LanguageServiceDefaults } from './monaco.contribution'; +import type { JSONWorker } from './jsonWorker'; +import { IDisposable, Uri, editor } from './fillers/monaco-editor-core'; + +const STOP_WHEN_IDLE_FOR = 2 * 60 * 1000; // 2min + +export class WorkerManager { + private _defaults: LanguageServiceDefaults; + private _idleCheckInterval: number; + private _lastUsedTime: number; + private _configChangeListener: IDisposable; + + private _worker: editor.MonacoWebWorker; + private _client: Promise; + + constructor(defaults: LanguageServiceDefaults) { + this._defaults = defaults; + this._worker = null; + this._idleCheckInterval = setInterval(() => this._checkIfIdle(), 30 * 1000); + this._lastUsedTime = 0; + this._configChangeListener = this._defaults.onDidChange(() => + this._stopWorker() + ); + } + + private _stopWorker(): void { + if (this._worker) { + this._worker.dispose(); + this._worker = null; + } + this._client = null; + } + + dispose(): void { + clearInterval(this._idleCheckInterval); + this._configChangeListener.dispose(); + this._stopWorker(); + } + + private _checkIfIdle(): void { + if (!this._worker) { + return; + } + let timePassedSinceLastUsed = Date.now() - this._lastUsedTime; + if (timePassedSinceLastUsed > STOP_WHEN_IDLE_FOR) { + this._stopWorker(); + } + } + + private _getClient(): Promise { + this._lastUsedTime = Date.now(); + + if (!this._client) { + this._worker = editor.createWebWorker({ + // module that exports the create() method and returns a `JSONWorker` instance + moduleId: 'vs/language/json/jsonWorker', + + label: this._defaults.languageId, + + // passed in to the create() method + createData: { + languageSettings: this._defaults.diagnosticsOptions, + languageId: this._defaults.languageId, + enableSchemaRequest: this._defaults.diagnosticsOptions + .enableSchemaRequest + } + }); + + this._client = >(this._worker.getProxy()); + } + + return this._client; + } + + getLanguageServiceWorker(...resources: Uri[]): Promise { + let _client: JSONWorker; + return this._getClient() + .then((client) => { + _client = client; + }) + .then((_) => { + return this._worker.withSyncedResources(resources); + }) + .then((_) => _client); + } +} diff --git a/test/index.html b/test/index.html index 1a644ec6..c1bb2cad 100644 --- a/test/index.html +++ b/test/index.html @@ -1,118 +1,126 @@ - - - - - - - - - -

Monaco Editor JSON test page

-
- - - - - - - - - - + + + + + + + + +

Monaco Editor JSON test page

+
+ + + + + + + + +