From 4059f6b5cd19390baa47d48c7b5565c07072e744 Mon Sep 17 00:00:00 2001 From: azerr Date: Mon, 4 Apr 2022 16:01:05 +0200 Subject: [PATCH] Contribute to html language server with a custom language. Fixes #146730 Signed-off-by: azerr --- extensions/handlebars/package.json | 14 +- .../client/src/htmlClient.ts | 9 +- .../client/src/htmlExtension.ts | 153 ++++++++++++++++++ .../schemas/package.schema.json | 36 ++++- 4 files changed, 199 insertions(+), 13 deletions(-) create mode 100644 extensions/html-language-features/client/src/htmlExtension.ts diff --git a/extensions/handlebars/package.json b/extensions/handlebars/package.json index 0eb6cb2eb2b6e..14e9ff14c2e1f 100644 --- a/extensions/handlebars/package.json +++ b/extensions/handlebars/package.json @@ -36,10 +36,20 @@ "scopeName": "text.html.handlebars", "path": "./syntaxes/Handlebars.tmLanguage.json" } - ] + ], + "html": { + "languages": [ + { + "documentSelector": [ + "handlebars" + ], + "activateAutoInsertion": true + } + ] + } }, "repository": { "type": "git", "url": "https://github.com/microsoft/vscode.git" } -} +} \ No newline at end of file diff --git a/extensions/html-language-features/client/src/htmlClient.ts b/extensions/html-language-features/client/src/htmlClient.ts index 7baceece6fb6e..ffbe99f443d9f 100644 --- a/extensions/html-language-features/client/src/htmlClient.ts +++ b/extensions/html-language-features/client/src/htmlClient.ts @@ -18,6 +18,7 @@ import { import { FileSystemProvider, serveFileSystemRequests } from './requests'; import { getCustomDataSource } from './customData'; import { activateAutoInsertion } from './autoInsertion'; +import { collectHtmlLanguageContributions, getActivateAutoInsertionSupportedLanguages, getDocumentSelector, HtmlLanguageContribution } from './htmlExtension'; namespace CustomDataChangedNotification { export const type: NotificationType = new NotificationType('html/customDataChanged'); @@ -87,8 +88,8 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua let toDispose = context.subscriptions; - - let documentSelector = ['html', 'handlebars']; + const htmlContributions: HtmlLanguageContribution[] = collectHtmlLanguageContributions(extensions.all); + let documentSelector = getDocumentSelector(htmlContributions); let embeddedLanguages = { css: true, javascript: true }; let rangeFormatting: Disposable | undefined = undefined; @@ -158,7 +159,8 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua }; return client.sendRequest(AutoInsertRequest.type, param); }; - let disposable = activateAutoInsertion(insertRequestor, { html: true, handlebars: true }, runtime); + const supportedLanguages = getActivateAutoInsertionSupportedLanguages(htmlContributions); + let disposable = activateAutoInsertion(insertRequestor, supportedLanguages, runtime); toDispose.push(disposable); disposable = client.onTelemetry(e => { @@ -299,5 +301,4 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua toDispose.push(activeEditorListener); } } - } diff --git a/extensions/html-language-features/client/src/htmlExtension.ts b/extensions/html-language-features/client/src/htmlExtension.ts new file mode 100644 index 0000000000000..95c4a3ec4cc16 --- /dev/null +++ b/extensions/html-language-features/client/src/htmlExtension.ts @@ -0,0 +1,153 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { DocumentFilter, DocumentSelector } from 'vscode-languageclient'; +import { isDeepStrictEqual } from 'util'; + +const RELOAD_WINDOW = 'workbench.action.reloadWindow'; + +let existingExtensions: HtmlLanguageContribution[]; + +/** + * Html language contribution + */ +export interface HtmlLanguageContribution { + documentSelector: DocumentSelector; + activateAutoInsertion: false; +} + +/** + * Returns all Html language contributions from package.json + * + * @param extensions array of extensions to search contributions from + */ +export function collectHtmlLanguageContributions(extensions: readonly vscode.Extension[]): HtmlLanguageContribution[] { + const result: HtmlLanguageContribution[] = []; + if (extensions && extensions.length) { + for (const extension of extensions) { + const htmlLanguages = extension.packageJSON?.contributes?.html?.languages; + if (Array.isArray(htmlLanguages)) { + htmlLanguages.forEach(htmlExtensionSection => { + const htmlExtension = createHtmlExtension(htmlExtensionSection); + if (htmlExtension) { + result.push(htmlExtension); + } + }); + } + } + } + // Make a copy of extensions: + existingExtensions = result.slice(); + return result; +} + +export function handleExtensionChange(extensions: readonly vscode.Extension[]): void { + if (!existingExtensions) { + return; + } + const oldExtensions = new Set(existingExtensions.slice()); + const newExtensions = collectHtmlLanguageContributions(extensions); + let hasChanged = (oldExtensions.size !== newExtensions.length); + if (!hasChanged) { + for (const newExtension of newExtensions) { + let found = false; + for (const oldExtension of oldExtensions) { + if (isDeepStrictEqual(oldExtension, newExtension)) { + found = true; + break; + } + } + if (found) { + continue; + } else { + hasChanged = true; + break; + } + } + } + + if (hasChanged) { + const msg = `Extensions to the Html Language Server changed, reloading ${vscode.env.appName} is required for the changes to take effect.`; + const action = 'Reload'; + vscode.window.showWarningMessage(msg, action).then((selection) => { + if (action === selection) { + vscode.commands.executeCommand(RELOAD_WINDOW); + } + }); + } +} + +/** + * Returns the document selector. + * + * The returned document selector contains the html document selectors + * and all document selectors contained in `htmlContributions`. + * + * @param htmlContributions Html language server contributions from other VS Code extensions + */ +export function getDocumentSelector(htmlContributions: HtmlLanguageContribution[]): DocumentSelector { + let documentSelector: DocumentSelector = [ + 'html' + ]; + htmlContributions.forEach((contribution: HtmlLanguageContribution) => { + documentSelector = documentSelector.concat(contribution.documentSelector); + }); + return documentSelector; +} + +export function getActivateAutoInsertionSupportedLanguages(htmlContributions: HtmlLanguageContribution[]): { [id: string]: boolean } { + const supportedLanguages: { [id: string]: boolean } = { html: true }; + htmlContributions.forEach((contribution: HtmlLanguageContribution) => { + contribution.documentSelector.forEach(selector => { + if (typeof selector === 'string') { + const language: string = selector; + supportedLanguages[language] = contribution.activateAutoInsertion; + } else { + const language = (selector).language; + if (language) { + supportedLanguages[language] = contribution.activateAutoInsertion; + } + } + }); + }); + return supportedLanguages; +} + +function createHtmlExtension(section: any): HtmlLanguageContribution | undefined { + const documentSelector = createSelector(section); + if (documentSelector.length > 0) { + const activateAutoInsertion = section.activateAutoInsertion === true ? true : false; + return { documentSelector, activateAutoInsertion } as HtmlLanguageContribution; + } + return; +} + +function createSelector(section: any): DocumentSelector { + if (!Array.isArray(section.documentSelector)) { + return []; + } + const documentSelector: DocumentSelector = []; + section.documentSelector.forEach((selector: any) => { + if (typeof selector === 'string') { + documentSelector.push(selector); + } else if (selector) { + const documentFilter: { [key: string]: string } = {}; + if (typeof selector.language === 'string') { + documentFilter.language = selector.language; + } + if (typeof selector.scheme === 'string') { + documentFilter.scheme = selector.scheme; + } + if (typeof selector.pattern === 'string') { + documentFilter.pattern = selector.pattern; + } + if (documentFilter.language || documentFilter.scheme || documentFilter.pattern) { + documentSelector.push(documentFilter as DocumentFilter); + } + } + }); + return documentSelector; +} diff --git a/extensions/html-language-features/schemas/package.schema.json b/extensions/html-language-features/schemas/package.schema.json index a4d8715b918f5..cee9860d47bf7 100644 --- a/extensions/html-language-features/schemas/package.schema.json +++ b/extensions/html-language-features/schemas/package.schema.json @@ -6,15 +6,37 @@ "contributes": { "type": "object", "properties": { - "html.customData": { - "type": "array", - "markdownDescription": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-html-languageservice/blob/master/docs/customData.md).\n\nVS Code loads custom data on startup to enhance its HTML support for the custom HTML tags, attributes and attribute values you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", - "items": { - "type": "string", - "description": "Relative path to a HTML custom data file" + "html": { + "type": "object", + "properties": { + "customData": { + "type": "array", + "markdownDescription": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-html-languageservice/blob/master/docs/customData.md).\n\nVS Code loads custom data on startup to enhance its HTML support for the custom HTML tags, attributes and attribute values you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", + "items": { + "type": "string", + "description": "Relative path to a HTML custom data file" + } + }, + "languages": { + "type": "array", + "description": "A list of custom languages which contributes to the Html language server.", + "items": { + "type": "object", + "properties": { + "documentSelector": { + "type": "object", + "description": "The Document selector (string or DocumentFilter) which defines the custom language." + }, + "activateAutoInsertion": { + "type": "boolean", + "description": "true if the custom language activates the auto insertion and false otherwise." + } + } + } + } } } } } } -} +} \ No newline at end of file