From 573369782478aa07ba53223e0b66cfee367e1f83 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Mon, 26 Feb 2024 09:31:49 +0800 Subject: [PATCH 1/4] refactor: make all editor settings configurable --- packages/css/index.ts | 242 +++++++++++++++++-------------- packages/html/index.ts | 225 ++++++++++++++-------------- packages/json/index.ts | 134 ++++++++++++----- packages/markdown/index.ts | 56 ++++--- packages/prettyhtml/index.ts | 34 ++++- packages/pug-beautify/index.ts | 30 +++- packages/pug/index.ts | 30 +++- packages/sass-formatter/index.ts | 44 ++++-- packages/typescript/index.ts | 60 ++++---- packages/yaml/index.ts | 132 +++++++++++------ packages/yaml/package.json | 3 +- 11 files changed, 613 insertions(+), 377 deletions(-) diff --git a/packages/css/index.ts b/packages/css/index.ts index 752e1760..685de459 100644 --- a/packages/css/index.ts +++ b/packages/css/index.ts @@ -1,71 +1,112 @@ -import type { CodeAction, Diagnostic, LocationLink, ServicePluginInstance, ServicePlugin } from '@volar/language-service'; +import type { CodeAction, Diagnostic, LocationLink, ServicePluginInstance, ServicePlugin, ServiceContext, DocumentSelector, Disposable } from '@volar/language-service'; import * as css from 'vscode-css-languageservice'; import { TextDocument } from 'vscode-languageserver-textdocument'; import { URI, Utils } from 'vscode-uri'; export interface Provide { 'css/stylesheet': (document: TextDocument) => css.Stylesheet | undefined; - 'css/languageService': (languageId: string) => css.LanguageService | undefined; + 'css/languageService': (document: TextDocument) => css.LanguageService | undefined; } -export function create(): ServicePlugin { +export function create({ + cssDocumentSelector = ['css'], + scssDocumentSelector = ['scss'], + lessDocumentSelector = ['less'], + useDefaultDataProvider = true, + getDocumentContext = context => { + return { + resolveReference(ref, base) { + if (ref.match(/^\w[\w\d+.-]*:/)) { + // starts with a schema + return ref; + } + if (ref[0] === '/') { // resolve absolute path against the current workspace folder + let folderUri = context.env.workspaceFolder; + if (!folderUri.endsWith('/')) { + folderUri += '/'; + } + return folderUri + ref.substring(1); + } + const baseUri = URI.parse(base); + const baseUriDir = baseUri.path.endsWith('/') ? baseUri : Utils.dirname(baseUri); + return Utils.resolvePath(baseUriDir, ref).toString(true); + }, + }; + }, + isFormattingEnabled = async (document, context) => { + return await context.env.getConfiguration?.(document.languageId + '.format.enable') ?? true; + }, + getFormatConfiguration = async (document, context) => { + return await context.env.getConfiguration?.(document.languageId + '.format'); + }, + getLanguageSettings = async (document, context) => { + return await context.env.getConfiguration?.(document.languageId); + }, + getCustomData = async context => { + const customData: string[] = await context.env.getConfiguration?.('css.customData') ?? []; + const newData: css.ICSSDataProvider[] = []; + for (const customDataPath of customData) { + const uri = Utils.resolvePath(URI.parse(context.env.workspaceFolder), customDataPath); + const json = await context.env.fs?.readFile?.(uri.toString()); + if (json) { + try { + const data = JSON.parse(json); + newData.push(css.newCSSDataProvider(data)); + } + catch (error) { + console.error(error); + } + } + } + return newData; + }, + onDidChangeCustomData = (listener, context) => { + const disposable = context.env.onDidChangeConfiguration?.(listener); + return { + dispose() { + disposable?.dispose(); + }, + }; + }, +}: { + cssDocumentSelector?: DocumentSelector, + scssDocumentSelector?: DocumentSelector, + lessDocumentSelector?: DocumentSelector, + useDefaultDataProvider?: boolean; + getDocumentContext?(context: ServiceContext): css.DocumentContext; + isFormattingEnabled?(document: TextDocument, context: ServiceContext): Promise; + getFormatConfiguration?(document: TextDocument, context: ServiceContext): Promise; + getLanguageSettings?(document: TextDocument, context: ServiceContext): Promise; + getCustomData?(context: ServiceContext): Promise; + onDidChangeCustomData?(listener: () => void, context: ServiceContext): Disposable; +} = {}): ServicePlugin { return { name: 'css', // https://github.com/microsoft/vscode/blob/09850876e652688fb142e2e19fd00fd38c0bc4ba/extensions/css-language-features/server/src/cssServer.ts#L97 triggerCharacters: ['/', '-', ':'], create(context): ServicePluginInstance { - let inited = false; - const stylesheets = new WeakMap(); const fileSystemProvider: css.FileSystemProvider = { - stat: async uri => await context.env.fs?.stat(uri) ?? { - type: css.FileType.Unknown, - ctime: 0, - mtime: 0, - size: 0, - }, - readDirectory: async (uri) => await context.env.fs?.readDirectory(uri) ?? [], - }; - const documentContext: css.DocumentContext = { - resolveReference(ref, base) { - if (ref.match(/^\w[\w\d+.-]*:/)) { - // starts with a schema - return ref; - } - if (ref[0] === '/') { // resolve absolute path against the current workspace folder - return base + ref; - } - const baseUri = URI.parse(base); - const baseUriDir = baseUri.path.endsWith('/') ? baseUri : Utils.dirname(baseUri); - return Utils.resolvePath(baseUriDir, ref).toString(true); - }, - }; - const cssLs = css.getCSSLanguageService({ - fileSystemProvider, - clientCapabilities: context.env.clientCapabilities, - }); - const scssLs = css.getSCSSLanguageService({ - fileSystemProvider, - clientCapabilities: context.env.clientCapabilities, - }); - const lessLs = css.getLESSLanguageService({ - fileSystemProvider, - clientCapabilities: context.env.clientCapabilities, - }); - const postcssLs: css.LanguageService = { - ...scssLs, - doValidation: (document, stylesheet, documentSettings) => { - let errors = scssLs.doValidation(document, stylesheet, documentSettings); - errors = errors.filter(error => error.code !== 'css-semicolonexpected'); - errors = errors.filter(error => error.code !== 'css-ruleorselectorexpected'); - errors = errors.filter(error => error.code !== 'unknownAtRules'); - return errors; - }, + stat: async uri => await context.env.fs?.stat(uri) + ?? { type: css.FileType.Unknown, ctime: 0, mtime: 0, size: 0 }, + readDirectory: async uri => await context.env.fs?.readDirectory(uri) ?? [], }; + const documentContext = getDocumentContext(context); + const disposable = onDidChangeCustomData(() => initializing = undefined, context); + + let cssLs: css.LanguageService | undefined; + let scssLs: css.LanguageService | undefined; + let lessLs: css.LanguageService | undefined; + let customData: css.ICSSDataProvider[] = []; + let initializing: Promise | undefined; return { + dispose() { + disposable.dispose(); + }, + provide: { 'css/stylesheet': getStylesheet, 'css/languageService': getCssLs, @@ -73,11 +114,8 @@ export function create(): ServicePlugin { async provideCompletionItems(document, position) { return worker(document, async (stylesheet, cssLs) => { - - const settings = await context.env.getConfiguration?.(document.languageId); - const cssResult = await cssLs.doComplete2(document, position, stylesheet, documentContext, settings?.completion); - - return cssResult; + const settings = await getLanguageSettings(document, context); + return await cssLs.doComplete2(document, position, stylesheet, documentContext, settings?.completion); }); }, @@ -101,9 +139,7 @@ export function create(): ServicePlugin { provideDefinition(document, position) { return worker(document, (stylesheet, cssLs) => { - const location = cssLs.findDefinition(document, position, stylesheet); - if (location) { return [{ targetUri: location.uri, @@ -116,18 +152,14 @@ export function create(): ServicePlugin { async provideDiagnostics(document) { return worker(document, async (stylesheet, cssLs) => { - - const settings = await context.env.getConfiguration?.(document.languageId); - + const settings = await getLanguageSettings(document, context); return cssLs.doValidation(document, stylesheet, settings) as Diagnostic[]; }); }, async provideHover(document, position) { return worker(document, async (stylesheet, cssLs) => { - - const settings = await context.env.getConfiguration?.(document.languageId); - + const settings = await getLanguageSettings(document, context); return cssLs.doHover(document, position, stylesheet, settings?.hover); }); }, @@ -183,11 +215,11 @@ export function create(): ServicePlugin { async provideDocumentFormattingEdits(document, formatRange, options, codeOptions) { return worker(document, async (_stylesheet, cssLs) => { - const formatSettings = await context.env.getConfiguration?.(document.languageId + '.format'); - if (formatSettings?.enable === false) { + if (!await isFormattingEnabled(document, context)) { return; } + const formatSettings = await getFormatConfiguration(document, context); const formatOptions: css.CSSFormatConfiguration = { ...options, ...formatSettings, @@ -281,50 +313,30 @@ export function create(): ServicePlugin { }, }; - async function initCustomData() { - if (!inited) { - - context.env.onDidChangeConfiguration?.(async () => { - const customData = await getCustomData(); - cssLs.setDataProviders(true, customData); - scssLs.setDataProviders(true, customData); - lessLs.setDataProviders(true, customData); + function getCssLs(document: TextDocument): css.LanguageService | undefined { + if (matchDocument(cssDocumentSelector, document)) { + return cssLs ??= css.getCSSLanguageService({ + fileSystemProvider, + clientCapabilities: context.env.clientCapabilities, + useDefaultDataProvider, + customDataProviders: customData, }); - - const customData = await getCustomData(); - cssLs.setDataProviders(true, customData); - scssLs.setDataProviders(true, customData); - lessLs.setDataProviders(true, customData); - inited = true; } - } - - async function getCustomData() { - - const customData: string[] = await context.env.getConfiguration?.('css.customData') ?? []; - const newData: css.ICSSDataProvider[] = []; - - for (const customDataPath of customData) { - try { - const pathModuleName = 'path'; // avoid bundle - const { posix: path } = require(pathModuleName) as typeof import('path'); - const jsonPath = path.resolve(customDataPath); - newData.push(css.newCSSDataProvider(require(jsonPath))); - } - catch (error) { - console.error(error); - } + else if (matchDocument(scssDocumentSelector, document)) { + return scssLs ??= css.getSCSSLanguageService({ + fileSystemProvider, + clientCapabilities: context.env.clientCapabilities, + useDefaultDataProvider, + customDataProviders: customData, + }); } - - return newData; - } - - function getCssLs(lang: string) { - switch (lang) { - case 'css': return cssLs; - case 'scss': return scssLs; - case 'less': return lessLs; - case 'postcss': return postcssLs; + else if (matchDocument(lessDocumentSelector, document)) { + return lessLs ??= css.getLESSLanguageService({ + fileSystemProvider, + clientCapabilities: context.env.clientCapabilities, + useDefaultDataProvider, + customDataProviders: customData, + }); } } @@ -338,7 +350,7 @@ export function create(): ServicePlugin { } } - const cssLs = getCssLs(document.languageId); + const cssLs = getCssLs(document); if (!cssLs) return; @@ -354,14 +366,30 @@ export function create(): ServicePlugin { if (!stylesheet) return; - const cssLs = getCssLs(document.languageId); + const cssLs = getCssLs(document); if (!cssLs) return; - await initCustomData(); + await (initializing ??= initialize()); return callback(stylesheet, cssLs); } + + async function initialize() { + customData = await getCustomData(context); + cssLs?.setDataProviders(useDefaultDataProvider, customData); + scssLs?.setDataProviders(useDefaultDataProvider, customData); + lessLs?.setDataProviders(useDefaultDataProvider, customData); + } }, }; } + +function matchDocument(selector: DocumentSelector, document: TextDocument) { + for (const sel of selector) { + if (sel === document.languageId || (typeof sel === 'object' && sel.language === document.languageId)) { + return true; + } + } + return false; +} diff --git a/packages/html/index.ts b/packages/html/index.ts index a0339cdf..1246bfde 100644 --- a/packages/html/index.ts +++ b/packages/html/index.ts @@ -1,89 +1,137 @@ -import type { ServicePluginInstance, ServicePlugin } from '@volar/language-service'; +import type { ServicePluginInstance, ServicePlugin, DocumentSelector, ServiceContext, Disposable } from '@volar/language-service'; import * as html from 'vscode-html-languageservice'; import { TextDocument } from 'vscode-languageserver-textdocument'; import { URI, Utils } from 'vscode-uri'; -const parserLs = html.getLanguageService(); -const htmlDocuments = new WeakMap(); export interface Provide { 'html/htmlDocument': (document: TextDocument) => html.HTMLDocument | undefined; 'html/languageService': () => html.LanguageService; 'html/documentContext': () => html.DocumentContext; - 'html/updateCustomData': (extraData: html.IHTMLDataProvider[]) => void; -} - -export function getHtmlDocument(document: TextDocument) { - - const cache = htmlDocuments.get(document); - if (cache) { - const [cacheVersion, cacheDoc] = cache; - if (cacheVersion === document.version) { - return cacheDoc; - } - } - - const doc = parserLs.parseHTMLDocument(document); - htmlDocuments.set(document, [document.version, doc]); - - return doc; } export function create({ - languageId = 'html', + documentSelector = ['html'], useDefaultDataProvider = true, - useCustomDataProviders = true, + getDocumentContext = context => { + return { + resolveReference(ref, base) { + if (ref.match(/^\w[\w\d+.-]*:/)) { + // starts with a schema + return ref; + } + if (ref[0] === '/') { // resolve absolute path against the current workspace folder + let folderUri = context.env.workspaceFolder; + if (!folderUri.endsWith('/')) { + folderUri += '/'; + } + return folderUri + ref.substring(1); + } + const baseUri = URI.parse(base); + const baseUriDir = baseUri.path.endsWith('/') ? baseUri : Utils.dirname(baseUri); + return Utils.resolvePath(baseUriDir, ref).toString(true); + }, + }; + }, + isFormattingEnabled = async (_document, context) => { + return await context.env.getConfiguration?.('html.format.enable') ?? true; + }, + isAutoCreateQuotesEnabled = async (_document, context) => { + return await context.env.getConfiguration?.('html.autoCreateQuotes') ?? true; + }, + isAutoClosingTagsEnabled = async (_document, context) => { + return await context.env.getConfiguration?.('html.autoClosingTags') ?? true; + }, + getFormattingOptions = async (_document, context) => { + return await context.env.getConfiguration?.('html.format'); + }, + getCompletionConfiguration = async (_document, context) => { + return await context.env.getConfiguration?.('html.completion'); + }, + getHoverSettings = async (_document, context) => { + return await context.env.getConfiguration?.('html.hover'); + }, + getCustomData = async context => { + const customData: string[] = await context.env.getConfiguration?.('html.customData') ?? []; + const newData: html.IHTMLDataProvider[] = []; + for (const customDataPath of customData) { + const uri = Utils.resolvePath(URI.parse(context.env.workspaceFolder), customDataPath); + const json = await context.env.fs?.readFile?.(uri.toString()); + if (json) { + try { + const data = JSON.parse(json); + newData.push(html.newHTMLDataProvider(customDataPath, data)); + } + catch (error) { + console.error(error); + } + } + } + return newData; + }, + onDidChangeCustomData = (listener, context) => { + const disposable = context.env.onDidChangeConfiguration?.(listener); + return { + dispose() { + disposable?.dispose(); + }, + }; + }, }: { - languageId?: string; + documentSelector?: DocumentSelector; useDefaultDataProvider?: boolean; useCustomDataProviders?: boolean; + isFormattingEnabled?(document: TextDocument, context: ServiceContext): Promise; + isAutoCreateQuotesEnabled?(document: TextDocument, context: ServiceContext): Promise; + isAutoClosingTagsEnabled?(document: TextDocument, context: ServiceContext): Promise; + getDocumentContext?(context: ServiceContext): html.DocumentContext; + getFormattingOptions?(document: TextDocument, context: ServiceContext): Promise; + getCompletionConfiguration?(document: TextDocument, context: ServiceContext): Promise; + getHoverSettings?(document: TextDocument, context: ServiceContext): Promise; + getCustomData?(context: ServiceContext): Promise; + onDidChangeCustomData?(listener: () => void, context: ServiceContext): Disposable; } = {}): ServicePlugin { return { name: 'html', // https://github.com/microsoft/vscode/blob/09850876e652688fb142e2e19fd00fd38c0bc4ba/extensions/html-language-features/server/src/htmlServer.ts#L183 triggerCharacters: ['.', ':', '<', '"', '=', '/'], create(context): ServicePluginInstance { - let shouldUpdateCustomData = true; - let customData: html.IHTMLDataProvider[] = []; - let extraData: html.IHTMLDataProvider[] = []; + const htmlDocuments = new WeakMap(); const fileSystemProvider: html.FileSystemProvider = { - stat: async uri => await context.env.fs?.stat(uri) ?? { - type: html.FileType.Unknown, - ctime: 0, - mtime: 0, - size: 0, - }, + stat: async uri => await context.env.fs?.stat(uri) + ?? { type: html.FileType.Unknown, ctime: 0, mtime: 0, size: 0 }, readDirectory: async (uri) => context.env.fs?.readDirectory(uri) ?? [], }; - const documentContext = getDocumentContext(context.env.workspaceFolder); + const documentContext = getDocumentContext(context); const htmlLs = html.getLanguageService({ fileSystemProvider, clientCapabilities: context.env.clientCapabilities, + useDefaultDataProvider, }); + const disposable = onDidChangeCustomData(() => initializing = undefined, context); - context.env.onDidChangeConfiguration?.(() => { - shouldUpdateCustomData = true; - }); + let initializing: Promise | undefined; return { + dispose() { + disposable.dispose(); + }, + provide: { 'html/htmlDocument': (document) => { - if (document.languageId === languageId) { + if (matchDocument(documentSelector, document)) { return getHtmlDocument(document); } }, 'html/languageService': () => htmlLs, 'html/documentContext': () => documentContext, - 'html/updateCustomData': updateExtraCustomData, }, async provideCompletionItems(document, position) { return worker(document, async (htmlDocument) => { - - const configs = await context.env.getConfiguration?.('html.completion'); - + const configs = await getCompletionConfiguration(document, context); return htmlLs.doComplete2(document, position, htmlDocument, documentContext, configs); }); }, @@ -106,9 +154,7 @@ export function create({ async provideHover(document, position) { return worker(document, async (htmlDocument) => { - - const hoverSettings = await context.env.getConfiguration?.('html.hover'); - + const hoverSettings = await getHoverSettings(document, context); return htmlLs.doHover(document, position, htmlDocument, hoverSettings); }); }, @@ -146,18 +192,10 @@ export function create({ async provideDocumentFormattingEdits(document, formatRange, options, codeOptions) { return worker(document, async () => { - const formatSettings = await context.env.getConfiguration?.('html.format') ?? {}; - if (formatSettings.enable === false) { + if (!await isFormattingEnabled(document, context)) { return; } - // https://github.com/microsoft/vscode/blob/a8f73340be02966c3816a2f23cb7e446a3a7cb9b/extensions/html-language-features/server/src/modes/htmlMode.ts#L47-L51 - if (formatSettings.contentUnformatted) { - formatSettings.contentUnformatted = formatSettings.contentUnformatted + ',script'; - } else { - formatSettings.contentUnformatted = 'script'; - } - // https://github.com/microsoft/vscode/blob/dce493cb6e36346ef2714e82c42ce14fc461b15c/extensions/html-language-features/server/src/modes/formatting.ts#L13-L23 const endPos = formatRange.end; let endOffset = document.offsetAt(endPos); @@ -174,6 +212,7 @@ export function create({ }; } + const formatSettings = await getFormattingOptions(document, context); const formatOptions: html.HTMLFormatConfiguration = { ...options, ...formatSettings, @@ -287,11 +326,12 @@ export function create({ if (rangeLengthIsZero && lastCharacter === '=') { - const enabled = (await context.env.getConfiguration?.('html.autoCreateQuotes')) ?? true; + const enabled = await isAutoCreateQuotesEnabled(document, context); if (enabled) { - const text = htmlLs.doQuoteComplete(document, position, htmlDocument, await context.env.getConfiguration?.('html.completion')); + const completionConfiguration = await getCompletionConfiguration(document, context); + const text = htmlLs.doQuoteComplete(document, position, htmlDocument, completionConfiguration); if (text) { return text; @@ -301,7 +341,7 @@ export function create({ if (rangeLengthIsZero && (lastCharacter === '>' || lastCharacter === '/')) { - const enabled = (await context.env.getConfiguration?.('html.autoClosingTags')) ?? true; + const enabled = await isAutoClosingTagsEnabled(document, context); if (enabled) { @@ -316,76 +356,42 @@ export function create({ }, }; - async function initCustomData() { - if (shouldUpdateCustomData && useCustomDataProviders) { - shouldUpdateCustomData = false; - customData = await getCustomData(); - htmlLs.setDataProviders(useDefaultDataProvider, [...customData, ...extraData]); - } - } - - function updateExtraCustomData(data: html.IHTMLDataProvider[]) { - extraData = data; - htmlLs.setDataProviders(useDefaultDataProvider, [...customData, ...extraData]); - } - - async function getCustomData() { + function getHtmlDocument(document: TextDocument) { - const customData: string[] = await context.env.getConfiguration?.('html.customData') ?? []; - const newData: html.IHTMLDataProvider[] = []; - - for (const customDataPath of customData) { - try { - const pathModuleName = 'path'; // avoid bundle - const { posix: path } = require(pathModuleName) as typeof import('path'); - const jsonPath = path.resolve(customDataPath); - newData.push(html.newHTMLDataProvider(customDataPath, require(jsonPath))); - } - catch (error) { - console.error(error); + const cache = htmlDocuments.get(document); + if (cache) { + const [cacheVersion, cacheDoc] = cache; + if (cacheVersion === document.version) { + return cacheDoc; } } - return newData; + const doc = htmlLs.parseHTMLDocument(document); + htmlDocuments.set(document, [document.version, doc]); + + return doc; } async function worker(document: TextDocument, callback: (htmlDocument: html.HTMLDocument) => T) { - if (document.languageId !== languageId) + if (!matchDocument(documentSelector, document)) return; const htmlDocument = getHtmlDocument(document); if (!htmlDocument) return; - await initCustomData(); + await (initializing ??= initialize()); return callback(htmlDocument); } - }, - }; -} -export function getDocumentContext(workspaceFolder: string) { - const documentContext: html.DocumentContext = { - resolveReference(ref, base) { - if (ref.match(/^\w[\w\d+.-]*:/)) { - // starts with a schema - return ref; - } - if (ref[0] === '/') { // resolve absolute path against the current workspace folder - let folderUri = workspaceFolder; - if (!folderUri.endsWith('/')) { - folderUri += '/'; - } - return folderUri + ref.substr(1); + async function initialize() { + const customData = await getCustomData(context); + htmlLs.setDataProviders(useDefaultDataProvider, customData); } - const baseUri = URI.parse(base); - const baseUriDir = baseUri.path.endsWith('/') ? baseUri : Utils.dirname(baseUri); - return Utils.resolvePath(baseUriDir, ref).toString(true); }, }; - return documentContext; } function isEOL(content: string, offset: number) { @@ -397,3 +403,12 @@ const NL = '\n'.charCodeAt(0); function isNewlineCharacter(charCode: number) { return charCode === CR || charCode === NL; } + +function matchDocument(selector: DocumentSelector, document: TextDocument) { + for (const sel of selector) { + if (sel === document.languageId || (typeof sel === 'object' && sel.language === document.languageId)) { + return true; + } + } + return false; +} diff --git a/packages/json/index.ts b/packages/json/index.ts index ac8a5c85..cd80eb4c 100644 --- a/packages/json/index.ts +++ b/packages/json/index.ts @@ -1,4 +1,4 @@ -import type { ServicePlugin, Diagnostic, ServicePluginInstance } from '@volar/language-service'; +import type { ServicePlugin, ServicePluginInstance, DocumentSelector, ServiceContext, Disposable } from '@volar/language-service'; import * as json from 'vscode-json-languageservice'; import type { TextDocument } from 'vscode-languageserver-textdocument'; import { URI, Utils } from 'vscode-uri'; @@ -8,7 +8,71 @@ export interface Provide { 'json/languageService': () => json.LanguageService; } -export function create(settings?: json.LanguageSettings): ServicePlugin { +export interface JSONSchemaSettings { + fileMatch?: string[]; + url?: string; + schema?: json.JSONSchema; + folderUri?: string; +} + +export function create({ + documentSelector = ['json', 'jsonc'], + getWorkspaceContextService = () => { + return { + resolveRelativePath(relativePath, resource) { + const base = resource.substring(0, resource.lastIndexOf('/') + 1); + return Utils.resolvePath(URI.parse(base), relativePath).toString(); + }, + }; + }, + isFormattingEnabled = async (_document, context) => { + return await context.env.getConfiguration?.('json.format.enable') ?? true; + }, + getFormattingOptions = async (_document, context) => { + return await context.env.getConfiguration?.('json.format'); + }, + getLanguageSettings = async context => { + const languageSettings: json.LanguageSettings = {}; + + languageSettings.validate = await context.env.getConfiguration?.('json.validate') ?? true; + languageSettings.schemas ??= []; + + const schemas = await context.env.getConfiguration?.('json.schemas') ?? []; + + for (let i = 0; i < schemas.length; i++) { + const schema = schemas[i]; + let uri = schema.url; + if (!uri && schema.schema) { + uri = schema.schema.id || `vscode://schemas/custom/${i}`; + } + if (uri) { + languageSettings.schemas.push({ uri, fileMatch: schema.fileMatch, schema: schema.schema, folderUri: schema.folderUri }); + } + } + return languageSettings; + }, + getDocumentLanguageSettings = async document => { + return document.languageId === 'jsonc' + ? { comments: 'ignore', trailingCommas: 'warning' } + : { comments: 'error', trailingCommas: 'error' }; + }, + onDidChangeLanguageSettings = (listener, context) => { + const disposable = context.env.onDidChangeConfiguration?.(listener); + return { + dispose() { + disposable?.dispose(); + }, + }; + }, +}: { + documentSelector?: DocumentSelector; + getWorkspaceContextService?(context: ServiceContext): json.WorkspaceContextService; + isFormattingEnabled?(document: TextDocument, context: ServiceContext): Promise; + getFormattingOptions?(document: TextDocument, context: ServiceContext): Promise; + getLanguageSettings?(context: ServiceContext): Promise; + getDocumentLanguageSettings?(document: TextDocument, context: ServiceContext): Promise; + onDidChangeLanguageSettings?(listener: () => void, context: ServiceContext): Disposable; +} = {}): ServicePlugin { return { name: 'json', // https://github.com/microsoft/vscode/blob/09850876e652688fb142e2e19fd00fd38c0bc4ba/extensions/json-language-features/server/src/jsonServer.ts#L150 @@ -16,32 +80,21 @@ export function create(settings?: json.LanguageSettings): ServicePlugin { create(context): ServicePluginInstance { const jsonDocuments = new WeakMap(); - const workspaceContext: json.WorkspaceContextService = { - resolveRelativePath: (ref: string, base: string) => { - if (ref.match(/^\w[\w\d+.-]*:/)) { - // starts with a schema - return ref; - } - if (ref[0] === '/') { // resolve absolute path against the current workspace folder - return base + ref; - } - const baseUri = URI.parse(base); - const baseUriDir = baseUri.path.endsWith('/') ? baseUri : Utils.dirname(baseUri); - return Utils.resolvePath(baseUriDir, ref).toString(true); - }, - }; const jsonLs = json.getLanguageService({ schemaRequestService: async (uri) => await context.env.fs?.readFile(uri) ?? '', - workspaceContext, + workspaceContext: getWorkspaceContextService(context), clientCapabilities: context.env.clientCapabilities, }); + const disposable = onDidChangeLanguageSettings(() => initializing = undefined, context); - if (settings) { - jsonLs.configure(settings); - } + let initializing: Promise | undefined; return { + dispose() { + disposable.dispose(); + }, + provide: { 'json/jsonDocument': getJsonDocument, 'json/languageService': () => jsonLs, @@ -65,15 +118,8 @@ export function create(settings?: json.LanguageSettings): ServicePlugin { provideDiagnostics(document) { return worker(document, async (jsonDocument) => { - - const documentLanguageSettings = undefined; // await getSettings(); // TODO - - return await jsonLs.doValidation( - document, - jsonDocument, - documentLanguageSettings, - undefined, // TODO - ) as Diagnostic[]; + const settings = await getDocumentLanguageSettings(document, context); + return await jsonLs.doValidation(document, jsonDocument, settings); }); }, @@ -122,32 +168,41 @@ export function create(settings?: json.LanguageSettings): ServicePlugin { provideDocumentFormattingEdits(document, range, options) { return worker(document, async () => { - const options_2 = await context.env.getConfiguration?.('json.format'); - if (!(options_2?.enable ?? true)) { + if (!await isFormattingEnabled(document, context)) { return; } + const formatOptions = await getFormattingOptions(document, context); + return jsonLs.format(document, range, { - ...options_2, ...options, + ...formatOptions, }); }); }, }; - function worker(document: TextDocument, callback: (jsonDocument: json.JSONDocument) => T) { + async function worker(document: TextDocument, callback: (jsonDocument: json.JSONDocument) => T): Promise | undefined> { const jsonDocument = getJsonDocument(document); if (!jsonDocument) return; - return callback(jsonDocument); + await (initializing ??= initialize()); + + return await callback(jsonDocument); + } + + async function initialize() { + const settings = await getLanguageSettings(context); + jsonLs.configure(settings); } function getJsonDocument(textDocument: TextDocument) { - if (textDocument.languageId !== 'json' && textDocument.languageId !== 'jsonc') + if (!matchDocument(documentSelector, textDocument)) { return; + } const cache = jsonDocuments.get(textDocument); if (cache) { @@ -165,3 +220,12 @@ export function create(settings?: json.LanguageSettings): ServicePlugin { }, }; } + +function matchDocument(selector: DocumentSelector, document: TextDocument) { + for (const sel of selector) { + if (sel === document.languageId || (typeof sel === 'object' && sel.language === document.languageId)) { + return true; + } + } + return false; +} diff --git a/packages/markdown/index.ts b/packages/markdown/index.ts index b7ec8d04..7da561ad 100644 --- a/packages/markdown/index.ts +++ b/packages/markdown/index.ts @@ -1,4 +1,4 @@ -import { ServicePluginInstance, forEachEmbeddedCode, type FileChangeType, type FileType, type LocationLink, type ServicePlugin } from '@volar/language-service'; +import { ServicePluginInstance, forEachEmbeddedCode, type FileChangeType, type FileType, type LocationLink, type ServicePlugin, ServiceContext, DocumentSelector } from '@volar/language-service'; import { Emitter } from 'vscode-jsonrpc'; import type { TextDocument } from 'vscode-languageserver-textdocument'; import type { DiagnosticOptions, ILogger, IMdLanguageService, IMdParser, IWorkspace } from 'vscode-markdown-languageservice'; @@ -10,28 +10,23 @@ export interface Provide { 'markdown/languageService': () => IMdLanguageService; } -export interface CreateOptions { - /** - * The section to use for configuring validation options. - * - * @example 'markdown.validate' - */ - configurationSection: string; -} - const md = new MarkdownIt(); -function isMarkdown(document: TextDocument): boolean { - return document.languageId === 'markdown'; -} - function assert(condition: unknown, message: string): asserts condition { if (!condition) { throw new Error(message); } } -export function create(options: CreateOptions): ServicePlugin { +export function create({ + documentSelector = ['markdown'], + getDiagnosticOptions = async (_document, context) => { + return await context.env.getConfiguration?.('markdown.validate'); + }, +}: { + documentSelector?: DocumentSelector; + getDiagnosticOptions?(document: TextDocument, context: ServiceContext): Promise; +} = {}): ServicePlugin { return { name: 'markdown', triggerCharacters: ['.', '/', '#'], @@ -39,7 +34,7 @@ export function create(options: CreateOptions): ServicePlugin { let lastProjectVersion: string | undefined; - const { fs, getConfiguration, onDidChangeWatchedFiles } = context.env; + const { fs, onDidChangeWatchedFiles } = context.env; assert(fs, 'context.env.fs must be defined'); assert( onDidChangeWatchedFiles, @@ -76,7 +71,6 @@ export function create(options: CreateOptions): ServicePlugin { } break; } - case 1 satisfies typeof FileChangeType.Created: { const document = getTextDocument(change.uri, false); if (document) { @@ -84,7 +78,6 @@ export function create(options: CreateOptions): ServicePlugin { } break; } - case 3 satisfies typeof FileChangeType.Deleted: { onDidDeleteMarkdownDocument.fire(URI.parse(change.uri)); break; @@ -105,7 +98,7 @@ export function create(options: CreateOptions): ServicePlugin { hasMarkdownDocument(resource) { const document = getTextDocument(resource.toString(), true); - return Boolean(document && isMarkdown(document)); + return Boolean(document && matchDocument(documentSelector, document)); }, onDidChangeMarkdownDocument: onDidChangeMarkdownDocument.event, @@ -133,7 +126,7 @@ export function create(options: CreateOptions): ServicePlugin { } }, - workspaceFolders: [] + workspaceFolders: [URI.parse(context.env.workspaceFolder)], }; const ls = createLanguageService({ @@ -166,7 +159,7 @@ export function create(options: CreateOptions): ServicePlugin { const [_, sourceFile] = context.documents.getVirtualCodeByUri(uri); if (sourceFile?.generated) { for (const virtualCode of forEachEmbeddedCode(sourceFile.generated.code)) { - if (virtualCode.languageId === 'markdown') { + if (matchDocument(documentSelector, virtualCode)) { const uri = context.documents.getVirtualCodeUri(sourceFile.id, virtualCode.id); const document = context.documents.get(uri, virtualCode.languageId, virtualCode.snapshot); newVersions.set(document.uri, document); @@ -175,7 +168,7 @@ export function create(options: CreateOptions): ServicePlugin { } else if (sourceFile) { const document = context.documents.get(fileName, sourceFile.languageId, sourceFile.snapshot); - if (document && isMarkdown(document)) { + if (document && matchDocument(documentSelector, document)) { newVersions.set(document.uri, document); } } @@ -199,7 +192,7 @@ export function create(options: CreateOptions): ServicePlugin { } }; const prepare = (document: TextDocument) => { - if (!isMarkdown(document)) { + if (!matchDocument(documentSelector, document)) { return false; } sync(); @@ -262,13 +255,9 @@ export function create(options: CreateOptions): ServicePlugin { async provideDiagnostics(document, token) { if (prepare(document)) { - const configuration = await getConfiguration?.(options.configurationSection, document.uri); + const configuration = await getDiagnosticOptions(document, context); if (configuration) { - return ls.computeDiagnostics( - document, - configuration as DiagnosticOptions, - token - ); + return ls.computeDiagnostics(document, configuration, token); } } }, @@ -363,3 +352,12 @@ export function create(options: CreateOptions): ServicePlugin { }, }; } + +function matchDocument(selector: DocumentSelector, document: { languageId: string; }) { + for (const sel of selector) { + if (sel === document.languageId || (typeof sel === 'object' && sel.language === document.languageId)) { + return true; + } + } + return false; +} diff --git a/packages/prettyhtml/index.ts b/packages/prettyhtml/index.ts index fd538985..abc5cb98 100644 --- a/packages/prettyhtml/index.ts +++ b/packages/prettyhtml/index.ts @@ -1,21 +1,34 @@ -import type { ServicePluginInstance, ServicePlugin } from '@volar/language-service'; +import type { ServicePluginInstance, ServicePlugin, DocumentSelector, TextDocument, ServiceContext } from '@volar/language-service'; import * as prettyhtml from '@starptech/prettyhtml'; -export function create(configs: NonNullable[1]>): ServicePlugin { +export type FormattingOptions = Parameters[1]; + +export function create({ + documentSelector = ['html'], + isFormattingEnabled = async () => true, + getFormattingOptions = async () => ({}), +}: { + documentSelector?: DocumentSelector; + isFormattingEnabled?(document: TextDocument, context: ServiceContext): Promise; + getFormattingOptions?(document: TextDocument, context: ServiceContext): Promise; +} = {}): ServicePlugin { return { name: 'prettyhtml', - create(): ServicePluginInstance { + create(context): ServicePluginInstance { return { - provideDocumentFormattingEdits(document, range, options) { + async provideDocumentFormattingEdits(document, range, options) { + + if (!matchDocument(documentSelector, document)) + return; - if (document.languageId !== 'html') + if (!await isFormattingEnabled(document, context)) return; const oldRangeText = document.getText(range); const newRangeText = prettyhtml(oldRangeText, { - ...configs, tabWidth: options.tabSize, useTabs: !options.insertSpaces, + ...await getFormattingOptions(document, context), }).contents; if (newRangeText === oldRangeText) @@ -43,3 +56,12 @@ export function create(configs: NonNullable[1]>): }, }; } + +function matchDocument(selector: DocumentSelector, document: TextDocument) { + for (const sel of selector) { + if (sel === document.languageId || (typeof sel === 'object' && sel.language === document.languageId)) { + return true; + } + } + return false; +} diff --git a/packages/pug-beautify/index.ts b/packages/pug-beautify/index.ts index 9b3c72e5..55011d91 100644 --- a/packages/pug-beautify/index.ts +++ b/packages/pug-beautify/index.ts @@ -1,13 +1,24 @@ -import type { ServicePluginInstance, ServicePlugin } from '@volar/language-service'; +import type { ServicePluginInstance, ServicePlugin, DocumentSelector, TextDocument, ServiceContext } from '@volar/language-service'; -export function create(): ServicePlugin { +export function create({ + documentSelector = ['jade'], + isFormattingEnabled = async () => { + return true; + }, +}: { + documentSelector?: DocumentSelector; + isFormattingEnabled?(document: TextDocument, context: ServiceContext): Promise; +} = {}): ServicePlugin { return { name: 'pug-beautify', - create(): ServicePluginInstance { + create(context): ServicePluginInstance { return { - provideDocumentFormattingEdits(document, range, options) { + async provideDocumentFormattingEdits(document, range, options) { - if (document.languageId !== 'jade') + if (!matchDocument(documentSelector, document)) + return; + + if (!await isFormattingEnabled(document, context)) return; const pugCode = document.getText(range); @@ -36,3 +47,12 @@ export function create(): ServicePlugin { }, }; } + +function matchDocument(selector: DocumentSelector, document: TextDocument) { + for (const sel of selector) { + if (sel === document.languageId || (typeof sel === 'object' && sel.language === document.languageId)) { + return true; + } + } + return false; +} diff --git a/packages/pug/index.ts b/packages/pug/index.ts index 555f0dbf..32d5b32b 100644 --- a/packages/pug/index.ts +++ b/packages/pug/index.ts @@ -1,4 +1,4 @@ -import { transformDocumentSymbol, type Diagnostic, type DiagnosticSeverity, type ServicePlugin, ServicePluginInstance } from '@volar/language-service'; +import { transformDocumentSymbol, type Diagnostic, type DiagnosticSeverity, type Disposable, type DocumentSelector, type ServiceContext, type ServicePlugin, type ServicePluginInstance } from '@volar/language-service'; import { create as createHtmlService } from 'volar-service-html'; import type * as html from 'vscode-html-languageservice'; import type { TextDocument } from 'vscode-languageserver-textdocument'; @@ -7,11 +7,21 @@ import * as pug from './lib/languageService'; export interface Provide { 'pug/pugDocument': (document: TextDocument) => pug.PugDocument | undefined; 'pug/languageService': () => pug.LanguageService; - 'pug/updateCustomData': (extraData: html.IHTMLDataProvider[]) => void; } -export function create(): ServicePlugin { - const _htmlService = createHtmlService(); +export function create({ + documentSelector = ['jade'], + getCustomData, + onDidChangeCustomData, +}: { + documentSelector?: DocumentSelector; + getCustomData?(context: ServiceContext): Promise; + onDidChangeCustomData?(listener: () => void, context: ServiceContext): Disposable; +} = {}): ServicePlugin { + const _htmlService = createHtmlService({ + getCustomData, + onDidChangeCustomData, + }); return { ..._htmlService, name: 'pug', @@ -27,7 +37,6 @@ export function create(): ServicePlugin { provide: { 'pug/pugDocument': getPugDocument, 'pug/languageService': () => pugLs, - 'pug/updateCustomData': htmlService.provide['html/updateCustomData'], }, provideCompletionItems(document, position, _) { @@ -138,7 +147,7 @@ export function create(): ServicePlugin { function getPugDocument(document: TextDocument) { - if (document.languageId !== 'jade') + if (!matchDocument(documentSelector, document)) return; const cache = pugDocuments.get(document); @@ -157,3 +166,12 @@ export function create(): ServicePlugin { }, }; } + +function matchDocument(selector: DocumentSelector, document: TextDocument) { + for (const sel of selector) { + if (sel === document.languageId || (typeof sel === 'object' && sel.language === document.languageId)) { + return true; + } + } + return false; +} diff --git a/packages/sass-formatter/index.ts b/packages/sass-formatter/index.ts index 125c4390..d2c75f2a 100644 --- a/packages/sass-formatter/index.ts +++ b/packages/sass-formatter/index.ts @@ -1,27 +1,38 @@ -import type { ServicePluginInstance, ServicePlugin } from '@volar/language-service'; -import { SassFormatter } from 'sass-formatter'; +import type { ServicePluginInstance, ServicePlugin, DocumentSelector, TextDocument, ServiceContext } from '@volar/language-service'; +import { SassFormatter, SassFormatterConfig } from 'sass-formatter'; -export function create(configs: Parameters[1]): ServicePlugin { +export function create({ + documentSelector = ['sass'], + isFormattingEnabled = async () => true, + getFormatterConfig, +}: { + documentSelector?: DocumentSelector; + isFormattingEnabled?(document: TextDocument, context: ServiceContext): Promise; + getFormatterConfig?(document: TextDocument): Promise; +} = {}): ServicePlugin { return { name: 'sass-formatter', - create(): ServicePluginInstance { + create(context): ServicePluginInstance { return { - provideDocumentFormattingEdits(document, range, options) { + async provideDocumentFormattingEdits(document, range, options) { - if (document.languageId !== 'sass') + if (!matchDocument(documentSelector, document)) return; - const _options: typeof configs = { - ...configs, - insertSpaces: options.insertSpaces, + if (!await isFormattingEnabled(document, context)) + return; + + const config = { + ...options, + ...getFormatterConfig ? await getFormatterConfig(document) : {}, }; // don't set when options.insertSpaces is false to avoid sass-formatter internal judge bug - if (options.insertSpaces) - _options.tabSize = options.tabSize; + if (config.insertSpaces) + config.tabSize = options.tabSize; return [{ - newText: SassFormatter.Format(document.getText(), _options), + newText: SassFormatter.Format(document.getText(), config), range: range, }]; }, @@ -29,3 +40,12 @@ export function create(configs: Parameters[1]): Ser }, }; } + +function matchDocument(selector: DocumentSelector, document: TextDocument) { + for (const sel of selector) { + if (sel === document.languageId || (typeof sel === 'object' && sel.language === document.languageId)) { + return true; + } + } + return false; +} diff --git a/packages/typescript/index.ts b/packages/typescript/index.ts index a1fdc865..375c1939 100644 --- a/packages/typescript/index.ts +++ b/packages/typescript/index.ts @@ -1,4 +1,4 @@ -import type { CancellationToken, CompletionList, CompletionTriggerKind, FileChangeType, ServicePluginInstance, ServicePlugin, VirtualCode } from '@volar/language-service'; +import type { CancellationToken, CompletionList, CompletionTriggerKind, FileChangeType, ServicePluginInstance, ServicePlugin, VirtualCode, ServiceContext } from '@volar/language-service'; import * as semver from 'semver'; import type * as ts from 'typescript'; import type { TextDocument } from 'vscode-languageserver-textdocument'; @@ -44,7 +44,28 @@ export interface Provide { 'typescript/syntacticLanguageServiceHost': () => ts.LanguageServiceHost; }; -export function create(ts: typeof import('typescript')): ServicePlugin { +export function create( + ts: typeof import('typescript'), + { + isFormattingEnabled = async (document, context) => { + return await context.env.getConfiguration?.(getConfigTitle(document) + '.format.enable') ?? true; + }, + isValidationEnabled = async (document, context) => { + return await context.env.getConfiguration?.(getConfigTitle(document) + '.validate.enable') ?? true; + }, + isSuggestionsEnabled = async (document, context) => { + return await context.env.getConfiguration?.(getConfigTitle(document) + '.suggest.enabled') ?? true; + }, + isAutoClosingTagsEnabled = async (document, context) => { + return await context.env.getConfiguration?.(getConfigTitle(document) + '.autoClosingTags') ?? true; + }, + }: { + isFormattingEnabled?(document: TextDocument, context: ServiceContext): Promise; + isValidationEnabled?(document: TextDocument, context: ServiceContext): Promise; + isSuggestionsEnabled?(document: TextDocument, context: ServiceContext): Promise; + isAutoClosingTagsEnabled?(document: TextDocument, context: ServiceContext): Promise; + } = {}, +): ServicePlugin { const basicTriggerCharacters = getBasicTriggerCharacters(ts.version); const jsDocTriggerCharacter = '*'; const directiveCommentTriggerCharacter = '@'; @@ -109,20 +130,17 @@ export function create(ts: typeof import('typescript')): ServicePlugin { 'typescript/syntacticLanguageServiceHost': () => syntacticCtx.languageServiceHost, }, - provideAutoInsertionEdit(document, position, lastChange) { + async provideAutoInsertionEdit(document, position, lastChange) { if ( (document.languageId === 'javascriptreact' || document.languageId === 'typescriptreact') && lastChange.text.endsWith('>') + && await isAutoClosingTagsEnabled(document, context) ) { - const config = context.env.getConfiguration?.(getConfigTitle(document) + '.autoClosingTags') ?? true; - if (config) { - - const ctx = prepareSyntacticService(document); - const close = syntacticCtx.languageService.getJsxClosingTagAtPosition(ctx.fileName, document.offsetAt(position)); + const ctx = prepareSyntacticService(document); + const close = syntacticCtx.languageService.getJsxClosingTagAtPosition(ctx.fileName, document.offsetAt(position)); - if (close) { - return '$0' + close.newText; - } + if (close) { + return '$0' + close.newText; } } }, @@ -152,10 +170,8 @@ export function create(ts: typeof import('typescript')): ServicePlugin { if (!isTsDocument(document)) return; - const enable = await context.env.getConfiguration?.(getConfigTitle(document) + '.format.enable') ?? true; - if (!enable) { + if (!await isFormattingEnabled(document, context)) return; - } prepareSyntacticService(document); @@ -167,10 +183,8 @@ export function create(ts: typeof import('typescript')): ServicePlugin { if (!isTsDocument(document)) return; - const enable = await context.env.getConfiguration?.(getConfigTitle(document) + '.format.enable') ?? true; - if (!enable) { + if (!await isFormattingEnabled(document, context)) return; - } prepareSyntacticService(document); @@ -339,10 +353,8 @@ export function create(ts: typeof import('typescript')): ServicePlugin { if (!isSemanticDocument(document)) return; - const enable = await context.env.getConfiguration?.(getConfigTitle(document) + '.suggest.enabled') ?? true; - if (!enable) { + if (!await isSuggestionsEnabled(document, context)) return; - } return await worker(token, async () => { @@ -483,10 +495,8 @@ export function create(ts: typeof import('typescript')): ServicePlugin { if (!isSemanticDocument(document)) return; - const enable = await context.env.getConfiguration?.(getConfigTitle(document) + '.validate.enable') ?? true; - if (!enable) { + if (!await isValidationEnabled(document, context)) return; - } return await worker(token, () => { return doValidation(document.uri, { syntactic: true, suggestion: true }); @@ -498,10 +508,8 @@ export function create(ts: typeof import('typescript')): ServicePlugin { if (!isSemanticDocument(document)) return; - const enable = await context.env.getConfiguration?.(getConfigTitle(document) + '.validate.enable') ?? true; - if (!enable) { + if (!await isValidationEnabled(document, context)) return; - } return worker(token, () => { return doValidation(document.uri, { semantic: true, declaration: true }); diff --git a/packages/yaml/index.ts b/packages/yaml/index.ts index 46784b06..38e4f7e7 100644 --- a/packages/yaml/index.ts +++ b/packages/yaml/index.ts @@ -1,14 +1,10 @@ -import type { ServicePluginInstance, ServicePlugin } from '@volar/language-service'; +import type { ServicePluginInstance, ServicePlugin, DocumentSelector, ServiceContext, Disposable } from '@volar/language-service'; import type { TextDocument } from 'vscode-languageserver-textdocument'; -import type { LanguageService, LanguageSettings } from 'yaml-language-server'; -import { getLanguageService } from 'yaml-language-server'; +import * as yaml from 'yaml-language-server'; +import { URI, Utils } from 'vscode-uri'; export interface Provide { - 'yaml/languageService': () => LanguageService; -} - -function isYaml(document: TextDocument): boolean { - return document.languageId === 'yaml'; + 'yaml/languageService': () => yaml.LanguageService; } function noop(): undefined { } @@ -16,13 +12,42 @@ function noop(): undefined { } /** * Create a Volar language service for YAML documents. */ -export function create(settings?: LanguageSettings): ServicePlugin { +export function create({ + documentSelector = ['yaml'], + getWorkspaceContextService = () => { + return { + resolveRelativePath(relativePath, resource) { + const base = resource.substring(0, resource.lastIndexOf('/') + 1); + return Utils.resolvePath(URI.parse(base), relativePath).toString(); + }, + }; + }, + getLanguageSettings = async () => { + return { + completion: true, + customTags: [], + format: true, + hover: true, + isKubernetes: false, + validate: true, + yamlVersion: '1.2', + }; + }, + onDidChangeLanguageSettings = () => { + return { dispose() { } }; + }, +}: { + documentSelector?: DocumentSelector; + getWorkspaceContextService?(context: ServiceContext): yaml.WorkspaceContextService; + getLanguageSettings?(context: ServiceContext): Promise; + onDidChangeLanguageSettings?(listener: () => void, context: ServiceContext): Disposable; +} = {}): ServicePlugin { return { name: 'yaml', triggerCharacters: [' ', ':'], create(context): ServicePluginInstance { - const ls = getLanguageService({ + const ls = yaml.getLanguageService({ schemaRequestService: async (uri) => await context.env.fs?.readFile(uri) ?? '', telemetry: { send: noop, @@ -31,97 +56,114 @@ export function create(settings?: LanguageSettings): ServicePlugin { }, // @ts-expect-error https://github.com/redhat-developer/yaml-language-server/pull/910 clientCapabilities: context.env?.clientCapabilities, - workspaceContext: { - resolveRelativePath(relativePath, resource) { - return String(new URL(relativePath, resource)); - } - }, + workspaceContext: getWorkspaceContextService(context), }); + const disposable = onDidChangeLanguageSettings(() => initializing = undefined, context); - ls.configure({ - completion: true, - customTags: [], - format: true, - hover: true, - isKubernetes: false, - validate: true, - yamlVersion: '1.2', - ...settings - }); + let initializing: Promise | undefined; return { + dispose() { + disposable.dispose(); + }, + provide: { 'yaml/languageService': () => ls }, provideCodeActions(document, range, context) { - if (isYaml(document)) { + return worker(document, () => { return ls.getCodeAction(document, { context, range, textDocument: document }); - } + }); }, provideCodeLenses(document) { - if (isYaml(document)) { + return worker(document, () => { return ls.getCodeLens(document); - } + }); }, provideCompletionItems(document, position) { - if (isYaml(document)) { + return worker(document, () => { return ls.doComplete(document, position, false); - } + }); }, provideDefinition(document, position) { - if (isYaml(document)) { + return worker(document, () => { return ls.doDefinition(document, { position, textDocument: document }); - } + }); }, provideDiagnostics(document) { - if (isYaml(document)) { + return worker(document, () => { return ls.doValidation(document, false); - } + }); }, provideDocumentSymbols(document) { - if (isYaml(document)) { + return worker(document, () => { return ls.findDocumentSymbols2(document, {}); - } + }); }, provideHover(document, position) { - if (isYaml(document)) { + return worker(document, () => { return ls.doHover(document, position); - } + }); }, provideDocumentLinks(document) { - if (isYaml(document)) { + return worker(document, () => { return ls.findLinks(document); - } + }); }, provideFoldingRanges(document) { - if (isYaml(document)) { + return worker(document, () => { return ls.getFoldingRanges(document, context.env.clientCapabilities?.textDocument?.foldingRange ?? {}); - } + }); }, provideSelectionRanges(document, positions) { - if (isYaml(document)) { + return worker(document, () => { return ls.getSelectionRanges(document, positions); - } + }); }, resolveCodeLens(codeLens) { return ls.resolveCodeLens(codeLens); }, }; + + async function worker(document: TextDocument, callback: () => T): Promise | undefined> { + + if (!matchDocument(documentSelector, document)) { + return; + } + + await (initializing ??= initialize()); + + return await callback(); + } + + async function initialize() { + const settings = await getLanguageSettings(context); + ls.configure(settings); + } }, }; } + +function matchDocument(selector: DocumentSelector, document: TextDocument) { + for (const sel of selector) { + if (sel === document.languageId || (typeof sel === 'object' && sel.language === document.languageId)) { + return true; + } + } + return false; +} diff --git a/packages/yaml/package.json b/packages/yaml/package.json index cc88ce49..76e70552 100644 --- a/packages/yaml/package.json +++ b/packages/yaml/package.json @@ -24,7 +24,8 @@ "url": "https://github.com/remcohaszing" }, "dependencies": { - "yaml-language-server": "1.14.0" + "yaml-language-server": "1.14.0", + "vscode-uri": "^3.0.8" }, "devDependencies": { "vscode-languageserver-textdocument": "^1.0.11" From bc793028e43d376873d8309b0d086d8b076598bd Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Mon, 26 Feb 2024 09:57:35 +0800 Subject: [PATCH 2/4] Update index.ts --- packages/html/index.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/html/index.ts b/packages/html/index.ts index 1246bfde..bef56196 100644 --- a/packages/html/index.ts +++ b/packages/html/index.ts @@ -85,7 +85,7 @@ export function create({ isAutoCreateQuotesEnabled?(document: TextDocument, context: ServiceContext): Promise; isAutoClosingTagsEnabled?(document: TextDocument, context: ServiceContext): Promise; getDocumentContext?(context: ServiceContext): html.DocumentContext; - getFormattingOptions?(document: TextDocument, context: ServiceContext): Promise; + getFormattingOptions?(document: TextDocument, context: ServiceContext): Promise; getCompletionConfiguration?(document: TextDocument, context: ServiceContext): Promise; getHoverSettings?(document: TextDocument, context: ServiceContext): Promise; getCustomData?(context: ServiceContext): Promise; @@ -212,13 +212,19 @@ export function create({ }; } - const formatSettings = await getFormattingOptions(document, context); - const formatOptions: html.HTMLFormatConfiguration = { + const formatSettings: html.HTMLFormatConfiguration = { ...options, - ...formatSettings, endWithNewline: options.insertFinalNewline ? true : options.trimFinalNewlines ? false : undefined, + ...await getFormattingOptions(document, context), }; + // https://github.com/microsoft/vscode/blob/a8f73340be02966c3816a2f23cb7e446a3a7cb9b/extensions/html-language-features/server/src/modes/htmlMode.ts#L47-L51 + if (formatSettings.contentUnformatted) { + formatSettings.contentUnformatted = formatSettings.contentUnformatted + ',script'; + } else { + formatSettings.contentUnformatted = 'script'; + } + let formatDocument = document; let prefixes = []; let suffixes = []; @@ -241,7 +247,7 @@ export function create({ }; } - let edits = htmlLs.format(formatDocument, formatRange, formatOptions); + let edits = htmlLs.format(formatDocument, formatRange, formatSettings); if (codeOptions) { let newText = TextDocument.applyEdits(formatDocument, edits); @@ -267,7 +273,7 @@ export function create({ function ensureNewLines(newText: string) { const verifyDocument = TextDocument.create(document.uri, document.languageId, document.version, ''); - const verifyEdits = htmlLs.format(verifyDocument, undefined, formatOptions); + const verifyEdits = htmlLs.format(verifyDocument, undefined, formatSettings); let verifyText = TextDocument.applyEdits(verifyDocument, verifyEdits); verifyText = verifyText.trim().slice(''.length); if (startWithNewLine(verifyText) !== startWithNewLine(newText)) { From cdd7eecfeb2f425865d327caee456b52e5f8c08f Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Mon, 26 Feb 2024 10:04:09 +0800 Subject: [PATCH 3/4] Update index.ts --- packages/css/index.ts | 84 ++++++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/packages/css/index.ts b/packages/css/index.ts index 685de459..65730889 100644 --- a/packages/css/index.ts +++ b/packages/css/index.ts @@ -4,7 +4,7 @@ import { TextDocument } from 'vscode-languageserver-textdocument'; import { URI, Utils } from 'vscode-uri'; export interface Provide { - 'css/stylesheet': (document: TextDocument) => css.Stylesheet | undefined; + 'css/stylesheet': (document: TextDocument, ls: css.LanguageService) => css.Stylesheet; 'css/languageService': (document: TextDocument) => css.LanguageService | undefined; } @@ -315,32 +315,55 @@ export function create({ function getCssLs(document: TextDocument): css.LanguageService | undefined { if (matchDocument(cssDocumentSelector, document)) { - return cssLs ??= css.getCSSLanguageService({ - fileSystemProvider, - clientCapabilities: context.env.clientCapabilities, - useDefaultDataProvider, - customDataProviders: customData, - }); + if (!cssLs) { + cssLs = css.getCSSLanguageService({ + fileSystemProvider, + clientCapabilities: context.env.clientCapabilities, + useDefaultDataProvider, + customDataProviders: customData, + }); + cssLs.setDataProviders(useDefaultDataProvider, customData); + } + return cssLs; } else if (matchDocument(scssDocumentSelector, document)) { - return scssLs ??= css.getSCSSLanguageService({ - fileSystemProvider, - clientCapabilities: context.env.clientCapabilities, - useDefaultDataProvider, - customDataProviders: customData, - }); + if (!scssLs) { + scssLs = css.getSCSSLanguageService({ + fileSystemProvider, + clientCapabilities: context.env.clientCapabilities, + useDefaultDataProvider, + customDataProviders: customData, + }); + scssLs.setDataProviders(useDefaultDataProvider, customData); + } + return scssLs; } else if (matchDocument(lessDocumentSelector, document)) { - return lessLs ??= css.getLESSLanguageService({ - fileSystemProvider, - clientCapabilities: context.env.clientCapabilities, - useDefaultDataProvider, - customDataProviders: customData, - }); + if (!lessLs) { + lessLs = css.getLESSLanguageService({ + fileSystemProvider, + clientCapabilities: context.env.clientCapabilities, + useDefaultDataProvider, + customDataProviders: customData, + }); + lessLs.setDataProviders(useDefaultDataProvider, customData); + } + return lessLs; } } - function getStylesheet(document: TextDocument) { + async function worker(document: TextDocument, callback: (stylesheet: css.Stylesheet, cssLs: css.LanguageService) => T) { + + const cssLs = getCssLs(document); + if (!cssLs) + return; + + await (initializing ??= initialize()); + + return callback(getStylesheet(document, cssLs), cssLs); + } + + function getStylesheet(document: TextDocument, ls: css.LanguageService) { const cache = stylesheets.get(document); if (cache) { @@ -350,31 +373,12 @@ export function create({ } } - const cssLs = getCssLs(document); - if (!cssLs) - return; - - const stylesheet = cssLs.parseStylesheet(document); + const stylesheet = ls.parseStylesheet(document); stylesheets.set(document, [document.version, stylesheet]); return stylesheet; } - async function worker(document: TextDocument, callback: (stylesheet: css.Stylesheet, cssLs: css.LanguageService) => T) { - - const stylesheet = getStylesheet(document); - if (!stylesheet) - return; - - const cssLs = getCssLs(document); - if (!cssLs) - return; - - await (initializing ??= initialize()); - - return callback(stylesheet, cssLs); - } - async function initialize() { customData = await getCustomData(context); cssLs?.setDataProviders(useDefaultDataProvider, customData); From 749775b191d35851fe3b6e1290dad7fa9673acb6 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Mon, 26 Feb 2024 10:33:46 +0800 Subject: [PATCH 4/4] update types [skip ci] --- packages/css/index.ts | 10 +++++----- packages/emmet/index.ts | 26 ++++++++++++++++++++++++-- packages/emmet/package.json | 2 +- packages/html/index.ts | 16 ++++++++-------- packages/json/index.ts | 12 ++++++------ packages/markdown/index.ts | 5 +++-- packages/prettyhtml/index.ts | 10 +++++----- packages/pug-beautify/index.ts | 8 +++----- packages/pug/index.ts | 5 +++-- packages/sass-formatter/index.ts | 8 ++++---- packages/typescript/index.ts | 10 +++++----- packages/yaml/index.ts | 8 ++++---- 12 files changed, 71 insertions(+), 49 deletions(-) diff --git a/packages/css/index.ts b/packages/css/index.ts index 65730889..6f0d2726 100644 --- a/packages/css/index.ts +++ b/packages/css/index.ts @@ -1,4 +1,4 @@ -import type { CodeAction, Diagnostic, LocationLink, ServicePluginInstance, ServicePlugin, ServiceContext, DocumentSelector, Disposable } from '@volar/language-service'; +import type { CodeAction, Diagnostic, Disposable, DocumentSelector, LocationLink, Result, ServiceContext, ServicePlugin, ServicePluginInstance } from '@volar/language-service'; import * as css from 'vscode-css-languageservice'; import { TextDocument } from 'vscode-languageserver-textdocument'; import { URI, Utils } from 'vscode-uri'; @@ -74,10 +74,10 @@ export function create({ lessDocumentSelector?: DocumentSelector, useDefaultDataProvider?: boolean; getDocumentContext?(context: ServiceContext): css.DocumentContext; - isFormattingEnabled?(document: TextDocument, context: ServiceContext): Promise; - getFormatConfiguration?(document: TextDocument, context: ServiceContext): Promise; - getLanguageSettings?(document: TextDocument, context: ServiceContext): Promise; - getCustomData?(context: ServiceContext): Promise; + isFormattingEnabled?(document: TextDocument, context: ServiceContext): Result; + getFormatConfiguration?(document: TextDocument, context: ServiceContext): Result; + getLanguageSettings?(document: TextDocument, context: ServiceContext): Result; + getCustomData?(context: ServiceContext): Result; onDidChangeCustomData?(listener: () => void, context: ServiceContext): Disposable; } = {}): ServicePlugin { return { diff --git a/packages/emmet/index.ts b/packages/emmet/index.ts index 19834c0b..16ea790b 100644 --- a/packages/emmet/index.ts +++ b/packages/emmet/index.ts @@ -1,6 +1,28 @@ -import type { ServicePluginInstance, ServicePlugin } from '@volar/language-service'; +import type { ServicePluginInstance, ServicePlugin, TextDocument } from '@volar/language-service'; import * as emmet from '@vscode/emmet-helper'; -import { getHtmlDocument } from 'volar-service-html'; +import * as html from 'vscode-html-languageservice'; + +const htmlDocuments = new WeakMap(); + +let htmlLs: html.LanguageService; + +function getHtmlDocument(document: TextDocument) { + + const cache = htmlDocuments.get(document); + if (cache) { + const [cacheVersion, cacheDoc] = cache; + if (cacheVersion === document.version) { + return cacheDoc; + } + } + + htmlLs ??= html.getLanguageService(); + + const doc = htmlLs.parseHTMLDocument(document); + htmlDocuments.set(document, [document.version, doc]); + + return doc; +} export function create(): ServicePlugin { return { diff --git a/packages/emmet/package.json b/packages/emmet/package.json index b4503f24..7acde3f8 100644 --- a/packages/emmet/package.json +++ b/packages/emmet/package.json @@ -25,7 +25,7 @@ }, "dependencies": { "@vscode/emmet-helper": "^2.9.2", - "volar-service-html": "0.0.30" + "vscode-html-languageservice": "^5.1.0" }, "peerDependencies": { "@volar/language-service": "~2.1.0" diff --git a/packages/html/index.ts b/packages/html/index.ts index bef56196..b1aeb39b 100644 --- a/packages/html/index.ts +++ b/packages/html/index.ts @@ -1,4 +1,4 @@ -import type { ServicePluginInstance, ServicePlugin, DocumentSelector, ServiceContext, Disposable } from '@volar/language-service'; +import type { Disposable, DocumentSelector, Result, ServiceContext, ServicePlugin, ServicePluginInstance } from '@volar/language-service'; import * as html from 'vscode-html-languageservice'; import { TextDocument } from 'vscode-languageserver-textdocument'; import { URI, Utils } from 'vscode-uri'; @@ -81,14 +81,14 @@ export function create({ documentSelector?: DocumentSelector; useDefaultDataProvider?: boolean; useCustomDataProviders?: boolean; - isFormattingEnabled?(document: TextDocument, context: ServiceContext): Promise; - isAutoCreateQuotesEnabled?(document: TextDocument, context: ServiceContext): Promise; - isAutoClosingTagsEnabled?(document: TextDocument, context: ServiceContext): Promise; + isFormattingEnabled?(document: TextDocument, context: ServiceContext): Result; + isAutoCreateQuotesEnabled?(document: TextDocument, context: ServiceContext): Result; + isAutoClosingTagsEnabled?(document: TextDocument, context: ServiceContext): Result; getDocumentContext?(context: ServiceContext): html.DocumentContext; - getFormattingOptions?(document: TextDocument, context: ServiceContext): Promise; - getCompletionConfiguration?(document: TextDocument, context: ServiceContext): Promise; - getHoverSettings?(document: TextDocument, context: ServiceContext): Promise; - getCustomData?(context: ServiceContext): Promise; + getFormattingOptions?(document: TextDocument, context: ServiceContext): Result; + getCompletionConfiguration?(document: TextDocument, context: ServiceContext): Result; + getHoverSettings?(document: TextDocument, context: ServiceContext): Result; + getCustomData?(context: ServiceContext): Result; onDidChangeCustomData?(listener: () => void, context: ServiceContext): Disposable; } = {}): ServicePlugin { return { diff --git a/packages/json/index.ts b/packages/json/index.ts index cd80eb4c..2edf945d 100644 --- a/packages/json/index.ts +++ b/packages/json/index.ts @@ -1,4 +1,4 @@ -import type { ServicePlugin, ServicePluginInstance, DocumentSelector, ServiceContext, Disposable } from '@volar/language-service'; +import type { ServicePlugin, ServicePluginInstance, DocumentSelector, ServiceContext, Disposable, Result } from '@volar/language-service'; import * as json from 'vscode-json-languageservice'; import type { TextDocument } from 'vscode-languageserver-textdocument'; import { URI, Utils } from 'vscode-uri'; @@ -51,7 +51,7 @@ export function create({ } return languageSettings; }, - getDocumentLanguageSettings = async document => { + getDocumentLanguageSettings = document => { return document.languageId === 'jsonc' ? { comments: 'ignore', trailingCommas: 'warning' } : { comments: 'error', trailingCommas: 'error' }; @@ -67,10 +67,10 @@ export function create({ }: { documentSelector?: DocumentSelector; getWorkspaceContextService?(context: ServiceContext): json.WorkspaceContextService; - isFormattingEnabled?(document: TextDocument, context: ServiceContext): Promise; - getFormattingOptions?(document: TextDocument, context: ServiceContext): Promise; - getLanguageSettings?(context: ServiceContext): Promise; - getDocumentLanguageSettings?(document: TextDocument, context: ServiceContext): Promise; + isFormattingEnabled?(document: TextDocument, context: ServiceContext): Result; + getFormattingOptions?(document: TextDocument, context: ServiceContext): Result; + getLanguageSettings?(context: ServiceContext): Result; + getDocumentLanguageSettings?(document: TextDocument, context: ServiceContext): Result; onDidChangeLanguageSettings?(listener: () => void, context: ServiceContext): Disposable; } = {}): ServicePlugin { return { diff --git a/packages/markdown/index.ts b/packages/markdown/index.ts index 7da561ad..acbb87fa 100644 --- a/packages/markdown/index.ts +++ b/packages/markdown/index.ts @@ -1,4 +1,5 @@ -import { ServicePluginInstance, forEachEmbeddedCode, type FileChangeType, type FileType, type LocationLink, type ServicePlugin, ServiceContext, DocumentSelector } from '@volar/language-service'; +import type { DocumentSelector, FileChangeType, FileType, LocationLink, Result, ServiceContext, ServicePlugin, ServicePluginInstance } from '@volar/language-service'; +import { forEachEmbeddedCode } from '@volar/language-service'; import { Emitter } from 'vscode-jsonrpc'; import type { TextDocument } from 'vscode-languageserver-textdocument'; import type { DiagnosticOptions, ILogger, IMdLanguageService, IMdParser, IWorkspace } from 'vscode-markdown-languageservice'; @@ -25,7 +26,7 @@ export function create({ }, }: { documentSelector?: DocumentSelector; - getDiagnosticOptions?(document: TextDocument, context: ServiceContext): Promise; + getDiagnosticOptions?(document: TextDocument, context: ServiceContext): Result; } = {}): ServicePlugin { return { name: 'markdown', diff --git a/packages/prettyhtml/index.ts b/packages/prettyhtml/index.ts index abc5cb98..c0a2985a 100644 --- a/packages/prettyhtml/index.ts +++ b/packages/prettyhtml/index.ts @@ -1,16 +1,16 @@ -import type { ServicePluginInstance, ServicePlugin, DocumentSelector, TextDocument, ServiceContext } from '@volar/language-service'; import * as prettyhtml from '@starptech/prettyhtml'; +import type { DocumentSelector, Result, ServiceContext, ServicePlugin, ServicePluginInstance, TextDocument } from '@volar/language-service'; export type FormattingOptions = Parameters[1]; export function create({ documentSelector = ['html'], - isFormattingEnabled = async () => true, - getFormattingOptions = async () => ({}), + isFormattingEnabled = () => true, + getFormattingOptions = () => ({}), }: { documentSelector?: DocumentSelector; - isFormattingEnabled?(document: TextDocument, context: ServiceContext): Promise; - getFormattingOptions?(document: TextDocument, context: ServiceContext): Promise; + isFormattingEnabled?(document: TextDocument, context: ServiceContext): Result; + getFormattingOptions?(document: TextDocument, context: ServiceContext): Result; } = {}): ServicePlugin { return { name: 'prettyhtml', diff --git a/packages/pug-beautify/index.ts b/packages/pug-beautify/index.ts index 55011d91..4eac9480 100644 --- a/packages/pug-beautify/index.ts +++ b/packages/pug-beautify/index.ts @@ -1,13 +1,11 @@ -import type { ServicePluginInstance, ServicePlugin, DocumentSelector, TextDocument, ServiceContext } from '@volar/language-service'; +import type { DocumentSelector, Result, ServiceContext, ServicePlugin, ServicePluginInstance, TextDocument } from '@volar/language-service'; export function create({ documentSelector = ['jade'], - isFormattingEnabled = async () => { - return true; - }, + isFormattingEnabled = () => true, }: { documentSelector?: DocumentSelector; - isFormattingEnabled?(document: TextDocument, context: ServiceContext): Promise; + isFormattingEnabled?(document: TextDocument, context: ServiceContext): Result; } = {}): ServicePlugin { return { name: 'pug-beautify', diff --git a/packages/pug/index.ts b/packages/pug/index.ts index 32d5b32b..97d58823 100644 --- a/packages/pug/index.ts +++ b/packages/pug/index.ts @@ -1,4 +1,5 @@ -import { transformDocumentSymbol, type Diagnostic, type DiagnosticSeverity, type Disposable, type DocumentSelector, type ServiceContext, type ServicePlugin, type ServicePluginInstance } from '@volar/language-service'; +import type { Diagnostic, DiagnosticSeverity, Disposable, DocumentSelector, Result, ServiceContext, ServicePlugin, ServicePluginInstance } from '@volar/language-service'; +import { transformDocumentSymbol } from '@volar/language-service'; import { create as createHtmlService } from 'volar-service-html'; import type * as html from 'vscode-html-languageservice'; import type { TextDocument } from 'vscode-languageserver-textdocument'; @@ -15,7 +16,7 @@ export function create({ onDidChangeCustomData, }: { documentSelector?: DocumentSelector; - getCustomData?(context: ServiceContext): Promise; + getCustomData?(context: ServiceContext): Result; onDidChangeCustomData?(listener: () => void, context: ServiceContext): Disposable; } = {}): ServicePlugin { const _htmlService = createHtmlService({ diff --git a/packages/sass-formatter/index.ts b/packages/sass-formatter/index.ts index d2c75f2a..709d318a 100644 --- a/packages/sass-formatter/index.ts +++ b/packages/sass-formatter/index.ts @@ -1,14 +1,14 @@ -import type { ServicePluginInstance, ServicePlugin, DocumentSelector, TextDocument, ServiceContext } from '@volar/language-service'; +import type { DocumentSelector, Result, ServiceContext, ServicePlugin, ServicePluginInstance, TextDocument } from '@volar/language-service'; import { SassFormatter, SassFormatterConfig } from 'sass-formatter'; export function create({ documentSelector = ['sass'], - isFormattingEnabled = async () => true, + isFormattingEnabled = () => true, getFormatterConfig, }: { documentSelector?: DocumentSelector; - isFormattingEnabled?(document: TextDocument, context: ServiceContext): Promise; - getFormatterConfig?(document: TextDocument): Promise; + isFormattingEnabled?(document: TextDocument, context: ServiceContext): Result; + getFormatterConfig?(document: TextDocument): Result; } = {}): ServicePlugin { return { name: 'sass-formatter', diff --git a/packages/typescript/index.ts b/packages/typescript/index.ts index 375c1939..2ad5f3e7 100644 --- a/packages/typescript/index.ts +++ b/packages/typescript/index.ts @@ -1,4 +1,4 @@ -import type { CancellationToken, CompletionList, CompletionTriggerKind, FileChangeType, ServicePluginInstance, ServicePlugin, VirtualCode, ServiceContext } from '@volar/language-service'; +import type { CancellationToken, CompletionList, CompletionTriggerKind, FileChangeType, Result, ServiceContext, ServicePlugin, ServicePluginInstance, VirtualCode } from '@volar/language-service'; import * as semver from 'semver'; import type * as ts from 'typescript'; import type { TextDocument } from 'vscode-languageserver-textdocument'; @@ -60,10 +60,10 @@ export function create( return await context.env.getConfiguration?.(getConfigTitle(document) + '.autoClosingTags') ?? true; }, }: { - isFormattingEnabled?(document: TextDocument, context: ServiceContext): Promise; - isValidationEnabled?(document: TextDocument, context: ServiceContext): Promise; - isSuggestionsEnabled?(document: TextDocument, context: ServiceContext): Promise; - isAutoClosingTagsEnabled?(document: TextDocument, context: ServiceContext): Promise; + isFormattingEnabled?(document: TextDocument, context: ServiceContext): Result; + isValidationEnabled?(document: TextDocument, context: ServiceContext): Result; + isSuggestionsEnabled?(document: TextDocument, context: ServiceContext): Result; + isAutoClosingTagsEnabled?(document: TextDocument, context: ServiceContext): Result; } = {}, ): ServicePlugin { const basicTriggerCharacters = getBasicTriggerCharacters(ts.version); diff --git a/packages/yaml/index.ts b/packages/yaml/index.ts index 38e4f7e7..965b239f 100644 --- a/packages/yaml/index.ts +++ b/packages/yaml/index.ts @@ -1,7 +1,7 @@ -import type { ServicePluginInstance, ServicePlugin, DocumentSelector, ServiceContext, Disposable } from '@volar/language-service'; +import type { Disposable, DocumentSelector, Result, ServiceContext, ServicePlugin, ServicePluginInstance } from '@volar/language-service'; import type { TextDocument } from 'vscode-languageserver-textdocument'; -import * as yaml from 'yaml-language-server'; import { URI, Utils } from 'vscode-uri'; +import * as yaml from 'yaml-language-server'; export interface Provide { 'yaml/languageService': () => yaml.LanguageService; @@ -22,7 +22,7 @@ export function create({ }, }; }, - getLanguageSettings = async () => { + getLanguageSettings = () => { return { completion: true, customTags: [], @@ -39,7 +39,7 @@ export function create({ }: { documentSelector?: DocumentSelector; getWorkspaceContextService?(context: ServiceContext): yaml.WorkspaceContextService; - getLanguageSettings?(context: ServiceContext): Promise; + getLanguageSettings?(context: ServiceContext): Result; onDidChangeLanguageSettings?(listener: () => void, context: ServiceContext): Disposable; } = {}): ServicePlugin { return {