From c8b5d5b6d504f714e88f363cfc331ceba8dd23eb Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 10 Jun 2021 18:36:46 +0200 Subject: [PATCH] Support html custom data. For microsoft/monaco-editor#2284 --- monaco.d.ts | 63 ++++++++++++++- package-lock.json | 14 +++- package.json | 4 +- src/htmlMode.ts | 4 - src/htmlWorker.ts | 17 +++-- src/languageFeatures.ts | 153 ++++--------------------------------- src/monaco.contribution.ts | 71 ++++++++++++++++- 7 files changed, 165 insertions(+), 161 deletions(-) diff --git a/monaco.d.ts b/monaco.d.ts index 6253a70b..acdad83c 100644 --- a/monaco.d.ts +++ b/monaco.d.ts @@ -21,7 +21,7 @@ declare namespace monaco.languages.html { readonly wrapAttributes: 'auto' | 'force' | 'force-aligned' | 'force-expand-multiline'; } export interface CompletionConfiguration { - [provider: string]: boolean; + [providerId: string]: boolean; } export interface Options { /** @@ -32,6 +32,10 @@ declare namespace monaco.languages.html { * A list of known schemas and/or associations of schemas to file names. */ readonly suggest?: CompletionConfiguration; + /** + * Configures the HTML data types known by the HTML langauge service. + */ + readonly data?: HTMLDataConfiguration; } export interface ModeConfiguration { /** @@ -102,9 +106,9 @@ declare namespace monaco.languages.html { } /** * Registers a new HTML language service for the languageId. - * Note: 'html', 'handlebar' and 'razor' registered by default. + * Note: 'html', 'handlebar' and 'razor' are registered by default. * - * Use this method only to register additional language ids with a HTML service. + * Use this method to register additional language ids with a HTML service. * The language server has to be registered before an editor model is opened. */ export function registerHTMLLanguageService( @@ -112,4 +116,57 @@ declare namespace monaco.languages.html { options: Options, modeConfiguration: ModeConfiguration ): LanguageServiceRegistration; + export interface HTMLDataConfiguration { + /** + * Defines whether the standard HTML tags and attributes are shown + */ + useDefaultDataProvider?: boolean; + /** + * Provides a set of custom data providers. + */ + dataProviders?: { + [providerId: string]: HTMLDataV1; + }; + } + /** + * Custom HTML tags attributes and attribute values + * https://github.com/microsoft/vscode-html-languageservice/blob/main/docs/customData.md + */ + export interface HTMLDataV1 { + version: 1 | 1.1; + tags?: ITagData[]; + globalAttributes?: IAttributeData[]; + valueSets?: IValueSet[]; + } + export interface IReference { + name: string; + url: string; + } + export interface ITagData { + name: string; + description?: string | MarkupContent; + attributes: IAttributeData[]; + references?: IReference[]; + } + export interface IAttributeData { + name: string; + description?: string | MarkupContent; + valueSet?: string; + values?: IValueData[]; + references?: IReference[]; + } + export interface IValueData { + name: string; + description?: string | MarkupContent; + references?: IReference[]; + } + export interface IValueSet { + name: string; + values: IValueData[]; + } + export interface MarkupContent { + kind: MarkupKind; + value: string; + } + export type MarkupKind = 'plaintext' | 'markdown'; } diff --git a/package-lock.json b/package-lock.json index a9d46162..0ee2acd8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -443,15 +443,23 @@ "dev": true }, "vscode-html-languageservice": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-4.0.1.tgz", - "integrity": "sha512-CZtnuQoDwZdmPLKLMC6RqFlRTw0jvZK71l53u5ZIM3hSoVKAqW33gahBVNFpC3TPFxZSx0jqEhBTLf37RUMkWg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-4.0.5.tgz", + "integrity": "sha512-9ZKp7nfR6ObUA+K65GfgDPdOmXaPH8MOWxE2RwWF3tVnVMq2w+COKjDNHMvv+uNxtmaRT7/skls7CD/HzrW99w==", "dev": true, "requires": { "vscode-languageserver-textdocument": "^1.0.1", "vscode-languageserver-types": "^3.16.0", "vscode-nls": "^5.0.0", "vscode-uri": "^3.0.2" + }, + "dependencies": { + "vscode-languageserver-types": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", + "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==", + "dev": true + } } }, "vscode-languageserver-textdocument": { diff --git a/package.json b/package.json index e37f5916..2f7b80a1 100644 --- a/package.json +++ b/package.json @@ -29,9 +29,9 @@ "prettier": "^2.2.1", "pretty-quick": "^3.1.0", "requirejs": "^2.3.6", - "typescript": "^4.2.3", "terser": "^5.6.0", - "vscode-html-languageservice": "4.0.1", + "typescript": "^4.2.3", + "vscode-html-languageservice": "^4.0.5", "vscode-languageserver-types": "3.16.0", "vscode-languageserver-textdocument": "^1.0.1" }, diff --git a/src/htmlMode.ts b/src/htmlMode.ts index ebfd0af7..95b560cb 100644 --- a/src/htmlMode.ts +++ b/src/htmlMode.ts @@ -54,7 +54,6 @@ export function setupMode1(defaults: LanguageServiceDefaults): void { languageId, new languageFeatures.DocumentRangeFormattingEditProvider(worker) ); - new languageFeatures.DiagnosticsAdapter(languageId, worker, defaults); } } @@ -145,9 +144,6 @@ export function setupMode(defaults: LanguageServiceDefaults): IDisposable { ) ); } - if (modeConfiguration.diagnostics) { - providers.push(new languageFeatures.DiagnosticsAdapter(languageId, worker, defaults)); - } } registerProviders(); diff --git a/src/htmlWorker.ts b/src/htmlWorker.ts index cab32225..5de8154d 100644 --- a/src/htmlWorker.ts +++ b/src/htmlWorker.ts @@ -6,6 +6,7 @@ import { worker } from './fillers/monaco-editor-core'; import * as htmlService from 'vscode-html-languageservice'; import type { Options } from './monaco.contribution'; +import { IHTMLDataProvider } from 'vscode-html-languageservice'; export class HTMLWorker { private _ctx: worker.IWorkerContext; @@ -17,13 +18,19 @@ export class HTMLWorker { this._ctx = ctx; this._languageSettings = createData.languageSettings; this._languageId = createData.languageId; - this._languageService = htmlService.getLanguageService(); - } - async doValidation(uri: string): Promise { - // not yet suported - return Promise.resolve([]); + const data = this._languageSettings.data; + + const useDefaultDataProvider = data?.useDefaultDataProvider; + const customDataProviders: IHTMLDataProvider[] = []; + if (data?.dataProviders) { + for (const id in data.dataProviders) { + customDataProviders.push(htmlService.newHTMLDataProvider(id, data.dataProviders[id])); + } + } + this._languageService = htmlService.getLanguageService({ useDefaultDataProvider, customDataProviders }); } + async doComplete( uri: string, position: htmlService.Position diff --git a/src/languageFeatures.ts b/src/languageFeatures.ts index 15598e9a..f0020f40 100644 --- a/src/languageFeatures.ts +++ b/src/languageFeatures.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { LanguageServiceDefaults } from './monaco.contribution'; import type { HTMLWorker } from './htmlWorker'; import * as htmlService from 'vscode-html-languageservice'; import { @@ -13,139 +12,13 @@ import { Position, Range, CancellationToken, - IDisposable, - MarkerSeverity, IMarkdownString } from './fillers/monaco-editor-core'; -import { InsertReplaceEdit, TextEdit } from 'vscode-html-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 => { - const 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, []); - const uriStr = model.uri.toString(); - const 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._disposables.push( - editor.onDidChangeModelLanguage((event) => { - onModelRemoved(event.model); - onModelAdd(event.model); - }) - ); - - this._disposables.push( - defaults.onDidChange((_) => { - editor.getModels().forEach((model) => { - if (model.getModeId() === this._languageId) { - onModelRemoved(model); - onModelAdd(model); - } - }); - }) - ); - - this._disposables.push({ - dispose: () => { - for (const 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 _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)); - const 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 htmlService.DiagnosticSeverity.Error: - return MarkerSeverity.Error; - case htmlService.DiagnosticSeverity.Warning: - return MarkerSeverity.Warning; - case htmlService.DiagnosticSeverity.Information: - return MarkerSeverity.Info; - case htmlService.DiagnosticSeverity.Hint: - return MarkerSeverity.Hint; - default: - return MarkerSeverity.Info; - } -} - -function toDiagnostics(resource: Uri, diag: htmlService.Diagnostic): editor.IMarkerData { - const 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): htmlService.Position { @@ -177,10 +50,10 @@ function toRange(range: htmlService.Range): Range { ); } -function isInsertReplaceEdit(edit: TextEdit | InsertReplaceEdit): edit is InsertReplaceEdit { +function isInsertReplaceEdit(edit: htmlService.TextEdit | htmlService.InsertReplaceEdit): edit is htmlService.InsertReplaceEdit { return ( - typeof (edit).insert !== 'undefined' && - typeof (edit).replace !== 'undefined' + typeof (edit).insert !== 'undefined' && + typeof (edit).replace !== 'undefined' ); } @@ -285,7 +158,7 @@ function toTextEdit(textEdit: htmlService.TextEdit): editor.ISingleEditOperation } export class CompletionAdapter implements languages.CompletionItemProvider { - constructor(private _worker: WorkerAccessor) {} + constructor(private _worker: WorkerAccessor) { } public get triggerCharacters(): string[] { return ['.', ':', '<', '"', '=', '/']; @@ -399,7 +272,7 @@ function toMarkedStringArray( } export class HoverAdapter implements languages.HoverProvider { - constructor(private _worker: WorkerAccessor) {} + constructor(private _worker: WorkerAccessor) { } provideHover( model: editor.IReadOnlyModel, @@ -441,7 +314,7 @@ function toHighlighKind(kind: htmlService.DocumentHighlightKind): languages.Docu } export class DocumentHighlightAdapter implements languages.DocumentHighlightProvider { - constructor(private _worker: WorkerAccessor) {} + constructor(private _worker: WorkerAccessor) { } public provideDocumentHighlights( model: editor.IReadOnlyModel, @@ -511,7 +384,7 @@ function toSymbolKind(kind: htmlService.SymbolKind): languages.SymbolKind { } export class DocumentSymbolAdapter implements languages.DocumentSymbolProvider { - constructor(private _worker: WorkerAccessor) {} + constructor(private _worker: WorkerAccessor) { } public provideDocumentSymbols( model: editor.IReadOnlyModel, @@ -539,7 +412,7 @@ export class DocumentSymbolAdapter implements languages.DocumentSymbolProvider { } export class DocumentLinkAdapter implements languages.LinkProvider { - constructor(private _worker: WorkerAccessor) {} + constructor(private _worker: WorkerAccessor) { } public provideLinks( model: editor.IReadOnlyModel, @@ -573,7 +446,7 @@ function fromFormattingOptions( } export class DocumentFormattingEditProvider implements languages.DocumentFormattingEditProvider { - constructor(private _worker: WorkerAccessor) {} + constructor(private _worker: WorkerAccessor) { } public provideDocumentFormattingEdits( model: editor.IReadOnlyModel, @@ -597,7 +470,7 @@ export class DocumentFormattingEditProvider implements languages.DocumentFormatt export class DocumentRangeFormattingEditProvider implements languages.DocumentRangeFormattingEditProvider { - constructor(private _worker: WorkerAccessor) {} + constructor(private _worker: WorkerAccessor) { } public provideDocumentRangeFormattingEdits( model: editor.IReadOnlyModel, @@ -621,7 +494,7 @@ export class DocumentRangeFormattingEditProvider } export class RenameAdapter implements languages.RenameProvider { - constructor(private _worker: WorkerAccessor) {} + constructor(private _worker: WorkerAccessor) { } provideRenameEdits( model: editor.IReadOnlyModel, @@ -664,7 +537,7 @@ function toWorkspaceEdit(edit: htmlService.WorkspaceEdit): languages.WorkspaceEd } export class FoldingRangeAdapter implements languages.FoldingRangeProvider { - constructor(private _worker: WorkerAccessor) {} + constructor(private _worker: WorkerAccessor) { } public provideFoldingRanges( model: editor.IReadOnlyModel, @@ -705,7 +578,7 @@ function toFoldingRangeKind(kind: htmlService.FoldingRangeKind): languages.Foldi } export class SelectionRangeAdapter implements languages.SelectionRangeProvider { - constructor(private _worker: WorkerAccessor) {} + constructor(private _worker: WorkerAccessor) { } public provideSelectionRanges( model: editor.IReadOnlyModel, diff --git a/src/monaco.contribution.ts b/src/monaco.contribution.ts index 0dec6299..675d01f7 100644 --- a/src/monaco.contribution.ts +++ b/src/monaco.contribution.ts @@ -22,7 +22,7 @@ export interface HTMLFormatConfiguration { } export interface CompletionConfiguration { - [provider: string]: boolean; + [providerId: string]: boolean; } export interface Options { @@ -34,6 +34,10 @@ export interface Options { * A list of known schemas and/or associations of schemas to file names. */ readonly suggest?: CompletionConfiguration; + /** + * Configures the HTML data types known by the HTML langauge service. + */ + readonly data?: HTMLDataConfiguration; } export interface ModeConfiguration { @@ -165,17 +169,20 @@ const formatDefaults: Required = { const htmlOptionsDefault: Required = { format: formatDefaults, - suggest: { html5: true, angular1: true, ionic: true } + suggest: { html5: true, angular1: true, ionic: true }, + data: { useDefaultDataProvider: true } }; const handlebarOptionsDefault: Required = { format: formatDefaults, - suggest: { html5: true } + suggest: { html5: true }, + data: { useDefaultDataProvider: true } }; const razorOptionsDefault: Required = { format: formatDefaults, - suggest: { html5: true, razor: true } + suggest: { html5: true, razor: true }, + data: { useDefaultDataProvider: true } }; function getConfigurationDefault(languageId: string): Required { @@ -261,3 +268,59 @@ export function registerHTMLLanguageService( } }; } + + + +export interface HTMLDataConfiguration { + /** + * Defines whether the standard HTML tags and attributes are shown + */ + useDefaultDataProvider?: boolean; + /** + * Provides a set of custom data providers. + */ + dataProviders?: { [providerId: string]: HTMLDataV1 }; +} + +/** + * Custom HTML tags attributes and attribute values + * https://github.com/microsoft/vscode-html-languageservice/blob/main/docs/customData.md + */ +export interface HTMLDataV1 { + version: 1 | 1.1; + tags?: ITagData[]; + globalAttributes?: IAttributeData[]; + valueSets?: IValueSet[]; +} + +export interface IReference { + name: string; + url: string; +} +export interface ITagData { + name: string; + description?: string | MarkupContent; + attributes: IAttributeData[]; + references?: IReference[]; +} +export interface IAttributeData { + name: string; + description?: string | MarkupContent; + valueSet?: string; + values?: IValueData[]; + references?: IReference[]; +} +export interface IValueData { + name: string; + description?: string | MarkupContent; + references?: IReference[]; +} +export interface IValueSet { + name: string; + values: IValueData[]; +} +export interface MarkupContent { + kind: MarkupKind; + value: string; +} +export declare type MarkupKind = 'plaintext' | 'markdown'; \ No newline at end of file