From 0c13968c1ab02d1e3737e5d34666eb7eaafb13b7 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 8 Dec 2023 01:31:05 +0800 Subject: [PATCH] feat: upgrade to 2.0 alpha (#64) --- package.json | 4 +- packages/css/package.json | 2 +- packages/css/src/index.ts | 489 +++++---- packages/emmet/package.json | 2 +- packages/emmet/src/empty.ts | 12 +- packages/emmet/src/index.ts | 133 ++- packages/eslint/package.json | 2 +- packages/eslint/src/index.ts | 183 ++-- packages/html/package.json | 2 +- packages/html/src/index.ts | 516 +++++----- packages/json/package.json | 2 +- packages/json/src/index.ts | 289 +++--- packages/markdown/package.json | 2 +- packages/markdown/src/index.ts | 523 +++++----- packages/prettier/package.json | 2 +- packages/prettier/src/index.ts | 164 ++- packages/pretty-ts-errors/package.json | 2 +- packages/pretty-ts-errors/src/index.ts | 29 +- packages/prettyhtml/package.json | 2 +- packages/prettyhtml/src/index.ts | 70 +- packages/pug-beautify/package.json | 2 +- packages/pug-beautify/src/index.ts | 70 +- packages/pug/package.json | 4 +- packages/pug/src/baseParse.ts | 3 +- packages/pug/src/empty.ts | 12 +- packages/pug/src/index.ts | 236 +++-- packages/pug/src/pugDocument.ts | 5 +- packages/pug/src/services/completion.ts | 6 +- .../pug/src/services/documentHighlight.ts | 8 +- packages/pug/src/services/documentLinks.ts | 6 +- packages/pug/src/services/hover.ts | 6 +- packages/pug/src/services/quoteComplete.ts | 2 +- packages/pug/src/services/scanner.ts | 10 +- packages/pug/src/services/selectionRanges.ts | 6 +- packages/sass-formatter/package.json | 2 +- packages/sass-formatter/src/index.ts | 44 +- packages/tsconfig/package.json | 2 +- packages/tsconfig/src/index.ts | 347 ++++--- packages/tslint/package.json | 2 +- packages/tslint/src/index.ts | 183 ++-- .../typescript-twoslash-queries/package.json | 2 +- .../typescript-twoslash-queries/src/index.ts | 62 +- packages/typescript/package.json | 4 +- .../src/configs/getFormatCodeSettings.ts | 6 +- .../src/configs/getUserPreferences.ts | 15 +- .../typescript/src/features/callHierarchy.ts | 8 +- .../typescript/src/features/codeAction.ts | 8 +- .../src/features/codeActionResolve.ts | 6 +- .../src/features/completions/basic.ts | 6 +- .../features/completions/directiveComment.ts | 2 +- .../src/features/completions/jsDoc.ts | 2 +- .../src/features/completions/resolve.ts | 4 +- .../typescript/src/features/definition.ts | 4 +- .../typescript/src/features/diagnostics.ts | 2 +- .../src/features/documentHighlight.ts | 2 +- .../typescript/src/features/documentSymbol.ts | 6 +- .../typescript/src/features/fileReferences.ts | 4 +- .../typescript/src/features/fileRename.ts | 4 +- .../typescript/src/features/foldingRanges.ts | 6 +- .../typescript/src/features/formatting.ts | 4 +- packages/typescript/src/features/hover.ts | 4 +- .../typescript/src/features/implementation.ts | 4 +- .../typescript/src/features/inlayHints.ts | 2 +- .../typescript/src/features/prepareRename.ts | 2 +- .../typescript/src/features/references.ts | 4 +- packages/typescript/src/features/rename.ts | 6 +- .../src/features/selectionRanges.ts | 4 +- .../typescript/src/features/semanticTokens.ts | 2 +- .../typescript/src/features/signatureHelp.ts | 4 +- .../typescript/src/features/typeDefinition.ts | 4 +- .../src/features/workspaceSymbol.ts | 6 +- packages/typescript/src/index.ts | 932 +++++++++--------- packages/typescript/src/types.ts | 2 + packages/typescript/src/utils/previewer.ts | 2 +- packages/typescript/src/utils/transforms.ts | 4 +- packages/vetur/package.json | 2 +- packages/vetur/src/index.ts | 407 ++++---- packages/yaml/package.json | 2 +- packages/yaml/src/index.ts | 225 ++--- pnpm-lock.yaml | 99 +- tsconfig.base.json | 3 + 81 files changed, 2619 insertions(+), 2644 deletions(-) diff --git a/package.json b/package.json index d24a1198..9fd81ed0 100644 --- a/package.json +++ b/package.json @@ -5,13 +5,13 @@ "build": "tsc -b", "watch": "tsc -b -w", "prerelease": "npm run build", - "release": "lerna publish --exact --force-publish --yes --sync-workspace-lock --no-push", + "release": "lerna publish --exact --force-publish --yes --sync-workspace-lock --no-push --dist-tag volar-2.0", "release:next": "npm run release -- --dist-tag next" }, "devDependencies": { "@lerna-lite/cli": "latest", "@lerna-lite/publish": "latest", - "@volar/language-service": "~1.11.0", + "@volar/language-service": "2.0.0-alpha.0", "typescript": "latest", "vscode-languageserver-protocol": "^3.17.5" } diff --git a/packages/css/package.json b/packages/css/package.json index cd01eb52..832ab0a8 100644 --- a/packages/css/package.json +++ b/packages/css/package.json @@ -33,7 +33,7 @@ "vscode-languageserver-textdocument": "^1.0.11" }, "peerDependencies": { - "@volar/language-service": "~1.11.0" + "@volar/language-service": "2.0.0-alpha.0" }, "peerDependenciesMeta": { "@volar/language-service": { diff --git a/packages/css/src/index.ts b/packages/css/src/index.ts index a8f521ca..617c6df3 100644 --- a/packages/css/src/index.ts +++ b/packages/css/src/index.ts @@ -1,4 +1,4 @@ -import type { CodeAction, Diagnostic, LocationLink, Service } from '@volar/language-service'; +import type { CodeAction, Diagnostic, LocationLink, ServicePluginInstance, ServicePlugin } from '@volar/language-service'; import * as css from 'vscode-css-languageservice'; import type { TextDocument } from 'vscode-languageserver-textdocument'; import { URI, Utils } from 'vscode-uri'; @@ -8,281 +8,274 @@ export interface Provide { 'css/languageService': (languageId: string) => css.LanguageService | undefined; } -// https://github.com/microsoft/vscode/blob/09850876e652688fb142e2e19fd00fd38c0bc4ba/extensions/css-language-features/server/src/cssServer.ts#L97 -const triggerCharacters = ['/', '-', ':']; - -export function create(): Service { - return (context): ReturnType> => { - - if (!context) { - return { triggerCharacters } as any; - } - - 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) => 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; - }, - }; - - return { - - provide: { - 'css/stylesheet': getStylesheet, - 'css/languageService': getCssLs, - }, - - triggerCharacters, - - 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; - }); - }, - - provideRenameRange(document, position) { - return worker(document, (stylesheet, cssLs) => { - return cssLs.prepareRename(document, position, stylesheet); - }); - }, - - provideRenameEdits(document, position, newName) { - return worker(document, (stylesheet, cssLs) => { - return cssLs.doRename(document, position, newName, stylesheet); - }); - }, - - provideCodeActions(document, range, context) { - return worker(document, (stylesheet, cssLs) => { - return cssLs.doCodeActions2(document, range, context, stylesheet) as CodeAction[]; - }); - }, - - provideDefinition(document, position) { - return worker(document, (stylesheet, cssLs) => { - - const location = cssLs.findDefinition(document, position, stylesheet); - - if (location) { - return [{ - targetUri: location.uri, - targetRange: location.range, - targetSelectionRange: location.range, - } satisfies LocationLink]; +export function create(): ServicePlugin { + return { + // 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) => context.env.fs?.readDirectory(uri) ?? [], + }; + const documentContext: css.DocumentContext = { + resolveReference(ref, base) { + if (ref.match(/^\w[\w\d+.-]*:/)) { + // starts with a schema + return ref; } - }); - }, - - async provideDiagnostics(document) { - return worker(document, async (stylesheet, cssLs) => { - - const settings = await context.env.getConfiguration?.(document.languageId); - - 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); - - return cssLs.doHover(document, position, stylesheet, settings?.hover); - }); - }, - - provideReferences(document, position) { - return worker(document, (stylesheet, cssLs) => { - return cssLs.findReferences(document, position, stylesheet); - }); - }, - - provideDocumentHighlights(document, position) { - return worker(document, (stylesheet, cssLs) => { - return cssLs.findDocumentHighlights(document, position, stylesheet); - }); - }, - - async provideDocumentLinks(document) { - return await worker(document, (stylesheet, cssLs) => { - return cssLs.findDocumentLinks2(document, stylesheet, documentContext); - }); - }, - - provideDocumentSymbols(document) { - return worker(document, (stylesheet, cssLs) => { - return cssLs.findDocumentSymbols2(document, stylesheet); - }); - }, - - provideDocumentColors(document) { - return worker(document, (stylesheet, cssLs) => { - return cssLs.findDocumentColors(document, stylesheet); - }); - }, - - provideColorPresentations(document, color, range) { - return worker(document, (stylesheet, cssLs) => { - return cssLs.getColorPresentations(document, stylesheet, color, range); - }); - }, - - provideFoldingRanges(document) { - return worker(document, (stylesheet, cssLs) => { - return cssLs.getFoldingRanges(document, stylesheet); - }); - }, - - provideSelectionRanges(document, positions) { - return worker(document, (stylesheet, cssLs) => { - return cssLs.getSelectionRanges(document, positions, stylesheet); - }); - }, - - async provideDocumentFormattingEdits(document, formatRange, options) { - return worker(document, async (_stylesheet, cssLs) => { - - const options_2 = await context.env.getConfiguration?.(document.languageId + '.format'); - if (options_2?.enable === false) { - return; + 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; + }, + }; + + return { + + provide: { + 'css/stylesheet': getStylesheet, + 'css/languageService': getCssLs, + }, + + 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; + }); + }, + + provideRenameRange(document, position) { + return worker(document, (stylesheet, cssLs) => { + return cssLs.prepareRename(document, position, stylesheet); + }); + }, + + provideRenameEdits(document, position, newName) { + return worker(document, (stylesheet, cssLs) => { + return cssLs.doRename(document, position, newName, stylesheet); + }); + }, + + provideCodeActions(document, range, context) { + return worker(document, (stylesheet, cssLs) => { + return cssLs.doCodeActions2(document, range, context, stylesheet) as CodeAction[]; + }); + }, + + provideDefinition(document, position) { + return worker(document, (stylesheet, cssLs) => { + + const location = cssLs.findDefinition(document, position, stylesheet); + + if (location) { + return [{ + targetUri: location.uri, + targetRange: location.range, + targetSelectionRange: location.range, + } satisfies LocationLink]; + } + }); + }, + + async provideDiagnostics(document) { + return worker(document, async (stylesheet, cssLs) => { + + const settings = await context.env.getConfiguration?.(document.languageId); + + 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); + + return cssLs.doHover(document, position, stylesheet, settings?.hover); + }); + }, + + provideReferences(document, position) { + return worker(document, (stylesheet, cssLs) => { + return cssLs.findReferences(document, position, stylesheet); + }); + }, + + provideDocumentHighlights(document, position) { + return worker(document, (stylesheet, cssLs) => { + return cssLs.findDocumentHighlights(document, position, stylesheet); + }); + }, + + async provideDocumentLinks(document) { + return await worker(document, (stylesheet, cssLs) => { + return cssLs.findDocumentLinks2(document, stylesheet, documentContext); + }); + }, - return cssLs.format(document, formatRange, { - ...options_2, - ...options, + provideDocumentSymbols(document) { + return worker(document, (stylesheet, cssLs) => { + return cssLs.findDocumentSymbols2(document, stylesheet); }); - }); - }, - }; + }, - async function initCustomData() { - if (!inited) { + provideDocumentColors(document) { + return worker(document, (stylesheet, cssLs) => { + return cssLs.findDocumentColors(document, stylesheet); + }); + }, + + provideColorPresentations(document, color, range) { + return worker(document, (stylesheet, cssLs) => { + return cssLs.getColorPresentations(document, stylesheet, color, range); + }); + }, + + provideFoldingRanges(document) { + return worker(document, (stylesheet, cssLs) => { + return cssLs.getFoldingRanges(document, stylesheet); + }); + }, + + provideSelectionRanges(document, positions) { + return worker(document, (stylesheet, cssLs) => { + return cssLs.getSelectionRanges(document, positions, stylesheet); + }); + }, + + async provideDocumentFormattingEdits(document, formatRange, options) { + return worker(document, async (_stylesheet, cssLs) => { + + const options_2 = await context.env.getConfiguration?.(document.languageId + '.format'); + if (options_2?.enable === false) { + return; + } + + return cssLs.format(document, formatRange, { + ...options_2, + ...options, + }); + }); + }, + }; + + 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); + }); - context?.env.onDidChangeConfiguration?.(async () => { const customData = await getCustomData(); cssLs.setDataProviders(true, customData); scssLs.setDataProviders(true, customData); lessLs.setDataProviders(true, customData); - }); - - const customData = await getCustomData(); - cssLs.setDataProviders(true, customData); - scssLs.setDataProviders(true, customData); - lessLs.setDataProviders(true, customData); - inited = true; + inited = true; + } } - } - async function getCustomData() { + async function getCustomData() { - const customData: string[] = await context?.env.getConfiguration?.('css.customData') ?? []; - const newData: css.ICSSDataProvider[] = []; + 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); + 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); + } } - } - return newData; - } + return newData; + } - function getCssLs(lang: string) { - switch (lang) { - case 'css': return cssLs; - case 'scss': return scssLs; - case 'less': return lessLs; - case 'postcss': return postcssLs; + function getCssLs(lang: string) { + switch (lang) { + case 'css': return cssLs; + case 'scss': return scssLs; + case 'less': return lessLs; + case 'postcss': return postcssLs; + } } - } - function getStylesheet(document: TextDocument) { + function getStylesheet(document: TextDocument) { - const cache = stylesheets.get(document); - if (cache) { - const [cacheVersion, cacheStylesheet] = cache; - if (cacheVersion === document.version) { - return cacheStylesheet; + const cache = stylesheets.get(document); + if (cache) { + const [cacheVersion, cacheStylesheet] = cache; + if (cacheVersion === document.version) { + return cacheStylesheet; + } } - } - const cssLs = getCssLs(document.languageId); - if (!cssLs) - return; + const cssLs = getCssLs(document.languageId); + if (!cssLs) + return; - const stylesheet = cssLs.parseStylesheet(document); - stylesheets.set(document, [document.version, stylesheet]); + const stylesheet = cssLs.parseStylesheet(document); + stylesheets.set(document, [document.version, stylesheet]); - return stylesheet; - } + return stylesheet; + } - async function worker(document: TextDocument, callback: (stylesheet: css.Stylesheet, cssLs: css.LanguageService) => T) { + async function worker(document: TextDocument, callback: (stylesheet: css.Stylesheet, cssLs: css.LanguageService) => T) { - const stylesheet = getStylesheet(document); - if (!stylesheet) - return; + const stylesheet = getStylesheet(document); + if (!stylesheet) + return; - const cssLs = getCssLs(document.languageId); - if (!cssLs) - return; + const cssLs = getCssLs(document.languageId); + if (!cssLs) + return; - await initCustomData(); + await initCustomData(); - return callback(stylesheet, cssLs); - } + return callback(stylesheet, cssLs); + } + }, }; } - -export default create; diff --git a/packages/emmet/package.json b/packages/emmet/package.json index 703712ab..e485f4fa 100644 --- a/packages/emmet/package.json +++ b/packages/emmet/package.json @@ -29,7 +29,7 @@ "volar-service-html": "0.0.17" }, "peerDependencies": { - "@volar/language-service": "~1.11.0" + "@volar/language-service": "2.0.0-alpha.0" }, "peerDependenciesMeta": { "@volar/language-service": { diff --git a/packages/emmet/src/empty.ts b/packages/emmet/src/empty.ts index 43a969a1..186782d2 100644 --- a/packages/emmet/src/empty.ts +++ b/packages/emmet/src/empty.ts @@ -1,9 +1,11 @@ -import type { Service } from '@volar/language-service'; +import type { ServicePlugin } from '@volar/language-service'; console.warn('volar-service-emmet: this module is not yet supported for web.'); -export function create(): Service { - return () => ({}); +export function create(): ServicePlugin { + return { + create() { + return {}; + }, + }; } - -export default create; diff --git a/packages/emmet/src/index.ts b/packages/emmet/src/index.ts index 599a90b3..e1154ecd 100644 --- a/packages/emmet/src/index.ts +++ b/packages/emmet/src/index.ts @@ -1,85 +1,78 @@ -import type { Service } from '@volar/language-service'; +import type { ServicePluginInstance, ServicePlugin } from '@volar/language-service'; import * as emmet from '@vscode/emmet-helper'; import { getHtmlDocument } from 'volar-service-html'; -// https://docs.emmet.io/abbreviations/syntax/ -const triggerCharacters = '>+^*()#.[]$@-{}'.split(''); +export function create(): ServicePlugin { + return { + // https://docs.emmet.io/abbreviations/syntax/ + triggerCharacters: '>+^*()#.[]$@-{}'.split(''), + create(context): ServicePluginInstance { -export function create(): Service { - return (context): ReturnType => { - - if (!context) { - return { triggerCharacters }; - } - - return { - - triggerCharacters, - - isAdditionalCompletion: true, - - async provideCompletionItems(textDocument, position) { - - const syntax = emmet.getEmmetMode(textDocument.languageId === 'vue' ? 'html' : textDocument.languageId); - if (!syntax) - return; + return { - // fix https://github.com/vuejs/language-tools/issues/1329 - if (syntax === 'html') { - const htmlDocument = getHtmlDocument(textDocument); - const node = htmlDocument.findNodeAt(textDocument.offsetAt(position)); - if (node.tag) { - let insideBlock = false; - if (node.startTagEnd !== undefined && node.endTagStart !== undefined) { - insideBlock = textDocument.offsetAt(position) >= node.startTagEnd && textDocument.offsetAt(position) <= node.endTagStart; - } - if (!insideBlock) { - return; + isAdditionalCompletion: true, + + async provideCompletionItems(textDocument, position) { + + const syntax = emmet.getEmmetMode(textDocument.languageId === 'vue' ? 'html' : textDocument.languageId); + if (!syntax) + return; + + // fix https://github.com/vuejs/language-tools/issues/1329 + if (syntax === 'html') { + const htmlDocument = getHtmlDocument(textDocument); + const node = htmlDocument.findNodeAt(textDocument.offsetAt(position)); + if (node.tag) { + let insideBlock = false; + if (node.startTagEnd !== undefined && node.endTagStart !== undefined) { + insideBlock = textDocument.offsetAt(position) >= node.startTagEnd && textDocument.offsetAt(position) <= node.endTagStart; + } + if (!insideBlock) { + return; + } } } - } - // monkey fix https://github.com/johnsoncodehk/volar/issues/1105 - if (syntax === 'jsx') - return; + // monkey fix https://github.com/johnsoncodehk/volar/issues/1105 + if (syntax === 'jsx') + return; - const emmetConfig = await getEmmetConfig(syntax); + const emmetConfig = await getEmmetConfig(syntax); - return emmet.doComplete(textDocument, position, syntax, emmetConfig); - }, - }; - - async function getEmmetConfig(syntax: string): Promise { - - const emmetConfig: any = await context?.env.getConfiguration?.('emmet') ?? {}; - const syntaxProfiles = Object.assign({}, emmetConfig['syntaxProfiles'] || {}); - const preferences = Object.assign({}, emmetConfig['preferences'] || {}); + return emmet.doComplete(textDocument, position, syntax, emmetConfig); + }, + }; - // jsx, xml and xsl syntaxes need to have self closing tags unless otherwise configured by user - if (syntax === 'jsx' || syntax === 'xml' || syntax === 'xsl') { - syntaxProfiles[syntax] = syntaxProfiles[syntax] || {}; - if (typeof syntaxProfiles[syntax] === 'object' - && !syntaxProfiles[syntax].hasOwnProperty('self_closing_tag') // Old Emmet format - && !syntaxProfiles[syntax].hasOwnProperty('selfClosingStyle') // Emmet 2.0 format - ) { - syntaxProfiles[syntax] = { - ...syntaxProfiles[syntax], - selfClosingStyle: 'xml' - }; + async function getEmmetConfig(syntax: string): Promise { + + const emmetConfig: any = await context.env.getConfiguration?.('emmet') ?? {}; + const syntaxProfiles = Object.assign({}, emmetConfig['syntaxProfiles'] || {}); + const preferences = Object.assign({}, emmetConfig['preferences'] || {}); + + // jsx, xml and xsl syntaxes need to have self closing tags unless otherwise configured by user + if (syntax === 'jsx' || syntax === 'xml' || syntax === 'xsl') { + syntaxProfiles[syntax] = syntaxProfiles[syntax] || {}; + if (typeof syntaxProfiles[syntax] === 'object' + && !syntaxProfiles[syntax].hasOwnProperty('self_closing_tag') // Old Emmet format + && !syntaxProfiles[syntax].hasOwnProperty('selfClosingStyle') // Emmet 2.0 format + ) { + syntaxProfiles[syntax] = { + ...syntaxProfiles[syntax], + selfClosingStyle: 'xml' + }; + } } - } - return { - preferences, - showExpandedAbbreviation: emmetConfig['showExpandedAbbreviation'], - showAbbreviationSuggestions: emmetConfig['showAbbreviationSuggestions'], - syntaxProfiles, - variables: emmetConfig['variables'], - excludeLanguages: emmetConfig['excludeLanguages'], - showSuggestionsAsSnippets: emmetConfig['showSuggestionsAsSnippets'] - }; - } + return { + preferences, + showExpandedAbbreviation: emmetConfig['showExpandedAbbreviation'], + showAbbreviationSuggestions: emmetConfig['showAbbreviationSuggestions'], + syntaxProfiles, + variables: emmetConfig['variables'], + excludeLanguages: emmetConfig['excludeLanguages'], + showSuggestionsAsSnippets: emmetConfig['showSuggestionsAsSnippets'] + }; + } + }, }; } - -export default create; diff --git a/packages/eslint/package.json b/packages/eslint/package.json index 6ff21ecc..01f5faa4 100644 --- a/packages/eslint/package.json +++ b/packages/eslint/package.json @@ -29,7 +29,7 @@ "volar-service-typescript": "0.0.17" }, "peerDependencies": { - "@volar/language-service": "~1.11.0", + "@volar/language-service": "2.0.0-alpha.0", "eslint": "*" }, "peerDependenciesMeta": { diff --git a/packages/eslint/src/index.ts b/packages/eslint/src/index.ts index 58c52736..4b9cc44e 100644 --- a/packages/eslint/src/index.ts +++ b/packages/eslint/src/index.ts @@ -1,105 +1,108 @@ -import type { Service, Diagnostic, CodeAction, ServiceContext } from '@volar/language-service'; +import type { CodeAction, Diagnostic, ServicePluginInstance, ServicePlugin } from '@volar/language-service'; import { ESLint, Linter } from 'eslint'; import type * as ts from 'typescript/lib/tsserverlibrary'; import type { Provide } from 'volar-service-typescript'; -export function create(resolveConfig?: (program: ts.Program) => Linter.Config): Service { +export function create(resolveConfig?: (program: ts.Program) => Linter.Config): ServicePlugin { const instances = new WeakMap(); const uriToLintResult = new Map(); - return (context: ServiceContext | undefined): ReturnType => ({ - - async provideSemanticDiagnostics(document) { - - const languageService = context!.inject('typescript/languageService'); - const eslint = getEslint(languageService.getProgram()!); - const lintResult = await eslint.lintText( - document.getText(), - { filePath: context!.env.uriToFileName(document.uri) }, - ); - uriToLintResult.set(document.uri, lintResult); - const diagnostics: Diagnostic[] = []; - - for (let i = 0; i < lintResult.length; i++) { - const result = lintResult[i]; - for (let j = 0; j < result.messages.length; j++) { - const message = result.messages[j]; - if (message.severity === 0) { - continue; - } - if (!message.line || !message.column) { - message.line = 1; - message.column = 1; + return { + create(context): ServicePluginInstance { + return { + async provideSemanticDiagnostics(document) { + + const languageService = context.inject('typescript/languageService'); + const eslint = getEslint(languageService.getProgram()!); + const lintResult = await eslint.lintText( + document.getText(), + { filePath: context.env.uriToFileName(document.uri) }, + ); + uriToLintResult.set(document.uri, lintResult); + const diagnostics: Diagnostic[] = []; + + for (let i = 0; i < lintResult.length; i++) { + const result = lintResult[i]; + for (let j = 0; j < result.messages.length; j++) { + const message = result.messages[j]; + if (message.severity === 0) { + continue; + } + if (!message.line || !message.column) { + message.line = 1; + message.column = 1; + } + diagnostics.push({ + source: 'eslint', + code: message.ruleId ?? undefined, + message: message.message, + severity: message.severity === 1 ? 2 : 1, + range: { + start: { + line: message.line - 1, + character: message.column - 1, + }, + end: { + line: message.endLine ? message.endLine - 1 : message.line - 1, + character: message.endColumn ? message.endColumn - 1 : message.column - 1, + }, + }, + data: { + uri: document.uri, + version: document.version, + indexes: [i, j], + }, + }); + } } - diagnostics.push({ - source: 'eslint', - code: message.ruleId ?? undefined, - message: message.message, - severity: message.severity === 1 ? 2 : 1, - range: { - start: { - line: message.line - 1, - character: message.column - 1, - }, - end: { - line: message.endLine ? message.endLine - 1 : message.line - 1, - character: message.endColumn ? message.endColumn - 1 : message.column - 1, - }, - }, - data: { - uri: document.uri, - version: document.version, - indexes: [i, j], - }, - }); - } - } - - return diagnostics; - }, - - provideCodeActions(document, _range, codeActionContext) { - const result: CodeAction[] = []; - - for (const diagnostic of codeActionContext.diagnostics) { - - if (diagnostic.source !== 'eslint') { - continue; - } - - if (diagnostic.data?.uri !== document.uri || diagnostic.data?.version !== document.version) { - continue; - } - - const lintResult = uriToLintResult.get(document.uri); - const message = lintResult?.[diagnostic.data.indexes[0]]?.messages[diagnostic.data.indexes[1]]; - if (!message?.fix) { - continue; - } - - const codeAction: CodeAction = { - title: 'Fix ESLint: ' + message.message, - kind: 'quickfix', - edit: { - changes: { - [document.uri]: [{ - range: { - start: document.positionAt(message.fix.range[0]), - end: document.positionAt(message.fix.range[1]), + return diagnostics; + }, + + provideCodeActions(document, _range, codeActionContext) { + + const result: CodeAction[] = []; + + for (const diagnostic of codeActionContext.diagnostics) { + + if (diagnostic.source !== 'eslint') { + continue; + } + + if (diagnostic.data?.uri !== document.uri || diagnostic.data?.version !== document.version) { + continue; + } + + const lintResult = uriToLintResult.get(document.uri); + const message = lintResult?.[diagnostic.data.indexes[0]]?.messages[diagnostic.data.indexes[1]]; + if (!message?.fix) { + continue; + } + + const codeAction: CodeAction = { + title: 'Fix ESLint: ' + message.message, + kind: 'quickfix', + edit: { + changes: { + [document.uri]: [{ + range: { + start: document.positionAt(message.fix.range[0]), + end: document.positionAt(message.fix.range[1]), + }, + newText: message.fix.text, + }], }, - newText: message.fix.text, - }], - }, - }, - }; - result.push(codeAction); - } + }, + }; + result.push(codeAction); + } - return result; + return result; + }, + }; }, - }); + }; function getEslint(program: ts.Program) { return instances.get(program) ?? instances.set(program, new ESLint( @@ -114,5 +117,3 @@ export function create(resolveConfig?: (program: ts.Program) => Linter.Config): )).get(program)!; } }; - -export default create; diff --git a/packages/html/package.json b/packages/html/package.json index 605f48ed..1aac2433 100644 --- a/packages/html/package.json +++ b/packages/html/package.json @@ -33,7 +33,7 @@ "vscode-languageserver-textdocument": "^1.0.11" }, "peerDependencies": { - "@volar/language-service": "~1.11.0" + "@volar/language-service": "2.0.0-alpha.0" }, "peerDependenciesMeta": { "@volar/language-service": { diff --git a/packages/html/src/index.ts b/packages/html/src/index.ts index 8f6c4c12..b0baad00 100644 --- a/packages/html/src/index.ts +++ b/packages/html/src/index.ts @@ -1,4 +1,4 @@ -import type { Service } from '@volar/language-service'; +import type { ServicePluginInstance, ServicePlugin } from '@volar/language-service'; import * as html from 'vscode-html-languageservice'; import type { TextDocument } from 'vscode-languageserver-textdocument'; import { URI, Utils } from 'vscode-uri'; @@ -29,9 +29,6 @@ export function getHtmlDocument(document: TextDocument) { return doc; } -// https://github.com/microsoft/vscode/blob/09850876e652688fb142e2e19fd00fd38c0bc4ba/extensions/html-language-features/server/src/htmlServer.ts#L183 -const triggerCharacters = ['.', ':', '<', '"', '=', '/']; - export function create({ languageId = 'html', useDefaultDataProvider = true, @@ -40,311 +37,308 @@ export function create({ languageId?: string; useDefaultDataProvider?: boolean; useCustomDataProviders?: boolean; -} = {}): Service { - return (context): ReturnType> => { +} = {}): ServicePlugin { + return { + // 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 fileSystemProvider: html.FileSystemProvider = { + 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: 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 + 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 htmlLs = html.getLanguageService({ + fileSystemProvider, + clientCapabilities: context.env.clientCapabilities, + }); + + context.env.onDidChangeConfiguration?.(() => { + shouldUpdateCustomData = true; + }); + + return { + + provide: { + 'html/htmlDocument': (document) => { + if (document.languageId === languageId) { + return getHtmlDocument(document); + } + }, + 'html/languageService': () => htmlLs, + 'html/documentContext': () => documentContext, + 'html/updateCustomData': updateExtraCustomData, + }, - if (!context) { - return { triggerCharacters } as any; - } + async provideCompletionItems(document, position) { + return worker(document, async (htmlDocument) => { - let shouldUpdateCustomData = true; - let customData: html.IHTMLDataProvider[] = []; - let extraData: html.IHTMLDataProvider[] = []; - - const fileSystemProvider: html.FileSystemProvider = { - 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: 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 - 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 htmlLs = html.getLanguageService({ - fileSystemProvider, - clientCapabilities: context.env.clientCapabilities, - }); - - context.env.onDidChangeConfiguration?.(() => { - shouldUpdateCustomData = true; - }); - - return { - - provide: { - 'html/htmlDocument': (document) => { - if (document.languageId === languageId) { - return getHtmlDocument(document); - } + const configs = await context.env.getConfiguration?.('html.completion'); + + return htmlLs.doComplete2(document, position, htmlDocument, documentContext, configs); + }); }, - 'html/languageService': () => htmlLs, - 'html/documentContext': () => documentContext, - 'html/updateCustomData': updateExtraCustomData, - }, - - triggerCharacters, - - async provideCompletionItems(document, position) { - return worker(document, async (htmlDocument) => { - - const configs = await context.env.getConfiguration?.('html.completion'); - - return htmlLs.doComplete2(document, position, htmlDocument, documentContext, configs); - }); - }, - - provideRenameRange(document, position) { - return worker(document, (htmlDocument) => { - const offset = document.offsetAt(position); - return htmlLs - .findDocumentHighlights(document, position, htmlDocument) - ?.find(h => offset >= document.offsetAt(h.range.start) && offset <= document.offsetAt(h.range.end)) - ?.range; - }); - }, - - provideRenameEdits(document, position, newName) { - return worker(document, (htmlDocument) => { - return htmlLs.doRename(document, position, newName, htmlDocument); - }); - }, - - async provideHover(document, position) { - return worker(document, async (htmlDocument) => { - - const hoverSettings = await context.env.getConfiguration?.('html.hover'); - - return htmlLs.doHover(document, position, htmlDocument, hoverSettings); - }); - }, - - provideDocumentHighlights(document, position) { - return worker(document, (htmlDocument) => { - return htmlLs.findDocumentHighlights(document, position, htmlDocument); - }); - }, - - provideDocumentLinks(document) { - return worker(document, () => { - return htmlLs.findDocumentLinks(document, documentContext); - }); - }, - - provideDocumentSymbols(document) { - return worker(document, (htmlDocument) => { - return htmlLs.findDocumentSymbols2(document, htmlDocument); - }); - }, - - provideFoldingRanges(document) { - return worker(document, () => { - return htmlLs.getFoldingRanges(document); - }); - }, - - provideSelectionRanges(document, positions) { - return worker(document, () => { - return htmlLs.getSelectionRanges(document, positions); - }); - }, - - async provideDocumentFormattingEdits(document, formatRange, options) { - return worker(document, async () => { - - const options_2 = await context.env.getConfiguration?.('html.format'); - if (options_2?.enable === false) { - return; - } - { // 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); - const content = document.getText(); - if (endPos.character === 0 && endPos.line > 0 && endOffset !== content.length) { - // if selection ends after a new line, exclude that new line - const prevLineStart = document.offsetAt({ line: endPos.line - 1, character: 0 }); - while (isEOL(content, endOffset - 1) && endOffset > prevLineStart) { - endOffset--; - } - formatRange = { - start: formatRange.start, - end: document.positionAt(endOffset), - }; - } - } + provideRenameRange(document, position) { + return worker(document, (htmlDocument) => { + const offset = document.offsetAt(position); + return htmlLs + .findDocumentHighlights(document, position, htmlDocument) + ?.find(h => offset >= document.offsetAt(h.range.start) && offset <= document.offsetAt(h.range.end)) + ?.range; + }); + }, + + provideRenameEdits(document, position, newName) { + return worker(document, (htmlDocument) => { + return htmlLs.doRename(document, position, newName, htmlDocument); + }); + }, + + async provideHover(document, position) { + return worker(document, async (htmlDocument) => { + + const hoverSettings = await context.env.getConfiguration?.('html.hover'); + + return htmlLs.doHover(document, position, htmlDocument, hoverSettings); + }); + }, + + provideDocumentHighlights(document, position) { + return worker(document, (htmlDocument) => { + return htmlLs.findDocumentHighlights(document, position, htmlDocument); + }); + }, + + provideDocumentLinks(document) { + return worker(document, () => { + return htmlLs.findDocumentLinks(document, documentContext); + }); + }, + + provideDocumentSymbols(document) { + return worker(document, (htmlDocument) => { + return htmlLs.findDocumentSymbols2(document, htmlDocument); + }); + }, + + provideFoldingRanges(document) { + return worker(document, () => { + return htmlLs.getFoldingRanges(document); + }); + }, - return htmlLs.format(document, formatRange, { - ...options_2, - ...options, + provideSelectionRanges(document, positions) { + return worker(document, () => { + return htmlLs.getSelectionRanges(document, positions); }); - }); - }, - - provideFormattingIndentSensitiveLines(document) { - return worker(document, (htmlDocument) => { - const lines: number[] = []; - /** - * comments - */ - const scanner = htmlLs.createScanner(document.getText()); - let token = scanner.scan(); - let startCommentTagLine: number | undefined; - while (token !== html.TokenType.EOS) { - if (token === html.TokenType.StartCommentTag) { - startCommentTagLine = document.positionAt(scanner.getTokenOffset()).line; + }, + + async provideDocumentFormattingEdits(document, formatRange, options) { + return worker(document, async () => { + + const options_2 = await context.env.getConfiguration?.('html.format'); + if (options_2?.enable === false) { + return; } - else if (token === html.TokenType.EndCommentTag) { - const line = document.positionAt(scanner.getTokenOffset()).line; - for (let i = startCommentTagLine! + 1; i <= line; i++) { - lines.push(i); + + { // 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); + const content = document.getText(); + if (endPos.character === 0 && endPos.line > 0 && endOffset !== content.length) { + // if selection ends after a new line, exclude that new line + const prevLineStart = document.offsetAt({ line: endPos.line - 1, character: 0 }); + while (isEOL(content, endOffset - 1) && endOffset > prevLineStart) { + endOffset--; + } + formatRange = { + start: formatRange.start, + end: document.positionAt(endOffset), + }; } - startCommentTagLine = undefined; } - else if (token === html.TokenType.AttributeValue) { - const startLine = document.positionAt(scanner.getTokenOffset()).line; - for (let i = 1; i < scanner.getTokenText().split('\n').length; i++) { - lines.push(startLine + i); + + return htmlLs.format(document, formatRange, { + ...options_2, + ...options, + }); + }); + }, + + provideFormattingIndentSensitiveLines(document) { + return worker(document, (htmlDocument) => { + const lines: number[] = []; + /** + * comments + */ + const scanner = htmlLs.createScanner(document.getText()); + let token = scanner.scan(); + let startCommentTagLine: number | undefined; + while (token !== html.TokenType.EOS) { + if (token === html.TokenType.StartCommentTag) { + startCommentTagLine = document.positionAt(scanner.getTokenOffset()).line; } - } - token = scanner.scan(); - } - /** - * tags - */ - // https://github.com/beautify-web/js-beautify/blob/686f8c1b265990908ece86ce39291733c75c997c/js/src/html/options.js#L81 - const indentSensitiveTags = new Set(['pre', 'textarea']); - htmlDocument.roots.forEach(function visit(node) { - if ( - node.tag !== undefined - && node.startTagEnd !== undefined - && node.endTagStart !== undefined - && indentSensitiveTags.has(node.tag) - ) { - for (let i = document.positionAt(node.startTagEnd).line + 1; i <= document.positionAt(node.endTagStart).line; i++) { - lines.push(i); + else if (token === html.TokenType.EndCommentTag) { + const line = document.positionAt(scanner.getTokenOffset()).line; + for (let i = startCommentTagLine! + 1; i <= line; i++) { + lines.push(i); + } + startCommentTagLine = undefined; } + else if (token === html.TokenType.AttributeValue) { + const startLine = document.positionAt(scanner.getTokenOffset()).line; + for (let i = 1; i < scanner.getTokenText().split('\n').length; i++) { + lines.push(startLine + i); + } + } + token = scanner.scan(); } - else { - node.children.forEach(visit); - } + /** + * tags + */ + // https://github.com/beautify-web/js-beautify/blob/686f8c1b265990908ece86ce39291733c75c997c/js/src/html/options.js#L81 + const indentSensitiveTags = new Set(['pre', 'textarea']); + htmlDocument.roots.forEach(function visit(node) { + if ( + node.tag !== undefined + && node.startTagEnd !== undefined + && node.endTagStart !== undefined + && indentSensitiveTags.has(node.tag) + ) { + for (let i = document.positionAt(node.startTagEnd).line + 1; i <= document.positionAt(node.endTagStart).line; i++) { + lines.push(i); + } + } + else { + node.children.forEach(visit); + } + }); + return lines; }); - return lines; - }); - }, + }, - provideLinkedEditingRanges(document, position) { - return worker(document, (htmlDocument) => { + provideLinkedEditingRanges(document, position) { + return worker(document, (htmlDocument) => { - const ranges = htmlLs.findLinkedEditingRanges(document, position, htmlDocument); + const ranges = htmlLs.findLinkedEditingRanges(document, position, htmlDocument); - if (!ranges) - return; + if (!ranges) + return; - return { ranges }; - }); - }, + return { ranges }; + }); + }, - async provideAutoInsertionEdit(document, position, insertContext) { - return worker(document, async (htmlDocument) => { + async provideAutoInsertionEdit(document, position, lastChange) { + return worker(document, async (htmlDocument) => { - const lastCharacter = insertContext.lastChange.text[insertContext.lastChange.text.length - 1]; + const lastCharacter = lastChange.text[lastChange.text.length - 1]; + const rangeLengthIsZero = lastChange.range.start.line && lastChange.range.end.line + && lastChange.range.start.character && lastChange.range.end.character; - if (insertContext.lastChange.rangeLength === 0 && lastCharacter === '=') { + if (rangeLengthIsZero && lastCharacter === '=') { - const enabled = (await context.env.getConfiguration?.('html.autoCreateQuotes')) ?? true; + const enabled = (await context.env.getConfiguration?.('html.autoCreateQuotes')) ?? true; - if (enabled) { + if (enabled) { - const text = htmlLs.doQuoteComplete(document, position, htmlDocument, await context.env.getConfiguration?.('html.completion')); + const text = htmlLs.doQuoteComplete(document, position, htmlDocument, await context.env.getConfiguration?.('html.completion')); - if (text) { - return text; + if (text) { + return text; + } } } - } - if (insertContext.lastChange.rangeLength === 0 && (lastCharacter === '>' || lastCharacter === '/')) { + if (rangeLengthIsZero && (lastCharacter === '>' || lastCharacter === '/')) { - const enabled = (await context.env.getConfiguration?.('html.autoClosingTags')) ?? true; + const enabled = (await context.env.getConfiguration?.('html.autoClosingTags')) ?? true; - if (enabled) { + if (enabled) { - const text = htmlLs.doTagComplete(document, position, htmlDocument); + const text = htmlLs.doTagComplete(document, position, htmlDocument); - if (text) { - return text; + if (text) { + return text; + } } } - } - }); - }, - }; - - async function initCustomData() { - if (shouldUpdateCustomData && useCustomDataProviders) { - shouldUpdateCustomData = false; - customData = await getCustomData(); - htmlLs.setDataProviders(useDefaultDataProvider, [...customData, ...extraData]); + }); + }, + }; + + 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]); - } + function updateExtraCustomData(data: html.IHTMLDataProvider[]) { + extraData = data; + htmlLs.setDataProviders(useDefaultDataProvider, [...customData, ...extraData]); + } - async function getCustomData() { + async function getCustomData() { - const customData: string[] = await context?.env.getConfiguration?.('html.customData') ?? []; - const newData: html.IHTMLDataProvider[] = []; + 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); + 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); + } } - } - return newData; - } + return newData; + } - async function worker(document: TextDocument, callback: (htmlDocument: html.HTMLDocument) => T) { + async function worker(document: TextDocument, callback: (htmlDocument: html.HTMLDocument) => T) { - if (document.languageId !== languageId) - return; + if (document.languageId !== languageId) + return; - const htmlDocument = getHtmlDocument(document); - if (!htmlDocument) - return; + const htmlDocument = getHtmlDocument(document); + if (!htmlDocument) + return; - await initCustomData(); + await initCustomData(); - return callback(htmlDocument); - } + return callback(htmlDocument); + } + }, }; } -export default create; - function isEOL(content: string, offset: number) { return isNewlineCharacter(content.charCodeAt(offset)); } diff --git a/packages/json/package.json b/packages/json/package.json index 2a2267b0..4601bed1 100644 --- a/packages/json/package.json +++ b/packages/json/package.json @@ -32,7 +32,7 @@ "vscode-languageserver-textdocument": "^1.0.11" }, "peerDependencies": { - "@volar/language-service": "~1.11.0" + "@volar/language-service": "2.0.0-alpha.0" }, "peerDependenciesMeta": { "@volar/language-service": { diff --git a/packages/json/src/index.ts b/packages/json/src/index.ts index 9deef49f..e1889e47 100644 --- a/packages/json/src/index.ts +++ b/packages/json/src/index.ts @@ -1,4 +1,4 @@ -import type { Service, Diagnostic } from '@volar/language-service'; +import type { ServicePlugin, Diagnostic, ServicePluginInstance } 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,166 +8,159 @@ export interface Provide { 'json/languageService': () => json.LanguageService; } -// https://github.com/microsoft/vscode/blob/09850876e652688fb142e2e19fd00fd38c0bc4ba/extensions/json-language-features/server/src/jsonServer.ts#L150 -const triggerCharacters = ['"', ':']; +export function create(settings?: json.LanguageSettings): ServicePlugin { + return { + // https://github.com/microsoft/vscode/blob/09850876e652688fb142e2e19fd00fd38c0bc4ba/extensions/json-language-features/server/src/jsonServer.ts#L150 + triggerCharacters: ['"', ':'], + 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, + clientCapabilities: context.env.clientCapabilities, + }); + + if (settings) { + jsonLs.configure(settings); + } -export function create(settings?: json.LanguageSettings): Service { - return (context): ReturnType> => { + return { - if (!context) { - return { triggerCharacters } as any; - } + provide: { + 'json/jsonDocument': getJsonDocument, + 'json/languageService': () => jsonLs, + }, - 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, - clientCapabilities: context.env.clientCapabilities, - }); - - if (settings) { - jsonLs.configure(settings); - } - - return { - - provide: { - 'json/jsonDocument': getJsonDocument, - 'json/languageService': () => jsonLs, - }, - - triggerCharacters, - - provideCompletionItems(document, position) { - return worker(document, async (jsonDocument) => { - return await jsonLs.doComplete(document, position, jsonDocument); - }); - }, - - resolveCompletionItem(item) { - return jsonLs.doResolve(item); - }, - - provideDefinition(document, position) { - return worker(document, async (jsonDocument) => { - return await jsonLs.findDefinition(document, position, jsonDocument); - }); - }, - - provideDiagnostics(document) { - return worker(document, async (jsonDocument) => { - - const documentLanguageSettings = undefined; // await getSettings(); // TODO - - return await jsonLs.doValidation( - document, - jsonDocument, - documentLanguageSettings, - undefined, // TODO - ) as Diagnostic[]; - }); - }, - - provideHover(document, position) { - return worker(document, async (jsonDocument) => { - return await jsonLs.doHover(document, position, jsonDocument); - }); - }, - - provideDocumentLinks(document) { - return worker(document, async (jsonDocument) => { - return await jsonLs.findLinks(document, jsonDocument); - }); - }, - - provideDocumentSymbols(document) { - return worker(document, async (jsonDocument) => { - return await jsonLs.findDocumentSymbols2(document, jsonDocument); - }); - }, - - provideDocumentColors(document) { - return worker(document, async (jsonDocument) => { - return await jsonLs.findDocumentColors(document, jsonDocument); - }); - }, - - provideColorPresentations(document, color, range) { - return worker(document, async (jsonDocument) => { - return await jsonLs.getColorPresentations(document, jsonDocument, color, range); - }); - }, - - provideFoldingRanges(document) { - return worker(document, async () => { - return await jsonLs.getFoldingRanges(document); - }); - }, - - provideSelectionRanges(document, positions) { - return worker(document, async (jsonDocument) => { - return await jsonLs.getSelectionRanges(document, positions, jsonDocument); - }); - }, - - provideDocumentFormattingEdits(document, range, options) { - return worker(document, async () => { - - const options_2 = await context.env.getConfiguration?.('json.format'); - if (!(options_2?.enable ?? true)) { - return; - } + provideCompletionItems(document, position) { + return worker(document, async (jsonDocument) => { + return await jsonLs.doComplete(document, position, jsonDocument); + }); + }, + + resolveCompletionItem(item) { + return jsonLs.doResolve(item); + }, + + provideDefinition(document, position) { + return worker(document, async (jsonDocument) => { + return await jsonLs.findDefinition(document, position, jsonDocument); + }); + }, + + provideDiagnostics(document) { + return worker(document, async (jsonDocument) => { + + const documentLanguageSettings = undefined; // await getSettings(); // TODO - return jsonLs.format(document, range, { - ...options_2, - ...options, + return await jsonLs.doValidation( + document, + jsonDocument, + documentLanguageSettings, + undefined, // TODO + ) as Diagnostic[]; }); - }); - }, - }; + }, - function worker(document: TextDocument, callback: (jsonDocument: json.JSONDocument) => T) { + provideHover(document, position) { + return worker(document, async (jsonDocument) => { + return await jsonLs.doHover(document, position, jsonDocument); + }); + }, - const jsonDocument = getJsonDocument(document); - if (!jsonDocument) - return; + provideDocumentLinks(document) { + return worker(document, async (jsonDocument) => { + return await jsonLs.findLinks(document, jsonDocument); + }); + }, - return callback(jsonDocument); - } + provideDocumentSymbols(document) { + return worker(document, async (jsonDocument) => { + return await jsonLs.findDocumentSymbols2(document, jsonDocument); + }); + }, - function getJsonDocument(textDocument: TextDocument) { + provideDocumentColors(document) { + return worker(document, async (jsonDocument) => { + return await jsonLs.findDocumentColors(document, jsonDocument); + }); + }, - if (textDocument.languageId !== 'json' && textDocument.languageId !== 'jsonc') - return; + provideColorPresentations(document, color, range) { + return worker(document, async (jsonDocument) => { + return await jsonLs.getColorPresentations(document, jsonDocument, color, range); + }); + }, - const cache = jsonDocuments.get(textDocument); - if (cache) { - const [cacheVersion, cacheDoc] = cache; - if (cacheVersion === textDocument.version) { - return cacheDoc; - } + provideFoldingRanges(document) { + return worker(document, async () => { + return await jsonLs.getFoldingRanges(document); + }); + }, + + provideSelectionRanges(document, positions) { + return worker(document, async (jsonDocument) => { + return await jsonLs.getSelectionRanges(document, positions, jsonDocument); + }); + }, + + provideDocumentFormattingEdits(document, range, options) { + return worker(document, async () => { + + const options_2 = await context.env.getConfiguration?.('json.format'); + if (!(options_2?.enable ?? true)) { + return; + } + + return jsonLs.format(document, range, { + ...options_2, + ...options, + }); + }); + }, + }; + + function worker(document: TextDocument, callback: (jsonDocument: json.JSONDocument) => T) { + + const jsonDocument = getJsonDocument(document); + if (!jsonDocument) + return; + + return callback(jsonDocument); } - const doc = jsonLs.parseJSONDocument(textDocument); - jsonDocuments.set(textDocument, [textDocument.version, doc]); + function getJsonDocument(textDocument: TextDocument) { + + if (textDocument.languageId !== 'json' && textDocument.languageId !== 'jsonc') + return; + + const cache = jsonDocuments.get(textDocument); + if (cache) { + const [cacheVersion, cacheDoc] = cache; + if (cacheVersion === textDocument.version) { + return cacheDoc; + } + } + + const doc = jsonLs.parseJSONDocument(textDocument); + jsonDocuments.set(textDocument, [textDocument.version, doc]); - return doc; - } + return doc; + } + }, }; } - -export default create; diff --git a/packages/markdown/package.json b/packages/markdown/package.json index f42f3185..b369b5bb 100644 --- a/packages/markdown/package.json +++ b/packages/markdown/package.json @@ -35,7 +35,7 @@ "vscode-languageserver-textdocument": "^1.0.11" }, "peerDependencies": { - "@volar/language-service": "~1.11.0" + "@volar/language-service": "2.0.0-alpha.0" }, "peerDependenciesMeta": { "@volar/language-service": { diff --git a/packages/markdown/src/index.ts b/packages/markdown/src/index.ts index 45be47ca..92981394 100644 --- a/packages/markdown/src/index.ts +++ b/packages/markdown/src/index.ts @@ -1,4 +1,4 @@ -import type { FileChangeType, FileType, Service } from '@volar/language-service'; +import { forEachEmbeddedFile, type FileChangeType, type FileType, type ServicePlugin, ServicePluginInstance } from '@volar/language-service'; import { Emitter } from 'vscode-jsonrpc'; import type { TextDocument } from 'vscode-languageserver-textdocument'; import type { ILogger, IMdLanguageService, IMdParser, IWorkspace } from 'vscode-markdown-languageservice'; @@ -22,296 +22,315 @@ function assert(condition: unknown, message: string): asserts condition { } } -export function create(): Service { - return (context) => { - if (!context) { - return {} as any; - } - - let lastProjectVersion = context.host.getProjectVersion(); - assert(context.env, 'context.env must be defined'); - const { fs, onDidChangeWatchedFiles } = context.env; - assert(fs, 'context.env.fs must be defined'); - assert( - onDidChangeWatchedFiles, - 'context.env.fs.onDidChangeWatchedFiles must be defined' - ); - - const logger: ILogger = { - level: LogLevel.Off, - - log(_logLevel, message) { - context.env.console?.log(message); - } - }; +export function create(): ServicePlugin { + return { + create(context): ServicePluginInstance { - const parser: IMdParser = { - slugifier: githubSlugifier, + let lastProjectVersion: string | undefined; - async tokenize(document) { - return md.parse(document.getText(), {}); - } - }; - - const onDidChangeMarkdownDocument = new Emitter(); - const onDidCreateMarkdownDocument = new Emitter(); - const onDidDeleteMarkdownDocument = new Emitter(); - - const fileWatcher = onDidChangeWatchedFiles((event) => { - for (const change of event.changes) { - switch (change.type) { - case 2 satisfies typeof FileChangeType.Changed: { - const document = context.getTextDocument(change.uri); - if (document) { - onDidChangeMarkdownDocument.fire(document); + const { fs, onDidChangeWatchedFiles } = context.env; + assert(fs, 'context.env.fs must be defined'); + assert( + onDidChangeWatchedFiles, + 'context.env.fs.onDidChangeWatchedFiles must be defined' + ); + + const logger: ILogger = { + level: LogLevel.Off, + + log(_logLevel, message) { + context.env.console?.log(message); + } + }; + + const parser: IMdParser = { + slugifier: githubSlugifier, + + async tokenize(document) { + return md.parse(document.getText(), {}); + } + }; + + const onDidChangeMarkdownDocument = new Emitter(); + const onDidCreateMarkdownDocument = new Emitter(); + const onDidDeleteMarkdownDocument = new Emitter(); + + const fileWatcher = onDidChangeWatchedFiles((event) => { + for (const change of event.changes) { + switch (change.type) { + case 2 satisfies typeof FileChangeType.Changed: { + const document = getTextDocument(change.uri, false); + if (document) { + onDidChangeMarkdownDocument.fire(document); + } + break; } - break; - } - case 1 satisfies typeof FileChangeType.Created: { - const document = context.getTextDocument(change.uri); - if (document) { - onDidCreateMarkdownDocument.fire(document); + case 1 satisfies typeof FileChangeType.Created: { + const document = getTextDocument(change.uri, false); + if (document) { + onDidCreateMarkdownDocument.fire(document); + } + break; } - break; - } - case 3 satisfies typeof FileChangeType.Deleted: { - onDidDeleteMarkdownDocument.fire(URI.parse(change.uri)); - break; + case 3 satisfies typeof FileChangeType.Deleted: { + onDidDeleteMarkdownDocument.fire(URI.parse(change.uri)); + break; + } } } - } - }); + }); - const workspace: IWorkspace = { - async getAllMarkdownDocuments() { - sync(); - return syncedVersions.values(); - }, + const workspace: IWorkspace = { + async getAllMarkdownDocuments() { + sync(); + return syncedVersions.values(); + }, - getContainingDocument() { - return undefined; - }, + getContainingDocument() { + return undefined; + }, - hasMarkdownDocument(resource) { - const document = context.getTextDocument(String(resource)); - return Boolean(document && isMarkdown(document)); - }, + hasMarkdownDocument(resource) { + const document = getTextDocument(resource.toString(), true); + return Boolean(document && isMarkdown(document)); + }, - onDidChangeMarkdownDocument: onDidChangeMarkdownDocument.event, + onDidChangeMarkdownDocument: onDidChangeMarkdownDocument.event, - onDidCreateMarkdownDocument: onDidCreateMarkdownDocument.event, + onDidCreateMarkdownDocument: onDidCreateMarkdownDocument.event, - onDidDeleteMarkdownDocument: onDidDeleteMarkdownDocument.event, + onDidDeleteMarkdownDocument: onDidDeleteMarkdownDocument.event, - async openMarkdownDocument(resource) { - return context.getTextDocument(String(resource)); - }, + async openMarkdownDocument(resource) { + return getTextDocument(resource.toString(), true); + }, - async readDirectory(resource) { - const directory = await fs.readDirectory(String(resource)); - return directory.map(([fileName, fileType]) => [ - fileName, - { isDirectory: fileType === 2 satisfies FileType.Directory } - ]); - }, + async readDirectory(resource) { + const directory = await fs.readDirectory(resource.toString()); + return directory.map(([fileName, fileType]) => [ + fileName, + { isDirectory: fileType === 2 satisfies FileType.Directory } + ]); + }, - async stat(resource) { - const stat = await fs.stat(String(resource)); - if (stat) { - return { isDirectory: stat.type === 2 satisfies FileType.Directory }; - } - }, + async stat(resource) { + const stat = await fs.stat(resource.toString()); + if (stat) { + return { isDirectory: stat.type === 2 satisfies FileType.Directory }; + } + }, - workspaceFolders: [] - }; + workspaceFolders: [] + }; - const ls = createLanguageService({ - logger, - parser, - workspace - }); + const ls = createLanguageService({ + logger, + parser, + workspace + }); - const syncedVersions = new Map(); + const syncedVersions = new Map(); - const sync = () => { - const newProjectVersion = context.host.getProjectVersion(); - const shouldUpdate = newProjectVersion !== lastProjectVersion; - if (!shouldUpdate) { - return; - } + const sync = () => { + + const languageServiceHost = context.language.typescript?.languageServiceHost; + if (!languageServiceHost) + return; - lastProjectVersion = newProjectVersion; - const oldVersions = new Set(syncedVersions.keys()); - const newVersions = new Map(); - - for (const { root } of context.virtualFiles.allSources()) { - const embeddeds = [root]; - root.embeddedFiles.forEach(function walk(embedded) { - embeddeds.push(embedded); - embedded.embeddedFiles.forEach(walk); - }); - for (const embedded of embeddeds) { - const document = context.getTextDocument(embedded.fileName); - if (document && isMarkdown(document)) { - newVersions.set(String(document.uri), document); + const newProjectVersion = languageServiceHost.getProjectVersion?.(); + const shouldUpdate = newProjectVersion === undefined || newProjectVersion !== lastProjectVersion; + if (!shouldUpdate) { + return; + } + lastProjectVersion = newProjectVersion; + + const oldVersions = new Set(syncedVersions.keys()); + const newVersions = new Map(); + + for (const fileName of languageServiceHost.getScriptFileNames()) { + const [_, sourceFile] = context.language.files.getVirtualFile(fileName); + if (sourceFile?.virtualFile) { + for (const virtualFile of Array.from(forEachEmbeddedFile(sourceFile.virtualFile[0]))) { + if (virtualFile.languageId === 'markdown') { + const document = context.documents.get(virtualFile.id, virtualFile.languageId, virtualFile.snapshot); + newVersions.set(document.uri, document); + } + } + } + else if (sourceFile) { + const document = context.documents.get(fileName, sourceFile.languageId, sourceFile.snapshot); + if (document && isMarkdown(document)) { + newVersions.set(document.uri, document); + } } } - } - for (const [uri, document] of newVersions) { - const old = syncedVersions.get(uri); - syncedVersions.set(uri, document); - if (old) { - onDidChangeMarkdownDocument.fire(document); - } else { - onDidCreateMarkdownDocument.fire(document); + for (const [uri, document] of Array.from(newVersions)) { + const old = syncedVersions.get(uri); + syncedVersions.set(uri, document); + if (old) { + onDidChangeMarkdownDocument.fire(document); + } else { + onDidCreateMarkdownDocument.fire(document); + } } - } - for (const uri of oldVersions) { - if (!newVersions.has(uri)) { - syncedVersions.delete(uri); - onDidDeleteMarkdownDocument.fire(URI.parse(uri)); - } - } - }; - const prepare = (document: TextDocument) => { - if (!isMarkdown(document)) { - return false; - } - sync(); - return true; - }; - - return { - dispose() { - ls.dispose(); - fileWatcher.dispose(); - onDidDeleteMarkdownDocument.dispose(); - onDidCreateMarkdownDocument.dispose(); - onDidChangeMarkdownDocument.dispose(); - }, - - provide: { - 'markdown/languageService': () => ls - }, - - provideCodeActions(document, range, context, token) { - if (prepare(document)) { - return ls.getCodeActions(document, range, context, token); - } - }, - - async provideCompletionItems(document, position, _context, token) { - if (prepare(document)) { - const items = await ls.getCompletionItems( - document, - position, - {}, - token - ); - return { - isIncomplete: false, - items - }; + for (const uri of Array.from(oldVersions)) { + if (!newVersions.has(uri)) { + syncedVersions.delete(uri); + onDidDeleteMarkdownDocument.fire(URI.parse(uri)); + } } - }, - - provideDiagnostics(document, token) { - if (prepare(document)) { - return ls.computeDiagnostics( - document, - { - ignoreLinks: [], - validateDuplicateLinkDefinitions: DiagnosticLevel.warning, - validateFileLinks: DiagnosticLevel.warning, - validateFragmentLinks: DiagnosticLevel.warning, - validateMarkdownFileLinkFragments: DiagnosticLevel.warning, - validateReferences: DiagnosticLevel.warning, - validateUnusedLinkDefinitions: DiagnosticLevel.warning - }, - token - ); + }; + const prepare = (document: TextDocument) => { + if (!isMarkdown(document)) { + return false; } - }, + sync(); + return true; + }; + + return { + dispose() { + ls.dispose(); + fileWatcher.dispose(); + onDidDeleteMarkdownDocument.dispose(); + onDidCreateMarkdownDocument.dispose(); + onDidChangeMarkdownDocument.dispose(); + }, + + provide: { + 'markdown/languageService': () => ls + }, + + provideCodeActions(document, range, context, token) { + if (prepare(document)) { + return ls.getCodeActions(document, range, context, token); + } + }, + + async provideCompletionItems(document, position, _context, token) { + if (prepare(document)) { + const items = await ls.getCompletionItems( + document, + position, + {}, + token + ); + return { + isIncomplete: false, + items + }; + } + }, + + provideDiagnostics(document, token) { + if (prepare(document)) { + return ls.computeDiagnostics( + document, + { + ignoreLinks: [], + validateDuplicateLinkDefinitions: DiagnosticLevel.warning, + validateFileLinks: DiagnosticLevel.warning, + validateFragmentLinks: DiagnosticLevel.warning, + validateMarkdownFileLinkFragments: DiagnosticLevel.warning, + validateReferences: DiagnosticLevel.warning, + validateUnusedLinkDefinitions: DiagnosticLevel.warning + }, + token + ); + } + }, - provideDocumentHighlights(document, position, token) { - if (prepare(document)) { - return ls.getDocumentHighlights(document, position, token); - } - }, + provideDocumentHighlights(document, position, token) { + if (prepare(document)) { + return ls.getDocumentHighlights(document, position, token); + } + }, - provideDocumentLinks(document, token) { - if (prepare(document)) { - return ls.getDocumentLinks(document, token); - } - }, - - provideDocumentSymbols(document, token) { - if (prepare(document)) { - return ls.getDocumentSymbols( - document, - { includeLinkDefinitions: true }, - token - ); - } - }, + provideDocumentLinks(document, token) { + if (prepare(document)) { + return ls.getDocumentLinks(document, token); + } + }, + + provideDocumentSymbols(document, token) { + if (prepare(document)) { + return ls.getDocumentSymbols( + document, + { includeLinkDefinitions: true }, + token + ); + } + }, - provideFileReferences(document, token) { - if (prepare(document)) { - return ls.getFileReferences(URI.parse(document.uri), token); - } - }, + provideFileReferences(document, token) { + if (prepare(document)) { + return ls.getFileReferences(URI.parse(document.uri), token); + } + }, - provideFoldingRanges(document, token) { - if (prepare(document)) { - return ls.getFoldingRanges(document, token); - } - }, - - provideReferences(document, position, token) { - if (prepare(document)) { - return ls.getReferences( - document, - position, - { includeDeclaration: true }, - token - ); - } - }, + provideFoldingRanges(document, token) { + if (prepare(document)) { + return ls.getFoldingRanges(document, token); + } + }, + + provideReferences(document, position, token) { + if (prepare(document)) { + return ls.getReferences( + document, + position, + { includeDeclaration: true }, + token + ); + } + }, - provideRenameEdits(document, position, newName, token) { - if (prepare(document)) { - return ls.getRenameEdit(document, position, newName, token); - } - }, + provideRenameEdits(document, position, newName, token) { + if (prepare(document)) { + return ls.getRenameEdit(document, position, newName, token); + } + }, - provideRenameRange(document, position, token) { - if (prepare(document)) { - return ls.prepareRename(document, position, token); - } - }, + provideRenameRange(document, position, token) { + if (prepare(document)) { + return ls.prepareRename(document, position, token); + } + }, - provideSelectionRanges(document, positions, token) { - if (prepare(document)) { - return ls.getSelectionRanges(document, positions, token); - } - }, + provideSelectionRanges(document, positions, token) { + if (prepare(document)) { + return ls.getSelectionRanges(document, positions, token); + } + }, - provideWorkspaceSymbols(query, token) { - sync(); - return ls.getWorkspaceSymbols(query, token); - }, + provideWorkspaceSymbols(query, token) { + sync(); + return ls.getWorkspaceSymbols(query, token); + }, + + async resolveDocumentLink(link, token) { + const result = await ls.resolveDocumentLink(link, token); - async resolveDocumentLink(link, token) { - const result = await ls.resolveDocumentLink(link, token); + return result || link; + } + }; - return result || link; + function getTextDocument(uri: string, includeVirtualFile: boolean) { + if (includeVirtualFile) { + const virtualFile = context.language.files.getVirtualFile(uri)[0]; + if (virtualFile) { + return context.documents.get(uri, virtualFile.languageId, virtualFile.snapshot); + } + } + const sourceFile = context.language.files.getSourceFile(uri); + if (sourceFile && !sourceFile.virtualFile) { + return context.documents.get(uri, sourceFile.languageId, sourceFile.snapshot); + } } - }; + }, }; } - -export default create; diff --git a/packages/prettier/package.json b/packages/prettier/package.json index a61ea7cc..ebdb0b5a 100644 --- a/packages/prettier/package.json +++ b/packages/prettier/package.json @@ -30,7 +30,7 @@ "prettier": "^3.0.3" }, "peerDependencies": { - "@volar/language-service": "~1.11.0", + "@volar/language-service": "2.0.0-alpha.0", "prettier": "^2.2 || ^3.0" }, "peerDependenciesMeta": { diff --git a/packages/prettier/src/index.ts b/packages/prettier/src/index.ts index 1c8fc0bf..c83fa3bc 100644 --- a/packages/prettier/src/index.ts +++ b/packages/prettier/src/index.ts @@ -1,5 +1,5 @@ -import type { Service } from '@volar/language-service'; -import { type Options, type ResolveConfigOptions } from 'prettier'; +import type { ServicePluginInstance, ServicePlugin } from '@volar/language-service'; +import type { Options, ResolveConfigOptions } from 'prettier'; export function create( options: { @@ -46,91 +46,87 @@ export function create( getPrettierConfig = async (filePath: string, prettier: typeof import('prettier'), config?: ResolveConfigOptions) => { return await prettier.resolveConfig(filePath, config) ?? {}; }, -): Service { - return (context): ReturnType => { - - if (!context) { - return {}; - } - - let prettier: typeof import('prettier'); - try { - prettier = options.prettier ?? require('prettier'); - } catch (e) { - throw new Error("Could not load Prettier: " + e); - } - const languages = options.languages ?? ['html', 'css', 'scss', 'typescript', 'javascript']; - - return { - async provideDocumentFormattingEdits(document, _, formatOptions) { - if (!languages.includes(document.languageId)) { - return; - } - - const filePath = context.env.uriToFileName(document.uri); - const fileInfo = await prettier.getFileInfo(filePath, { ignorePath: '.prettierignore', resolveConfig: false }); - - if (fileInfo.ignored) { - return; - } - - const filePrettierOptions = await getPrettierConfig( - filePath, - prettier, - options.resolveConfigOptions - ); - - const editorPrettierOptions = await context.env.getConfiguration?.('prettier', document.uri); - const ideFormattingOptions = - formatOptions !== undefined && options.useIdeOptionsFallback // We need to check for options existing here because some editors might not have it - ? { - tabWidth: formatOptions.tabSize, - useTabs: !formatOptions.insertSpaces, - } - : {}; - - // Return a config with the following cascade: - // - Prettier config file should always win if it exists, if it doesn't: - // - Prettier config from the VS Code extension is used, if it doesn't exist: - // - Use the editor's basic configuration settings - const prettierOptions = returnObjectIfHasKeys(filePrettierOptions) || returnObjectIfHasKeys(editorPrettierOptions) || ideFormattingOptions; - - const currentPrettierConfig: Options = { - ...(options.additionalOptions - ? await options.additionalOptions(prettierOptions) - : prettierOptions), - filepath: filePath, - }; - - if (!options.ignoreIdeOptions) { - currentPrettierConfig.useTabs = !formatOptions.insertSpaces; - currentPrettierConfig.tabWidth = formatOptions.tabSize; - } - - const fullText = document.getText(); - let oldText = fullText; - - const isHTML = document.languageId === "html"; - if (isHTML && options.html?.breakContentsFromTags) { - oldText = oldText - .replace(/(<[a-z][^>]*>)([^ \n])/gi, "$1 $2") - .replace(/([^ \n])(<\/[a-z][a-z0-9\t\n\r -]*>)/gi, "$1 $2"); - } - - return [{ - newText: await prettier.format(oldText, currentPrettierConfig), - range: { - start: document.positionAt(0), - end: document.positionAt(fullText.length), - }, - }]; - }, - }; +): ServicePlugin { + return { + create(context): ServicePluginInstance { + + let prettier: typeof import('prettier'); + try { + prettier = options.prettier ?? require('prettier'); + } catch (e) { + throw new Error("Could not load Prettier: " + e); + } + const languages = options.languages ?? ['html', 'css', 'scss', 'typescript', 'javascript']; + + return { + async provideDocumentFormattingEdits(document, _, formatOptions) { + if (!languages.includes(document.languageId)) { + return; + } + + const filePath = context.env.uriToFileName(document.uri); + const fileInfo = await prettier.getFileInfo(filePath, { ignorePath: '.prettierignore', resolveConfig: false }); + + if (fileInfo.ignored) { + return; + } + + const filePrettierOptions = await getPrettierConfig( + filePath, + prettier, + options.resolveConfigOptions + ); + + const editorPrettierOptions = await context.env.getConfiguration?.('prettier', document.uri); + const ideFormattingOptions = + formatOptions !== undefined && options.useIdeOptionsFallback // We need to check for options existing here because some editors might not have it + ? { + tabWidth: formatOptions.tabSize, + useTabs: !formatOptions.insertSpaces, + } + : {}; + + // Return a config with the following cascade: + // - Prettier config file should always win if it exists, if it doesn't: + // - Prettier config from the VS Code extension is used, if it doesn't exist: + // - Use the editor's basic configuration settings + const prettierOptions = returnObjectIfHasKeys(filePrettierOptions) || returnObjectIfHasKeys(editorPrettierOptions) || ideFormattingOptions; + + const currentPrettierConfig: Options = { + ...(options.additionalOptions + ? await options.additionalOptions(prettierOptions) + : prettierOptions), + filepath: filePath, + }; + + if (!options.ignoreIdeOptions) { + currentPrettierConfig.useTabs = !formatOptions.insertSpaces; + currentPrettierConfig.tabWidth = formatOptions.tabSize; + } + + const fullText = document.getText(); + let oldText = fullText; + + const isHTML = document.languageId === "html"; + if (isHTML && options.html?.breakContentsFromTags) { + oldText = oldText + .replace(/(<[a-z][^>]*>)([^ \n])/gi, "$1 $2") + .replace(/([^ \n])(<\/[a-z][a-z0-9\t\n\r -]*>)/gi, "$1 $2"); + } + + return [{ + newText: await prettier.format(oldText, currentPrettierConfig), + range: { + start: document.positionAt(0), + end: document.positionAt(fullText.length), + }, + }]; + }, + }; + }, }; } -export default create; - function returnObjectIfHasKeys(obj: T | undefined): T | undefined { if (Object.keys(obj || {}).length > 0) { return obj; diff --git a/packages/pretty-ts-errors/package.json b/packages/pretty-ts-errors/package.json index bc758ee0..35755c87 100644 --- a/packages/pretty-ts-errors/package.json +++ b/packages/pretty-ts-errors/package.json @@ -28,7 +28,7 @@ "pretty-ts-errors-lsp": "^0.0.3" }, "peerDependencies": { - "@volar/language-service": "~1.11.0" + "@volar/language-service": "2.0.0-alpha.0" }, "peerDependenciesMeta": { "@volar/language-service": { diff --git a/packages/pretty-ts-errors/src/index.ts b/packages/pretty-ts-errors/src/index.ts index 80d30332..d4cfacc5 100644 --- a/packages/pretty-ts-errors/src/index.ts +++ b/packages/pretty-ts-errors/src/index.ts @@ -1,20 +1,17 @@ -import type { Service } from '@volar/language-service'; +import type { ServicePluginInstance, ServicePlugin } from '@volar/language-service'; import { formatDiagnostic } from 'pretty-ts-errors-lsp'; -export function create(format: (text: string) => string): Service { - return (contextOrNull): ReturnType => { - - if (!contextOrNull) return {}; - - return { - provideDiagnosticMarkupContent(diagnostic) { - return { - kind: 'markdown', - value: formatDiagnostic(diagnostic, format), - }; - }, - }; +export function create(format: (text: string) => string): ServicePlugin { + return { + create(): ServicePluginInstance { + return { + provideDiagnosticMarkupContent(diagnostic) { + return { + kind: 'markdown', + value: formatDiagnostic(diagnostic, format), + }; + }, + }; + }, }; } - -export default create; diff --git a/packages/prettyhtml/package.json b/packages/prettyhtml/package.json index 86f13cc3..055dd59a 100644 --- a/packages/prettyhtml/package.json +++ b/packages/prettyhtml/package.json @@ -28,7 +28,7 @@ "@starptech/prettyhtml": "^0.10.0" }, "peerDependencies": { - "@volar/language-service": "~1.11.0" + "@volar/language-service": "2.0.0-alpha.0" }, "peerDependenciesMeta": { "@volar/language-service": { diff --git a/packages/prettyhtml/src/index.ts b/packages/prettyhtml/src/index.ts index 84ae9896..cc19770d 100644 --- a/packages/prettyhtml/src/index.ts +++ b/packages/prettyhtml/src/index.ts @@ -1,44 +1,44 @@ -import type { Service } from '@volar/language-service'; +import type { ServicePluginInstance, ServicePlugin } from '@volar/language-service'; import * as prettyhtml from '@starptech/prettyhtml'; -export function create(configs: NonNullable[1]>): Service { - return (): ReturnType => { - return { - provideDocumentFormattingEdits(document, range, options) { +export function create(configs: NonNullable[1]>): ServicePlugin { + return { + create(): ServicePluginInstance { + return { + provideDocumentFormattingEdits(document, range, options) { - if (document.languageId !== 'html') - return; + if (document.languageId !== 'html') + return; - const oldRangeText = document.getText(range); - const newRangeText = prettyhtml(oldRangeText, { - ...configs, - tabWidth: options.tabSize, - useTabs: !options.insertSpaces, - }).contents; + const oldRangeText = document.getText(range); + const newRangeText = prettyhtml(oldRangeText, { + ...configs, + tabWidth: options.tabSize, + useTabs: !options.insertSpaces, + }).contents; - if (newRangeText === oldRangeText) - return []; + if (newRangeText === oldRangeText) + return []; - const newText = document.getText({ - start: document.positionAt(0), - end: range.start, - }) - + newRangeText - + document.getText({ - start: range.end, - end: document.positionAt(document.getText().length), - }); - - return [{ - newText, - range: { + const newText = document.getText({ start: document.positionAt(0), - end: document.positionAt(document.getText().length), - }, - }]; - }, - }; + end: range.start, + }) + + newRangeText + + document.getText({ + start: range.end, + end: document.positionAt(document.getText().length), + }); + + return [{ + newText, + range: { + start: document.positionAt(0), + end: document.positionAt(document.getText().length), + }, + }]; + }, + }; + }, }; } - -export default create; diff --git a/packages/pug-beautify/package.json b/packages/pug-beautify/package.json index bfc05e9e..19c8d1f4 100644 --- a/packages/pug-beautify/package.json +++ b/packages/pug-beautify/package.json @@ -28,7 +28,7 @@ "@johnsoncodehk/pug-beautify": "^0.2.2" }, "peerDependencies": { - "@volar/language-service": "~1.11.0" + "@volar/language-service": "2.0.0-alpha.0" }, "peerDependenciesMeta": { "@volar/language-service": { diff --git a/packages/pug-beautify/src/index.ts b/packages/pug-beautify/src/index.ts index f84127eb..535da93d 100644 --- a/packages/pug-beautify/src/index.ts +++ b/packages/pug-beautify/src/index.ts @@ -1,37 +1,37 @@ -import type { Service } from '@volar/language-service'; - -export function create(): Service { - return (): ReturnType => { - return { - provideDocumentFormattingEdits(document, range, options) { - - if (document.languageId !== 'jade') - return; - - const pugCode = document.getText(range); - - // fix https://github.com/johnsoncodehk/volar/issues/304 - if (pugCode.trim() === '') - return; - - const pugBeautify = require('@johnsoncodehk/pug-beautify'); - const prefixesLength = pugCode.length - pugCode.trimStart().length; - const suffixesLength = pugCode.length - pugCode.trimEnd().length; - const prefixes = pugCode.slice(0, prefixesLength); - const suffixes = pugCode.slice(pugCode.length - suffixesLength); - - let newText: string = pugBeautify(pugCode, { - tab_size: options.tabSize, - fill_tab: !options.insertSpaces, - }); - - return [{ - range, - newText: prefixes + newText.trim() + suffixes, - }]; - }, - }; +import type { ServicePluginInstance, ServicePlugin } from '@volar/language-service'; + +export function create(): ServicePlugin { + return { + create(): ServicePluginInstance { + return { + provideDocumentFormattingEdits(document, range, options) { + + if (document.languageId !== 'jade') + return; + + const pugCode = document.getText(range); + + // fix https://github.com/johnsoncodehk/volar/issues/304 + if (pugCode.trim() === '') + return; + + const pugBeautify = require('@johnsoncodehk/pug-beautify'); + const prefixesLength = pugCode.length - pugCode.trimStart().length; + const suffixesLength = pugCode.length - pugCode.trimEnd().length; + const prefixes = pugCode.slice(0, prefixesLength); + const suffixes = pugCode.slice(pugCode.length - suffixesLength); + + let newText: string = pugBeautify(pugCode, { + tab_size: options.tabSize, + fill_tab: !options.insertSpaces, + }); + + return [{ + range, + newText: prefixes + newText.trim() + suffixes, + }]; + }, + }; + }, }; } - -export default create; diff --git a/packages/pug/package.json b/packages/pug/package.json index fed2aace..16187252 100644 --- a/packages/pug/package.json +++ b/packages/pug/package.json @@ -25,9 +25,7 @@ "url": "https://github.com/johnsoncodehk" }, "dependencies": { - "@volar/language-service": "~1.11.0", - "@volar/source-map": "~1.11.0", - "muggle-string": "^0.3.1", + "@volar/language-service": "2.0.0-alpha.0", "pug-lexer": "^5.0.1", "pug-parser": "^6.0.0", "volar-service-html": "0.0.17", diff --git a/packages/pug/src/baseParse.ts b/packages/pug/src/baseParse.ts index 125132b2..9cfd0e26 100644 --- a/packages/pug/src/baseParse.ts +++ b/packages/pug/src/baseParse.ts @@ -1,5 +1,4 @@ -import { Segment, toString } from 'muggle-string'; -import { buildMappings } from '@volar/source-map'; +import { Segment, buildMappings, toString } from '@volar/language-service'; import * as pugLex from 'pug-lexer'; import { TextDocument } from 'vscode-languageserver-textdocument'; diff --git a/packages/pug/src/empty.ts b/packages/pug/src/empty.ts index 33bf9a14..e5dec19f 100644 --- a/packages/pug/src/empty.ts +++ b/packages/pug/src/empty.ts @@ -1,9 +1,11 @@ -import type { Service } from '@volar/language-service'; +import type { ServicePlugin } from '@volar/language-service'; console.warn('volar-service-pug: this module is not yet supported for web.'); -export function create(): Service { - return () => ({}); +export function create(): ServicePlugin { + return { + create() { + return {}; + }, + }; } - -export default create; diff --git a/packages/pug/src/index.ts b/packages/pug/src/index.ts index a75e90d9..c8137578 100644 --- a/packages/pug/src/index.ts +++ b/packages/pug/src/index.ts @@ -1,8 +1,7 @@ +import { transformDocumentSymbol, type Diagnostic, type DiagnosticSeverity, type ServicePlugin, ServicePluginInstance } from '@volar/language-service'; import { create as createHtmlService } from 'volar-service-html'; -import type { Service, Diagnostic, DiagnosticSeverity } from '@volar/language-service'; -import { transformer } from '@volar/language-service'; import type * as html from 'vscode-html-languageservice'; -import { TextDocument } from 'vscode-languageserver-textdocument'; +import type { TextDocument } from 'vscode-languageserver-textdocument'; import * as pug from './languageService'; export interface Provide { @@ -11,150 +10,149 @@ export interface Provide { 'pug/updateCustomData': (extraData: html.IHTMLDataProvider[]) => void; } -export function create(): Service { - return (context, modules): ReturnType> => { - - const htmlService = createHtmlService()(context, modules); - - if (!context) { - return htmlService as any; - } - - const pugDocuments = new WeakMap(); - const pugLs = pug.getLanguageService(htmlService.provide['html/languageService']()); - - return { - ...htmlService, - - provide: { - 'pug/pugDocument': getPugDocument, - 'pug/languageService': () => pugLs, - 'pug/updateCustomData': htmlService.provide['html/updateCustomData'], - }, - - provideCompletionItems(document, position, _) { - return worker(document, (pugDocument) => { - return pugLs.doComplete(pugDocument, position, htmlService.provide['html/documentContext'](), /** TODO: CompletionConfiguration */); - }); - }, - - provideDiagnostics(document) { - return worker(document, (pugDocument): Diagnostic[] => { - - if (pugDocument.error) { - - return [{ - source: 'pug', - severity: 1 satisfies typeof DiagnosticSeverity.Error, - code: pugDocument.error.code, - message: pugDocument.error.msg, - range: { - start: { line: pugDocument.error.line, character: pugDocument.error.column }, - end: { line: pugDocument.error.line, character: pugDocument.error.column }, - }, - }]; - } +export function create(): ServicePlugin { + const _htmlService = createHtmlService(); + return { + ..._htmlService, + create(context): ServicePluginInstance { + + const htmlService = _htmlService.create(context); + const pugDocuments = new WeakMap(); + const pugLs = pug.getLanguageService(htmlService.provide['html/languageService']()); + + return { + ...htmlService, + + provide: { + 'pug/pugDocument': getPugDocument, + 'pug/languageService': () => pugLs, + 'pug/updateCustomData': htmlService.provide['html/updateCustomData'], + }, + + provideCompletionItems(document, position, _) { + return worker(document, (pugDocument) => { + return pugLs.doComplete(pugDocument, position, htmlService.provide['html/documentContext'](), /** TODO: CompletionConfiguration */); + }); + }, + + provideDiagnostics(document) { + return worker(document, (pugDocument): Diagnostic[] => { + + if (pugDocument.error) { + + return [{ + source: 'pug', + severity: 1 satisfies typeof DiagnosticSeverity.Error, + code: pugDocument.error.code, + message: pugDocument.error.msg, + range: { + start: { line: pugDocument.error.line, character: pugDocument.error.column }, + end: { line: pugDocument.error.line, character: pugDocument.error.column }, + }, + }]; + } - return []; - }); - }, + return []; + }); + }, - provideHover(document, position) { - return worker(document, async (pugDocument) => { + provideHover(document, position) { + return worker(document, async (pugDocument) => { - const hoverSettings = await context.env.getConfiguration?.('html.hover'); + const hoverSettings = await context.env.getConfiguration?.('html.hover'); - return pugLs.doHover(pugDocument, position, hoverSettings); - }); - }, + return pugLs.doHover(pugDocument, position, hoverSettings); + }); + }, - provideDocumentHighlights(document, position) { - return worker(document, (pugDocument) => { - return pugLs.findDocumentHighlights(pugDocument, position); - }); - }, + provideDocumentHighlights(document, position) { + return worker(document, (pugDocument) => { + return pugLs.findDocumentHighlights(pugDocument, position); + }); + }, - provideDocumentLinks(document) { - return worker(document, (pugDocument) => { - return pugLs.findDocumentLinks(pugDocument, htmlService.provide['html/documentContext']()); - }); - }, + provideDocumentLinks(document) { + return worker(document, (pugDocument) => { + return pugLs.findDocumentLinks(pugDocument, htmlService.provide['html/documentContext']()); + }); + }, - provideDocumentSymbols(document, token) { - return worker(document, async (pugDoc) => { + provideDocumentSymbols(document, token) { + return worker(document, async (pugDoc) => { - const htmlResult = await htmlService.provideDocumentSymbols?.(pugDoc.map.virtualFileDocument, token) ?? []; - const pugResult = htmlResult.map(htmlSymbol => transformer.asDocumentSymbol( - htmlSymbol, - range => pugDoc.map.toSourceRange(range), - )).filter((symbol): symbol is NonNullable => symbol !== undefined); + const htmlResult = await htmlService.provideDocumentSymbols?.(pugDoc.map.virtualFileDocument, token) ?? []; + const pugResult = htmlResult.map(htmlSymbol => transformDocumentSymbol( + htmlSymbol, + range => pugDoc.map.getSourceRange(range), + )).filter((symbol): symbol is NonNullable => symbol !== undefined); - return pugResult; - }); - }, + return pugResult; + }); + }, - provideFoldingRanges(document) { - return worker(document, (pugDocument) => { - return pugLs.getFoldingRanges(pugDocument); - }); - }, + provideFoldingRanges(document) { + return worker(document, (pugDocument) => { + return pugLs.getFoldingRanges(pugDocument); + }); + }, - provideSelectionRanges(document, positions) { - return worker(document, (pugDocument) => { - return pugLs.getSelectionRanges(pugDocument, positions); - }); - }, + provideSelectionRanges(document, positions) { + return worker(document, (pugDocument) => { + return pugLs.getSelectionRanges(pugDocument, positions); + }); + }, - async provideAutoInsertionEdit(document, position, insertContext) { - return worker(document, async (pugDocument) => { + async provideAutoInsertionEdit(document, position, lastChange) { + return worker(document, async (pugDocument) => { - const lastCharacter = insertContext.lastChange.text[insertContext.lastChange.text.length - 1]; + const lastCharacter = lastChange.text[lastChange.text.length - 1]; + const rangeLengthIsZero = lastChange.range.start.line && lastChange.range.end.line + && lastChange.range.start.character && lastChange.range.end.character; - if (insertContext.lastChange.rangeLength === 0 && lastCharacter === '=') { + if (rangeLengthIsZero && lastCharacter === '=') { - const enabled = (await context.env.getConfiguration?.('html.autoCreateQuotes')) ?? true; + const enabled = (await context.env.getConfiguration?.('html.autoCreateQuotes')) ?? true; - if (enabled) { + if (enabled) { - const text = pugLs.doQuoteComplete(pugDocument, position, await context.env.getConfiguration?.('html.completion')); + const text = pugLs.doQuoteComplete(pugDocument, position, await context.env.getConfiguration?.('html.completion')); - if (text) { - return text; + if (text) { + return text; + } } } - } - }); - }, - }; + }); + }, + }; - function worker(document: TextDocument, callback: (pugDocument: pug.PugDocument) => T) { + function worker(document: TextDocument, callback: (pugDocument: pug.PugDocument) => T) { - const pugDocument = getPugDocument(document); - if (!pugDocument) - return; + const pugDocument = getPugDocument(document); + if (!pugDocument) + return; - return callback(pugDocument); - } + return callback(pugDocument); + } - function getPugDocument(document: TextDocument) { + function getPugDocument(document: TextDocument) { - if (document.languageId !== 'jade') - return; + if (document.languageId !== 'jade') + return; - const cache = pugDocuments.get(document); - if (cache) { - const [cacheVersion, cacheDoc] = cache; - if (cacheVersion === document.version) { - return cacheDoc; + const cache = pugDocuments.get(document); + if (cache) { + const [cacheVersion, cacheDoc] = cache; + if (cacheVersion === document.version) { + return cacheDoc; + } } - } - const doc = pugLs.parsePugDocument(document.getText()); - pugDocuments.set(document, [document.version, doc]); + const doc = pugLs.parsePugDocument(document.getText()); + pugDocuments.set(document, [document.version, doc]); - return doc; - } + return doc; + } + }, }; } - -export default create; diff --git a/packages/pug/src/pugDocument.ts b/packages/pug/src/pugDocument.ts index 0e5a2f7f..8a94030c 100644 --- a/packages/pug/src/pugDocument.ts +++ b/packages/pug/src/pugDocument.ts @@ -1,8 +1,7 @@ -import { SourceMapWithDocuments } from '@volar/language-service'; -import { TextDocument } from 'vscode-languageserver-textdocument'; +import { SourceMap, SourceMapWithDocuments } from '@volar/language-service'; import type * as html from 'vscode-html-languageservice'; +import { TextDocument } from 'vscode-languageserver-textdocument'; import { baseParse } from './baseParse'; -import { SourceMap } from '@volar/source-map'; export interface PugDocument extends ReturnType> { } diff --git a/packages/pug/src/services/completion.ts b/packages/pug/src/services/completion.ts index 235e4363..430e4e6d 100644 --- a/packages/pug/src/services/completion.ts +++ b/packages/pug/src/services/completion.ts @@ -1,7 +1,7 @@ -import { transformer } from '@volar/language-service'; import type * as html from 'vscode-html-languageservice'; import { TextDocument } from 'vscode-html-languageservice'; import type { PugDocument } from '../pugDocument'; +import { transformCompletionList } from '@volar/language-service'; export function register(htmlLs: html.LanguageService) { @@ -27,7 +27,7 @@ export function register(htmlLs: html.LanguageService) { return htmlComplete; } - const htmlPos = pugDoc.map.toGeneratedPosition(pos); + const htmlPos = pugDoc.map.getGeneratedPosition(pos); if (!htmlPos) return; @@ -44,6 +44,6 @@ export function register(htmlLs: html.LanguageService) { options, ); - return transformer.asCompletionList(htmlComplete, htmlRange => pugDoc.map.toSourceRange(htmlRange), pugDoc.map.virtualFileDocument); + return transformCompletionList(htmlComplete, htmlRange => pugDoc.map.getSourceRange(htmlRange), pugDoc.map.virtualFileDocument); }; } diff --git a/packages/pug/src/services/documentHighlight.ts b/packages/pug/src/services/documentHighlight.ts index b27cf1a7..6bb509e4 100644 --- a/packages/pug/src/services/documentHighlight.ts +++ b/packages/pug/src/services/documentHighlight.ts @@ -1,11 +1,11 @@ -import { transformer } from '@volar/language-service'; import type * as html from 'vscode-html-languageservice'; import type { PugDocument } from '../pugDocument'; +import { transformLocations } from '@volar/language-service'; export function register(htmlLs: html.LanguageService) { return (pugDoc: PugDocument, pos: html.Position) => { - const htmlPos = pugDoc.map.toGeneratedPosition(pos); + const htmlPos = pugDoc.map.getGeneratedPosition(pos); if (!htmlPos) return; @@ -15,9 +15,9 @@ export function register(htmlLs: html.LanguageService) { pugDoc.htmlDocument, ); - return transformer.asLocations( + return transformLocations( htmlResult, - htmlRange => pugDoc.map.toSourceRange(htmlRange), + htmlRange => pugDoc.map.getSourceRange(htmlRange), ); }; } diff --git a/packages/pug/src/services/documentLinks.ts b/packages/pug/src/services/documentLinks.ts index 610d11b9..d524fa8f 100644 --- a/packages/pug/src/services/documentLinks.ts +++ b/packages/pug/src/services/documentLinks.ts @@ -1,6 +1,6 @@ -import { transformer } from '@volar/language-service'; import type * as html from 'vscode-html-languageservice'; import type { PugDocument } from '../pugDocument'; +import { transformLocations } from '@volar/language-service'; export function register(htmlLs: html.LanguageService) { return (pugDoc: PugDocument, docContext: html.DocumentContext) => { @@ -10,9 +10,9 @@ export function register(htmlLs: html.LanguageService) { docContext, ); - return transformer.asLocations( + return transformLocations( htmlResult, - htmlRange => pugDoc.map.toSourceRange(htmlRange), + htmlRange => pugDoc.map.getSourceRange(htmlRange), ); }; } diff --git a/packages/pug/src/services/hover.ts b/packages/pug/src/services/hover.ts index f643f8ee..728e0d59 100644 --- a/packages/pug/src/services/hover.ts +++ b/packages/pug/src/services/hover.ts @@ -1,11 +1,11 @@ -import { transformer } from '@volar/language-service'; import type * as html from 'vscode-html-languageservice'; import type { PugDocument } from '../pugDocument'; +import { transformHover } from '@volar/language-service'; export function register(htmlLs: html.LanguageService) { return (pugDoc: PugDocument, pos: html.Position, options?: html.HoverSettings | undefined) => { - const htmlPos = pugDoc.map.toGeneratedPosition(pos); + const htmlPos = pugDoc.map.getGeneratedPosition(pos); if (!htmlPos) return; @@ -17,6 +17,6 @@ export function register(htmlLs: html.LanguageService) { ); if (!htmlResult) return; - return transformer.asHover(htmlResult, htmlRange => pugDoc.map.toSourceRange(htmlRange)); + return transformHover(htmlResult, htmlRange => pugDoc.map.getSourceRange(htmlRange)); }; } diff --git a/packages/pug/src/services/quoteComplete.ts b/packages/pug/src/services/quoteComplete.ts index 99f48caa..037cab8a 100644 --- a/packages/pug/src/services/quoteComplete.ts +++ b/packages/pug/src/services/quoteComplete.ts @@ -4,7 +4,7 @@ import type { PugDocument } from '../pugDocument'; export function register(htmlLs: html.LanguageService) { return (pugDoc: PugDocument, pos: html.Position, options?: html.CompletionConfiguration | undefined) => { - const htmlStart = pugDoc.map.toGeneratedPosition(pos); + const htmlStart = pugDoc.map.getGeneratedPosition(pos); if (!htmlStart) return; const text = htmlLs.doQuoteComplete( diff --git a/packages/pug/src/services/scanner.ts b/packages/pug/src/services/scanner.ts index 957c2896..5aa032e8 100644 --- a/packages/pug/src/services/scanner.ts +++ b/packages/pug/src/services/scanner.ts @@ -5,9 +5,9 @@ export function register(htmlLs: html.LanguageService) { return (pugDoc: PugDocument, initialOffset = 0) => { const htmlOffset = pugDoc.map.map.mappings - .filter(mapping => mapping.sourceRange[0] >= initialOffset) - .sort((a, b) => a.generatedRange[0] - b.generatedRange[0])[0] - ?.generatedRange[0]; + .filter(mapping => mapping.sourceOffsets[0] >= initialOffset) + .sort((a, b) => a.generatedOffsets[0] - b.generatedOffsets[0])[0] + ?.generatedOffsets[0]; if (htmlOffset === undefined) return; @@ -20,10 +20,10 @@ export function register(htmlLs: html.LanguageService) { return htmlScanner.scan(); }, getTokenOffset: () => { - return pugDoc.map.map.toSourceOffset(htmlScanner.getTokenOffset())?.[0] ?? -1; + return pugDoc.map.map.getSourceOffset(htmlScanner.getTokenOffset())?.[0] ?? -1; }, getTokenEnd: () => { - return pugDoc.map.map.toSourceOffset(htmlScanner.getTokenEnd())?.[0] ?? -1; + return pugDoc.map.map.getSourceOffset(htmlScanner.getTokenEnd())?.[0] ?? -1; }, getTokenText: htmlScanner.getTokenText, getTokenLength: htmlScanner.getTokenLength, diff --git a/packages/pug/src/services/selectionRanges.ts b/packages/pug/src/services/selectionRanges.ts index 6eab7e01..325a05c3 100644 --- a/packages/pug/src/services/selectionRanges.ts +++ b/packages/pug/src/services/selectionRanges.ts @@ -1,12 +1,12 @@ -import { transformer } from '@volar/language-service'; import type * as html from 'vscode-html-languageservice'; import type { PugDocument } from '../pugDocument'; +import { transformLocations } from '@volar/language-service'; export function register(htmlLs: html.LanguageService) { return (pugDoc: PugDocument, posArr: html.Position[]) => { const htmlPosArr = posArr - .map(position => pugDoc.map.toGeneratedPosition(position)) + .map(position => pugDoc.map.getGeneratedPosition(position)) .filter((v): v is NonNullable => !!v); const htmlResult = htmlLs.getSelectionRanges( @@ -14,6 +14,6 @@ export function register(htmlLs: html.LanguageService) { htmlPosArr, ); - return transformer.asLocations(htmlResult, htmlRange => pugDoc.map.toSourceRange(htmlRange)); + return transformLocations(htmlResult, htmlRange => pugDoc.map.getSourceRange(htmlRange)); }; } diff --git a/packages/sass-formatter/package.json b/packages/sass-formatter/package.json index e4354c0a..f217640b 100644 --- a/packages/sass-formatter/package.json +++ b/packages/sass-formatter/package.json @@ -28,7 +28,7 @@ "sass-formatter": "^0.7.8" }, "peerDependencies": { - "@volar/language-service": "~1.11.0" + "@volar/language-service": "2.0.0-alpha.0" }, "peerDependenciesMeta": { "@volar/language-service": { diff --git a/packages/sass-formatter/src/index.ts b/packages/sass-formatter/src/index.ts index 85bfbe24..74eb90d1 100644 --- a/packages/sass-formatter/src/index.ts +++ b/packages/sass-formatter/src/index.ts @@ -1,30 +1,30 @@ -import type { Service } from '@volar/language-service'; +import type { ServicePluginInstance, ServicePlugin } from '@volar/language-service'; import { SassFormatter } from 'sass-formatter'; -export function create(configs: Parameters[1]): Service { - return (): ReturnType => { - return { - provideDocumentFormattingEdits(document, range, options) { +export function create(configs: Parameters[1]): ServicePlugin { + return { + create(): ServicePluginInstance { + return { + provideDocumentFormattingEdits(document, range, options) { - if (document.languageId !== 'sass') - return; + if (document.languageId !== 'sass') + return; - const _options: typeof configs = { - ...configs, - insertSpaces: options.insertSpaces, - }; + const _options: typeof configs = { + ...configs, + insertSpaces: options.insertSpaces, + }; - // don't set when options.insertSpaces is false to avoid sass-formatter internal judge bug - if (options.insertSpaces) - _options.tabSize = options.tabSize; + // don't set when options.insertSpaces is false to avoid sass-formatter internal judge bug + if (options.insertSpaces) + _options.tabSize = options.tabSize; - return [{ - newText: SassFormatter.Format(document.getText(), _options), - range: range, - }]; - }, - } + return [{ + newText: SassFormatter.Format(document.getText(), _options), + range: range, + }]; + }, + }; + }, }; } - -export default create; diff --git a/packages/tsconfig/package.json b/packages/tsconfig/package.json index bcc460a5..a99d0563 100644 --- a/packages/tsconfig/package.json +++ b/packages/tsconfig/package.json @@ -33,7 +33,7 @@ "vscode-uri": "^3.0.8" }, "peerDependencies": { - "@volar/language-service": "~1.11.0" + "@volar/language-service": "2.0.0-alpha.0" }, "peerDependenciesMeta": { "@volar/language-service": { diff --git a/packages/tsconfig/src/index.ts b/packages/tsconfig/src/index.ts index 1967308a..b04106a8 100644 --- a/packages/tsconfig/src/index.ts +++ b/packages/tsconfig/src/index.ts @@ -1,4 +1,4 @@ -import type { DocumentLink, FileType, Service } from '@volar/language-service'; +import type { DocumentLink, FileType, ServicePluginInstance, ServicePlugin } from '@volar/language-service'; import * as jsonc from 'jsonc-parser'; import { minimatch } from 'minimatch'; import type { TextDocument } from 'vscode-languageserver-textdocument'; @@ -15,208 +15,205 @@ function mapChildren(node: jsonc.Node | undefined, f: (x: jsonc.Node) => R): : []; } -export function create(): Service { - return (contextOrNull): ReturnType => { +export function create(): ServicePlugin { + return { + create(context): ServicePluginInstance { - if (!contextOrNull) return {}; + const patterns = [ + '**/[jt]sconfig.json', + '**/[jt]sconfig.*.json', + ]; + const languages = ['json', 'jsonc']; - const patterns = [ - '**/[jt]sconfig.json', - '**/[jt]sconfig.*.json', - ]; - const languages = ['json', 'jsonc']; - const ctx = contextOrNull; + return { - return { + /** + * Reference https://github.com/microsoft/vscode/blob/main/extensions/typescript-language-features/src/languageFeatures/tsconfig.ts + */ - /** - * Reference https://github.com/microsoft/vscode/blob/main/extensions/typescript-language-features/src/languageFeatures/tsconfig.ts - */ - - provideDocumentLinks(document) { + provideDocumentLinks(document) { - const match = languages.includes(document.languageId) && patterns.some(pattern => minimatch(document.uri, pattern)); - if (!match) { - return []; - } + const match = languages.includes(document.languageId) && patterns.some(pattern => minimatch(document.uri, pattern)); + if (!match) { + return []; + } - const root = jsonc.parseTree(document.getText()); - if (!root) { - return []; - } + const root = jsonc.parseTree(document.getText()); + if (!root) { + return []; + } - const links = [ - getExtendsLink(document, root), - ...getFilesLinks(document, root), - ...getReferencesLinks(document, root) - ]; + const links = [ + getExtendsLink(document, root), + ...getFilesLinks(document, root), + ...getReferencesLinks(document, root) + ]; - return links.filter(link => !!link) as DocumentLink[]; - }, + return links.filter(link => !!link) as DocumentLink[]; + }, - async resolveDocumentLink(link) { + async resolveDocumentLink(link) { - const data: OpenExtendsLinkCommandArgs = link.data; - if (data) { - const tsconfigPath = await getTsconfigPath(Utils.dirname(URI.parse(data.resourceUri)), data.extendsValue); - if (tsconfigPath === undefined) { - // console.error(vscode.l10n.t("Failed to resolve {0} as module", data.extendsValue)); + const data: OpenExtendsLinkCommandArgs = link.data; + if (data) { + const tsconfigPath = await getTsconfigPath(Utils.dirname(URI.parse(data.resourceUri)), data.extendsValue); + if (tsconfigPath === undefined) { + // console.error(vscode.l10n.t("Failed to resolve {0} as module", data.extendsValue)); + } + link.target = tsconfigPath?.toString(); } - link.target = tsconfigPath?.toString(); + return link; + }, + }; + + function getExtendsLink(document: TextDocument, root: jsonc.Node): DocumentLink | undefined { + const extendsNode = jsonc.findNodeAtLocation(root, ['extends']); + if (!isPathValue(extendsNode)) { + return undefined; + } + + const extendsValue: string = extendsNode.value; + if (extendsValue.startsWith('/')) { + return undefined; } + + const args: OpenExtendsLinkCommandArgs = { + resourceUri: document.uri, + extendsValue: extendsValue + }; + + const link: DocumentLink = { + range: getRange(document, extendsNode), + data: args, + }; + // link.tooltip = vscode.l10n.t("Follow link"); + link.tooltip = "Follow link"; return link; - }, - }; + } - function getExtendsLink(document: TextDocument, root: jsonc.Node): DocumentLink | undefined { - const extendsNode = jsonc.findNodeAtLocation(root, ['extends']); - if (!isPathValue(extendsNode)) { - return undefined; + function getFilesLinks(document: TextDocument, root: jsonc.Node) { + return mapChildren( + jsonc.findNodeAtLocation(root, ['files']), + child => pathNodeToLink(document, child)); } - const extendsValue: string = extendsNode.value; - if (extendsValue.startsWith('/')) { - return undefined; + function getReferencesLinks(document: TextDocument, root: jsonc.Node) { + return mapChildren( + jsonc.findNodeAtLocation(root, ['references']), + child => { + const pathNode = jsonc.findNodeAtLocation(child, ['path']); + if (!isPathValue(pathNode)) { + return undefined; + } + + const link: DocumentLink = { + range: getRange(document, pathNode), + target: pathNode.value.endsWith('.json') + ? getFileTarget(document, pathNode) + : getFolderTarget(document, pathNode) + }; + return link; + }); } - const args: OpenExtendsLinkCommandArgs = { - resourceUri: document.uri, - extendsValue: extendsValue - }; + function pathNodeToLink( + document: TextDocument, + node: jsonc.Node | undefined + ): DocumentLink | undefined { + return isPathValue(node) + ? { range: getRange(document, node), target: getFileTarget(document, node) } + : undefined; + } - const link: DocumentLink = { - range: getRange(document, extendsNode), - data: args, - }; - // link.tooltip = vscode.l10n.t("Follow link"); - link.tooltip = "Follow link"; - return link; - } - - function getFilesLinks(document: TextDocument, root: jsonc.Node) { - return mapChildren( - jsonc.findNodeAtLocation(root, ['files']), - child => pathNodeToLink(document, child)); - } - - function getReferencesLinks(document: TextDocument, root: jsonc.Node) { - return mapChildren( - jsonc.findNodeAtLocation(root, ['references']), - child => { - const pathNode = jsonc.findNodeAtLocation(child, ['path']); - if (!isPathValue(pathNode)) { - return undefined; - } + function isPathValue(extendsNode: jsonc.Node | undefined): extendsNode is jsonc.Node { + return extendsNode + && extendsNode.type === 'string' + && extendsNode.value + && !(extendsNode.value as string).includes('*'); // don't treat globs as links. + } - const link: DocumentLink = { - range: getRange(document, pathNode), - target: pathNode.value.endsWith('.json') - ? getFileTarget(document, pathNode) - : getFolderTarget(document, pathNode) - }; - return link; - }); - } - - function pathNodeToLink( - document: TextDocument, - node: jsonc.Node | undefined - ): DocumentLink | undefined { - return isPathValue(node) - ? { range: getRange(document, node), target: getFileTarget(document, node) } - : undefined; - } - - function isPathValue(extendsNode: jsonc.Node | undefined): extendsNode is jsonc.Node { - return extendsNode - && extendsNode.type === 'string' - && extendsNode.value - && !(extendsNode.value as string).includes('*'); // don't treat globs as links. - } - - function getFileTarget(document: TextDocument, node: jsonc.Node): string { - return Utils.joinPath(Utils.dirname(URI.parse(document.uri)), node.value).toString(); - } - - function getFolderTarget(document: TextDocument, node: jsonc.Node): string { - return Utils.joinPath(Utils.dirname(URI.parse(document.uri)), node.value, 'tsconfig.json').toString(); - } - - function getRange(document: TextDocument, node: jsonc.Node) { - const offset = node.offset; - const start = document.positionAt(offset + 1); - const end = document.positionAt(offset + (node.length - 1)); - return { start, end }; - } - - async function resolveNodeModulesPath(baseDirUri: URI, pathCandidates: string[]): Promise { - let currentUri = baseDirUri; - const baseCandidate = pathCandidates[0]; - const sepIndex = baseCandidate.startsWith('@') ? 2 : 1; - const moduleBasePath = baseCandidate.split('/').slice(0, sepIndex).join('/'); - while (true) { - const moduleAbsoluteUrl = Utils.joinPath(currentUri, 'node_modules', moduleBasePath); - const moduleStat = await ctx.env.fs?.stat(moduleAbsoluteUrl.toString()); - - if (moduleStat && moduleStat.type === 2 satisfies FileType.Directory) { - for (const uriCandidate of pathCandidates - .map((relativePath) => relativePath.split('/').slice(sepIndex).join('/')) - // skip empty paths within module - .filter(Boolean) - .map((relativeModulePath) => Utils.joinPath(moduleAbsoluteUrl, relativeModulePath)) - ) { - if (await exists(uriCandidate)) { - return uriCandidate; + function getFileTarget(document: TextDocument, node: jsonc.Node): string { + return Utils.joinPath(Utils.dirname(URI.parse(document.uri)), node.value).toString(); + } + + function getFolderTarget(document: TextDocument, node: jsonc.Node): string { + return Utils.joinPath(Utils.dirname(URI.parse(document.uri)), node.value, 'tsconfig.json').toString(); + } + + function getRange(document: TextDocument, node: jsonc.Node) { + const offset = node.offset; + const start = document.positionAt(offset + 1); + const end = document.positionAt(offset + (node.length - 1)); + return { start, end }; + } + + async function resolveNodeModulesPath(baseDirUri: URI, pathCandidates: string[]): Promise { + let currentUri = baseDirUri; + const baseCandidate = pathCandidates[0]; + const sepIndex = baseCandidate.startsWith('@') ? 2 : 1; + const moduleBasePath = baseCandidate.split('/').slice(0, sepIndex).join('/'); + while (true) { + const moduleAbsoluteUrl = Utils.joinPath(currentUri, 'node_modules', moduleBasePath); + const moduleStat = await context.env.fs?.stat(moduleAbsoluteUrl.toString()); + + if (moduleStat && moduleStat.type === 2 satisfies FileType.Directory) { + for (const uriCandidate of pathCandidates + .map((relativePath) => relativePath.split('/').slice(sepIndex).join('/')) + // skip empty paths within module + .filter(Boolean) + .map((relativeModulePath) => Utils.joinPath(moduleAbsoluteUrl, relativeModulePath)) + ) { + if (await exists(uriCandidate)) { + return uriCandidate; + } } + // Continue to looking for potentially another version } - // Continue to looking for potentially another version - } - const oldUri = currentUri; - currentUri = Utils.joinPath(currentUri, '..'); + const oldUri = currentUri; + currentUri = Utils.joinPath(currentUri, '..'); - // Can't go next. Reached the system root - if (oldUri.path === currentUri.path) { - return; + // Can't go next. Reached the system root + if (oldUri.path === currentUri.path) { + return; + } } } - } - - // Reference https://github.com/microsoft/TypeScript/blob/febfd442cdba343771f478cf433b0892f213ad2f/src/compiler/commandLineParser.ts#L3005 - /** - * @returns Returns undefined in case of lack of result while trying to resolve from node_modules - */ - async function getTsconfigPath(baseDirUri: URI, extendsValue: string): Promise { - // Don't take into account a case, where tsconfig might be resolved from the root (see the reference) - // e.g. C:/projects/shared-tsconfig/tsconfig.json (note that C: prefix is optional) - - const isRelativePath = ['./', '../'].some(str => extendsValue.startsWith(str)); - if (isRelativePath) { - const absolutePath = Utils.joinPath(baseDirUri, extendsValue); - if (await exists(absolutePath) || absolutePath.path.endsWith('.json')) { - return absolutePath; + + // Reference https://github.com/microsoft/TypeScript/blob/febfd442cdba343771f478cf433b0892f213ad2f/src/compiler/commandLineParser.ts#L3005 + /** + * @returns Returns undefined in case of lack of result while trying to resolve from node_modules + */ + async function getTsconfigPath(baseDirUri: URI, extendsValue: string): Promise { + // Don't take into account a case, where tsconfig might be resolved from the root (see the reference) + // e.g. C:/projects/shared-tsconfig/tsconfig.json (note that C: prefix is optional) + + const isRelativePath = ['./', '../'].some(str => extendsValue.startsWith(str)); + if (isRelativePath) { + const absolutePath = Utils.joinPath(baseDirUri, extendsValue); + if (await exists(absolutePath) || absolutePath.path.endsWith('.json')) { + return absolutePath; + } + return absolutePath.with({ + path: `${absolutePath.path}.json` + }); } - return absolutePath.with({ - path: `${absolutePath.path}.json` - }); + + // Otherwise resolve like a module + return resolveNodeModulesPath(baseDirUri, [ + extendsValue, + ...extendsValue.endsWith('.json') ? [] : [ + `${extendsValue}.json`, + `${extendsValue}/tsconfig.json`, + ] + ]); } - // Otherwise resolve like a module - return resolveNodeModulesPath(baseDirUri, [ - extendsValue, - ...extendsValue.endsWith('.json') ? [] : [ - `${extendsValue}.json`, - `${extendsValue}/tsconfig.json`, - ] - ]); - } - - async function exists(resource: URI): Promise { - const stat = await ctx.env.fs?.stat(resource.toString()); - // stat.type is an enum flag - return stat?.type === 1 satisfies FileType.File; - } + async function exists(resource: URI): Promise { + const stat = await context.env.fs?.stat(resource.toString()); + // stat.type is an enum flag + return stat?.type === 1 satisfies FileType.File; + } + }, }; } - -export default create; diff --git a/packages/tslint/package.json b/packages/tslint/package.json index fe628b1f..9e3510bf 100644 --- a/packages/tslint/package.json +++ b/packages/tslint/package.json @@ -29,7 +29,7 @@ "volar-service-typescript": "0.0.17" }, "peerDependencies": { - "@volar/language-service": "~1.11.0" + "@volar/language-service": "2.0.0-alpha.0" }, "peerDependenciesMeta": { "@volar/language-service": { diff --git a/packages/tslint/src/index.ts b/packages/tslint/src/index.ts index 7f310f71..fdef22e1 100644 --- a/packages/tslint/src/index.ts +++ b/packages/tslint/src/index.ts @@ -1,100 +1,101 @@ -import type { Service, Diagnostic, CodeAction, ServiceContext } from '@volar/language-service'; +import type { CodeAction, Diagnostic, ServicePlugin, ServicePluginInstance } from '@volar/language-service'; import type { IRule, RuleFailure } from 'tslint'; import type { Provide } from 'volar-service-typescript'; -export function create(rules: IRule[]): Service { +export function create(rules: IRule[]): ServicePlugin { const diagnosticToFailure = new Map(); - return (context: ServiceContext | undefined): ReturnType => ({ - - provideSemanticDiagnostics(document, token) { - - const languageService = context!.inject('typescript/languageService'); - const sourceFile = languageService.getProgram()?.getSourceFile(context!.env.uriToFileName(document.uri)); - if (!sourceFile) { - return; - } - - let failures: RuleFailure[] = []; - - for (const rule of rules) { - if (token.isCancellationRequested) { - return; - } - const ruleSeverity = rule.getOptions().ruleSeverity; - if (ruleSeverity === 'off') { - continue; - } - const ruleFailures = rule.apply(sourceFile); - for (const ruleFailure of ruleFailures) { - ruleFailure.setRuleSeverity(ruleSeverity); - } - failures = failures.concat(ruleFailures); - } - - diagnosticToFailure.set(document.uri, failures); - - const diagnostics: Diagnostic[] = []; - - for (let i = 0; i < failures.length; i++) { - const failure = failures[i]; - const diagnostic: Diagnostic = { - source: 'tslint', - code: failure.getRuleName(), - message: failure.getFailure(), - range: { - start: failure.getStartPosition().getLineAndCharacter(), - end: failure.getEndPosition().getLineAndCharacter(), - }, - severity: failure.getRuleSeverity() === 'error' ? 1 : 2, - data: i, - }; - diagnostics.push(diagnostic); - } - - return diagnostics; - }, - - provideCodeActions(document, _range, codeActionContext) { - - const result: CodeAction[] = []; - - for (const diagnostic of codeActionContext.diagnostics) { - - const failures = diagnosticToFailure.get(document.uri); - const failure = failures?.[diagnostic.data as number]; - if (!failure) { - continue; - } - - const fix = failure.getFix(); - if (!fix) { - continue; - } - - const replaces = Array.isArray(fix) ? fix : [fix]; - const codeAction: CodeAction = { - title: 'Fix TSLint: ' + failure.getFailure(), - kind: 'quickfix', - edit: { - changes: { - [document.uri]: replaces.map(replace => ({ - range: { - start: document.positionAt(replace.start), - end: document.positionAt(replace.end), + return { + create(context): ServicePluginInstance { + return { + provideSemanticDiagnostics(document, token) { + + const languageService = context.inject('typescript/languageService'); + const sourceFile = languageService.getProgram()?.getSourceFile(context.env.uriToFileName(document.uri)); + if (!sourceFile) { + return; + } + + let failures: RuleFailure[] = []; + + for (const rule of rules) { + if (token.isCancellationRequested) { + return; + } + const ruleSeverity = rule.getOptions().ruleSeverity; + if (ruleSeverity === 'off') { + continue; + } + const ruleFailures = rule.apply(sourceFile); + for (const ruleFailure of ruleFailures) { + ruleFailure.setRuleSeverity(ruleSeverity); + } + failures = failures.concat(ruleFailures); + } + + diagnosticToFailure.set(document.uri, failures); + + const diagnostics: Diagnostic[] = []; + + for (let i = 0; i < failures.length; i++) { + const failure = failures[i]; + const diagnostic: Diagnostic = { + source: 'tslint', + code: failure.getRuleName(), + message: failure.getFailure(), + range: { + start: failure.getStartPosition().getLineAndCharacter(), + end: failure.getEndPosition().getLineAndCharacter(), + }, + severity: failure.getRuleSeverity() === 'error' ? 1 : 2, + data: i, + }; + diagnostics.push(diagnostic); + } + + return diagnostics; + }, + + provideCodeActions(document, _range, codeActionContext) { + + const result: CodeAction[] = []; + + for (const diagnostic of codeActionContext.diagnostics) { + + const failures = diagnosticToFailure.get(document.uri); + const failure = failures?.[diagnostic.data as number]; + if (!failure) { + continue; + } + + const fix = failure.getFix(); + if (!fix) { + continue; + } + + const replaces = Array.isArray(fix) ? fix : [fix]; + const codeAction: CodeAction = { + title: 'Fix TSLint: ' + failure.getFailure(), + kind: 'quickfix', + edit: { + changes: { + [document.uri]: replaces.map(replace => ({ + range: { + start: document.positionAt(replace.start), + end: document.positionAt(replace.end), + }, + newText: replace.text, + })), }, - newText: replace.text, - })), - }, - }, - }; - result.push(codeAction); - } - - return result; + }, + }; + result.push(codeAction); + } + + return result; + }, + }; }, - }); + }; }; - -export default create; diff --git a/packages/typescript-twoslash-queries/package.json b/packages/typescript-twoslash-queries/package.json index 76002fcf..ebeea500 100644 --- a/packages/typescript-twoslash-queries/package.json +++ b/packages/typescript-twoslash-queries/package.json @@ -28,7 +28,7 @@ "volar-service-typescript": "0.0.17" }, "peerDependencies": { - "@volar/language-service": "~1.11.0" + "@volar/language-service": "2.0.0-alpha.0" }, "peerDependenciesMeta": { "@volar/language-service": { diff --git a/packages/typescript-twoslash-queries/src/index.ts b/packages/typescript-twoslash-queries/src/index.ts index 00d1587d..319c85f2 100644 --- a/packages/typescript-twoslash-queries/src/index.ts +++ b/packages/typescript-twoslash-queries/src/index.ts @@ -1,44 +1,44 @@ -import type { Service, InlayHint, ServiceContext } from '@volar/language-service'; +import type { InlayHint, ServicePlugin, ServicePluginInstance } from '@volar/language-service'; import type { Provide } from 'volar-service-typescript'; -export function create(): Service { - return (context: ServiceContext | undefined): ReturnType => { - return { - provideInlayHints(document, range) { - if (isTsDocument(document.languageId)) { +export function create(): ServicePlugin { + return { + create(context): ServicePluginInstance { + return { + provideInlayHints(document, range) { + if (isTsDocument(document.languageId)) { - const ts = context!.inject('typescript/typescript'); - const languageService = context!.inject('typescript/languageService'); - const inlayHints: InlayHint[] = []; + const ts = context.inject('typescript/typescript'); + const languageService = context.inject('typescript/languageService'); + const inlayHints: InlayHint[] = []; - for (const pointer of document.getText(range).matchAll(/^\s*\/\/\s*\^\?/gm)) { - const pointerOffset = pointer.index! + pointer[0].indexOf('^?') + document.offsetAt(range.start); - const pointerPosition = document.positionAt(pointerOffset); - const hoverOffset = document.offsetAt({ - line: pointerPosition.line - 1, - character: pointerPosition.character, - }); - - const quickInfo = languageService.getQuickInfoAtPosition(context!.env.uriToFileName(document.uri), hoverOffset); - if (quickInfo) { - inlayHints.push({ - position: { line: pointerPosition.line, character: pointerPosition.character + 2 }, - label: ts.displayPartsToString(quickInfo.displayParts), - paddingLeft: true, - paddingRight: false, + for (const pointer of document.getText(range).matchAll(/^\s*\/\/\s*\^\?/gm)) { + const pointerOffset = pointer.index! + pointer[0].indexOf('^?') + document.offsetAt(range.start); + const pointerPosition = document.positionAt(pointerOffset); + const hoverOffset = document.offsetAt({ + line: pointerPosition.line - 1, + character: pointerPosition.character, }); + + const quickInfo = languageService.getQuickInfoAtPosition(context.env.uriToFileName(document.uri), hoverOffset); + if (quickInfo) { + inlayHints.push({ + position: { line: pointerPosition.line, character: pointerPosition.character + 2 }, + label: ts.displayPartsToString(quickInfo.displayParts), + paddingLeft: true, + paddingRight: false, + }); + } } - } - return inlayHints; - } - }, - }; + return inlayHints; + } + }, + }; + }, }; } -export default create; - function isTsDocument(languageId: string) { return languageId === 'javascript' || languageId === 'typescript' || diff --git a/packages/typescript/package.json b/packages/typescript/package.json index e8f6b195..ebc80fa7 100644 --- a/packages/typescript/package.json +++ b/packages/typescript/package.json @@ -38,8 +38,8 @@ "vscode-uri": "^3.0.8" }, "peerDependencies": { - "@volar/language-service": "~1.11.0", - "@volar/typescript": "~1.11.0" + "@volar/language-service": "2.0.0-alpha.0", + "@volar/typescript": "2.0.0-alpha.0" }, "peerDependenciesMeta": { "@volar/language-service": { diff --git a/packages/typescript/src/configs/getFormatCodeSettings.ts b/packages/typescript/src/configs/getFormatCodeSettings.ts index 02c9bf9b..fe4a4aeb 100644 --- a/packages/typescript/src/configs/getFormatCodeSettings.ts +++ b/packages/typescript/src/configs/getFormatCodeSettings.ts @@ -1,8 +1,8 @@ -import { SharedContext } from '../types'; -import type * as ts from 'typescript/lib/tsserverlibrary'; import type { FormattingOptions } from '@volar/language-service'; -import { getConfigTitle } from '../shared'; +import type * as ts from 'typescript/lib/tsserverlibrary'; import type { TextDocument } from 'vscode-languageserver-textdocument'; +import { getConfigTitle } from '../shared'; +import type { SharedContext } from '../types'; export async function getFormatCodeSettings( ctx: SharedContext, diff --git a/packages/typescript/src/configs/getUserPreferences.ts b/packages/typescript/src/configs/getUserPreferences.ts index 13739818..d6af2c45 100644 --- a/packages/typescript/src/configs/getUserPreferences.ts +++ b/packages/typescript/src/configs/getUserPreferences.ts @@ -1,9 +1,8 @@ -import type * as ts from 'typescript/lib/tsserverlibrary'; -import { getConfigTitle } from '../shared'; import * as path from 'path-browserify'; -import { URI } from 'vscode-uri'; -import { SharedContext } from '../types'; +import type * as ts from 'typescript/lib/tsserverlibrary'; import type { TextDocument } from 'vscode-languageserver-textdocument'; +import { getConfigTitle } from '../shared'; +import type { SharedContext } from '../types'; export async function getUserPreferences( ctx: SharedContext, @@ -28,7 +27,7 @@ export async function getUserPreferences( includeCompletionsWithSnippetText: config.suggest?.includeCompletionsWithSnippetText ?? true, includeCompletionsWithClassMemberSnippets: config.suggest?.classMemberSnippets?.enabled ?? true, includeCompletionsWithObjectLiteralMethodSnippets: config.suggest?.objectLiteralMethodSnippets?.enabled ?? true, - autoImportFileExcludePatterns: getAutoImportFileExcludePatternsPreference(preferencesConfig, ctx.env.rootUri), + autoImportFileExcludePatterns: getAutoImportFileExcludePatternsPreference(preferencesConfig, ctx.typescript.languageServiceHost.getCurrentDirectory()), useLabelDetailsInCompletionEntries: true, allowIncompleteCompletions: true, displayPartsForJSDoc: true, @@ -60,14 +59,14 @@ function getQuoteStylePreference(config: any) { } } -function getAutoImportFileExcludePatternsPreference(config: any, workspaceFolder: URI | undefined) { - return workspaceFolder && (config.autoImportFileExcludePatterns as string[] | undefined)?.map(p => { +function getAutoImportFileExcludePatternsPreference(config: any, workspacePath: string | undefined) { + return workspacePath && (config.autoImportFileExcludePatterns as string[] | undefined)?.map(p => { // Normalization rules: https://github.com/microsoft/TypeScript/pull/49578 const slashNormalized = p.replace(/\\/g, '/'); const isRelative = /^\.\.?($|\/)/.test(slashNormalized); return path.isAbsolute(p) ? p : p.startsWith('*') ? '/' + slashNormalized : - isRelative ? URI.parse(path.join(workspaceFolder.toString(), p)).fsPath : + isRelative ? path.join(workspacePath, p) : '/**/' + slashNormalized; }); } diff --git a/packages/typescript/src/features/callHierarchy.ts b/packages/typescript/src/features/callHierarchy.ts index c5f0e090..6b58c489 100644 --- a/packages/typescript/src/features/callHierarchy.ts +++ b/packages/typescript/src/features/callHierarchy.ts @@ -1,12 +1,12 @@ -import type { TextDocument } from 'vscode-languageserver-textdocument'; import type * as vscode from '@volar/language-service'; +import * as path from 'path-browserify'; import type * as ts from 'typescript/lib/tsserverlibrary'; +import type { TextDocument } from 'vscode-languageserver-textdocument'; import * as PConst from '../protocol.const'; +import { safeCall } from '../shared'; +import type { SharedContext } from '../types'; import { parseKindModifier } from '../utils/modifiers'; import * as typeConverters from '../utils/typeConverters'; -import * as path from 'path-browserify'; -import { SharedContext } from '../types'; -import { safeCall } from '../shared'; export function register(ctx: SharedContext) { function doPrepare(uri: string, position: vscode.Position) { diff --git a/packages/typescript/src/features/codeAction.ts b/packages/typescript/src/features/codeAction.ts index 184f6629..7a669e3b 100644 --- a/packages/typescript/src/features/codeAction.ts +++ b/packages/typescript/src/features/codeAction.ts @@ -1,12 +1,12 @@ -import type * as ts from 'typescript/lib/tsserverlibrary'; import type * as vscode from '@volar/language-service'; -import { fileTextChangesToWorkspaceEdit } from './rename'; -import * as fixNames from '../utils/fixNames'; +import type * as ts from 'typescript/lib/tsserverlibrary'; import { getFormatCodeSettings } from '../configs/getFormatCodeSettings'; import { getUserPreferences } from '../configs/getUserPreferences'; -import { SharedContext } from '../types'; import { safeCall } from '../shared'; +import type { SharedContext } from '../types'; +import * as fixNames from '../utils/fixNames'; import { resolveFixAllCodeAction, resolveOrganizeImportsCodeAction, resolveRefactorCodeAction } from './codeActionResolve'; +import { fileTextChangesToWorkspaceEdit } from './rename'; export interface FixAllData { type: 'fixAll', diff --git a/packages/typescript/src/features/codeActionResolve.ts b/packages/typescript/src/features/codeActionResolve.ts index fa8197b5..e29d7768 100644 --- a/packages/typescript/src/features/codeActionResolve.ts +++ b/packages/typescript/src/features/codeActionResolve.ts @@ -1,11 +1,11 @@ -import type * as ts from 'typescript/lib/tsserverlibrary'; import type * as vscode from '@volar/language-service'; +import type * as ts from 'typescript/lib/tsserverlibrary'; import type { TextDocument } from 'vscode-languageserver-textdocument'; import { getFormatCodeSettings } from '../configs/getFormatCodeSettings'; import { getUserPreferences } from '../configs/getUserPreferences'; import { safeCall } from '../shared'; -import { SharedContext } from '../types'; -import { Data, FixAllData, RefactorData } from './codeAction'; +import type { SharedContext } from '../types'; +import type { Data, FixAllData, RefactorData } from './codeAction'; import { fileTextChangesToWorkspaceEdit } from './rename'; export function register(ctx: SharedContext) { diff --git a/packages/typescript/src/features/completions/basic.ts b/packages/typescript/src/features/completions/basic.ts index 2099e96b..3a339d38 100644 --- a/packages/typescript/src/features/completions/basic.ts +++ b/packages/typescript/src/features/completions/basic.ts @@ -1,12 +1,12 @@ -import { SharedContext } from '../../types'; +import type * as vscode from '@volar/language-service'; import * as semver from 'semver'; import type * as ts from 'typescript/lib/tsserverlibrary'; -import type * as vscode from '@volar/language-service'; import type { TextDocument } from 'vscode-languageserver-textdocument'; import { getUserPreferences } from '../../configs/getUserPreferences'; import * as PConst from '../../protocol.const'; -import { parseKindModifier } from '../../utils/modifiers'; import { safeCall } from '../../shared'; +import type { SharedContext } from '../../types'; +import { parseKindModifier } from '../../utils/modifiers'; export interface Data { uri: string, diff --git a/packages/typescript/src/features/completions/directiveComment.ts b/packages/typescript/src/features/completions/directiveComment.ts index f029ff4b..1cd878db 100644 --- a/packages/typescript/src/features/completions/directiveComment.ts +++ b/packages/typescript/src/features/completions/directiveComment.ts @@ -1,6 +1,6 @@ import type * as vscode from '@volar/language-service'; import * as nls from 'vscode-nls'; -import { SharedContext } from '../../types'; +import type { SharedContext } from '../../types'; const localize = nls.loadMessageBundle(); // TODO: not working diff --git a/packages/typescript/src/features/completions/jsDoc.ts b/packages/typescript/src/features/completions/jsDoc.ts index 4cdb61a8..89dbfeac 100644 --- a/packages/typescript/src/features/completions/jsDoc.ts +++ b/packages/typescript/src/features/completions/jsDoc.ts @@ -1,7 +1,7 @@ -import { SharedContext } from '../../types'; import type * as vscode from '@volar/language-service'; import type { TextDocument } from 'vscode-languageserver-textdocument'; import * as nls from 'vscode-nls'; +import type { SharedContext } from '../../types'; import { getLineText } from './resolve'; const localize = nls.loadMessageBundle(); // TODO: not working diff --git a/packages/typescript/src/features/completions/resolve.ts b/packages/typescript/src/features/completions/resolve.ts index 4749dc95..62c98820 100644 --- a/packages/typescript/src/features/completions/resolve.ts +++ b/packages/typescript/src/features/completions/resolve.ts @@ -1,10 +1,10 @@ -import { SharedContext } from '../../types'; -import type * as ts from 'typescript/lib/tsserverlibrary'; import type * as vscode from '@volar/language-service'; +import type * as ts from 'typescript/lib/tsserverlibrary'; import type { TextDocument } from 'vscode-languageserver-textdocument'; import { getFormatCodeSettings } from '../../configs/getFormatCodeSettings'; import { getUserPreferences } from '../../configs/getUserPreferences'; import { getConfigTitle } from '../../shared'; +import type { SharedContext } from '../../types'; import * as previewer from '../../utils/previewer'; import { snippetForFunctionCall } from '../../utils/snippetForFunctionCall'; import { entriesToLocations } from '../../utils/transforms'; diff --git a/packages/typescript/src/features/definition.ts b/packages/typescript/src/features/definition.ts index 95f2b31e..ebd61aa4 100644 --- a/packages/typescript/src/features/definition.ts +++ b/packages/typescript/src/features/definition.ts @@ -1,7 +1,7 @@ import type * as vscode from '@volar/language-service'; -import { boundSpanToLocationLinks } from '../utils/transforms'; -import { SharedContext } from '../types'; import { safeCall } from '../shared'; +import type { SharedContext } from '../types'; +import { boundSpanToLocationLinks } from '../utils/transforms'; export function register(ctx: SharedContext) { return (uri: string, position: vscode.Position) => { diff --git a/packages/typescript/src/features/diagnostics.ts b/packages/typescript/src/features/diagnostics.ts index f07fe27c..68270f2d 100644 --- a/packages/typescript/src/features/diagnostics.ts +++ b/packages/typescript/src/features/diagnostics.ts @@ -1,8 +1,8 @@ import type * as vscode from '@volar/language-service'; import type * as ts from 'typescript/lib/tsserverlibrary'; import type { TextDocument } from 'vscode-languageserver-textdocument'; -import { SharedContext } from '../types'; import { safeCall } from '../shared'; +import type { SharedContext } from '../types'; export function register(ctx: SharedContext) { const { ts } = ctx; diff --git a/packages/typescript/src/features/documentHighlight.ts b/packages/typescript/src/features/documentHighlight.ts index e95541b3..09dc58a6 100644 --- a/packages/typescript/src/features/documentHighlight.ts +++ b/packages/typescript/src/features/documentHighlight.ts @@ -1,6 +1,6 @@ -import { SharedContext } from '../types'; import type * as vscode from '@volar/language-service'; import { safeCall } from '../shared'; +import type { SharedContext } from '../types'; export function register(ctx: SharedContext) { const { ts } = ctx; diff --git a/packages/typescript/src/features/documentSymbol.ts b/packages/typescript/src/features/documentSymbol.ts index 894faec6..625c4791 100644 --- a/packages/typescript/src/features/documentSymbol.ts +++ b/packages/typescript/src/features/documentSymbol.ts @@ -1,9 +1,9 @@ +import type * as vscode from '@volar/language-service'; import type * as ts from 'typescript/lib/tsserverlibrary'; import * as PConst from '../protocol.const'; -import type * as vscode from '@volar/language-service'; -import { parseKindModifier } from '../utils/modifiers'; -import { SharedContext } from '../types'; import { safeCall } from '../shared'; +import type { SharedContext } from '../types'; +import { parseKindModifier } from '../utils/modifiers'; const getSymbolKind = (kind: string): vscode.SymbolKind => { switch (kind) { diff --git a/packages/typescript/src/features/fileReferences.ts b/packages/typescript/src/features/fileReferences.ts index 3977eef4..3c7e22ab 100644 --- a/packages/typescript/src/features/fileReferences.ts +++ b/packages/typescript/src/features/fileReferences.ts @@ -1,7 +1,7 @@ import type * as vscode from '@volar/language-service'; -import { entriesToLocations } from '../utils/transforms'; -import { SharedContext } from '../types'; import { safeCall } from '../shared'; +import type { SharedContext } from '../types'; +import { entriesToLocations } from '../utils/transforms'; export function register(ctx: SharedContext) { return (uri: string): vscode.Location[] => { diff --git a/packages/typescript/src/features/fileRename.ts b/packages/typescript/src/features/fileRename.ts index 900e633a..4a20045c 100644 --- a/packages/typescript/src/features/fileRename.ts +++ b/packages/typescript/src/features/fileRename.ts @@ -1,9 +1,9 @@ import type * as vscode from 'vscode-languageserver-protocol'; -import { fileTextChangesToWorkspaceEdit } from './rename'; import { getFormatCodeSettings } from '../configs/getFormatCodeSettings'; import { getUserPreferences } from '../configs/getUserPreferences'; -import { SharedContext } from '../types'; import { safeCall } from '../shared'; +import type { SharedContext } from '../types'; +import { fileTextChangesToWorkspaceEdit } from './rename'; export function register(ctx: SharedContext) { return async (oldUri: string, newUri: string): Promise => { diff --git a/packages/typescript/src/features/foldingRanges.ts b/packages/typescript/src/features/foldingRanges.ts index 3bd71af4..0a4bb275 100644 --- a/packages/typescript/src/features/foldingRanges.ts +++ b/packages/typescript/src/features/foldingRanges.ts @@ -1,8 +1,8 @@ -import type { TextDocument } from 'vscode-languageserver-textdocument'; -import type * as ts from 'typescript/lib/tsserverlibrary'; import type * as vscode from '@volar/language-service'; -import { SharedContext } from '../types'; +import type * as ts from 'typescript/lib/tsserverlibrary'; +import type { TextDocument } from 'vscode-languageserver-textdocument'; import { safeCall } from '../shared'; +import type { SharedContext } from '../types'; export function register(ctx: SharedContext) { const { ts } = ctx; diff --git a/packages/typescript/src/features/formatting.ts b/packages/typescript/src/features/formatting.ts index cb783f31..d6bc45c5 100644 --- a/packages/typescript/src/features/formatting.ts +++ b/packages/typescript/src/features/formatting.ts @@ -1,8 +1,8 @@ import type * as vscode from '@volar/language-service'; +import type { TextDocument } from 'vscode-languageserver-textdocument'; import { getFormatCodeSettings } from '../configs/getFormatCodeSettings'; import { safeCall } from '../shared'; -import { SharedContext } from '../types'; -import type { TextDocument } from 'vscode-languageserver-textdocument'; +import type { SharedContext } from '../types'; export function register(ctx: SharedContext) { return { diff --git a/packages/typescript/src/features/hover.ts b/packages/typescript/src/features/hover.ts index cd4c6399..01c34b08 100644 --- a/packages/typescript/src/features/hover.ts +++ b/packages/typescript/src/features/hover.ts @@ -1,7 +1,7 @@ import type * as vscode from '@volar/language-service'; -import * as previewer from '../utils/previewer'; -import { SharedContext } from '../types'; import { safeCall } from '../shared'; +import type { SharedContext } from '../types'; +import * as previewer from '../utils/previewer'; export function register(ctx: SharedContext) { const { ts } = ctx; diff --git a/packages/typescript/src/features/implementation.ts b/packages/typescript/src/features/implementation.ts index 883d1542..234e7fbd 100644 --- a/packages/typescript/src/features/implementation.ts +++ b/packages/typescript/src/features/implementation.ts @@ -1,7 +1,7 @@ import type * as vscode from 'vscode-languageserver-protocol'; -import { entriesToLocationLinks } from '../utils/transforms'; -import { SharedContext } from '../types'; import { safeCall } from '../shared'; +import type { SharedContext } from '../types'; +import { entriesToLocationLinks } from '../utils/transforms'; export function register(ctx: SharedContext) { return (uri: string, position: vscode.Position) => { diff --git a/packages/typescript/src/features/inlayHints.ts b/packages/typescript/src/features/inlayHints.ts index e9fbe460..66855fcd 100644 --- a/packages/typescript/src/features/inlayHints.ts +++ b/packages/typescript/src/features/inlayHints.ts @@ -1,7 +1,7 @@ -import { SharedContext } from '../types'; import type * as vscode from '@volar/language-service'; import { getUserPreferences } from '../configs/getUserPreferences'; import { safeCall } from '../shared'; +import type { SharedContext } from '../types'; export function register(ctx: SharedContext) { const { ts } = ctx; diff --git a/packages/typescript/src/features/prepareRename.ts b/packages/typescript/src/features/prepareRename.ts index 22ce0652..d0024c8e 100644 --- a/packages/typescript/src/features/prepareRename.ts +++ b/packages/typescript/src/features/prepareRename.ts @@ -1,6 +1,6 @@ -import { SharedContext } from '../types'; import type * as vscode from '@volar/language-service'; import { safeCall } from '../shared'; +import type { SharedContext } from '../types'; /* typescript-language-features is hardcode true */ export const renameInfoOptions = { allowRenameOfImportPath: true }; diff --git a/packages/typescript/src/features/references.ts b/packages/typescript/src/features/references.ts index 32a6ae50..87e2ca65 100644 --- a/packages/typescript/src/features/references.ts +++ b/packages/typescript/src/features/references.ts @@ -1,7 +1,7 @@ import type * as vscode from '@volar/language-service'; -import { entriesToLocations } from '../utils/transforms'; -import { SharedContext } from '../types'; import { safeCall } from '../shared'; +import type { SharedContext } from '../types'; +import { entriesToLocations } from '../utils/transforms'; export function register(ctx: SharedContext) { return (uri: string, position: vscode.Position): vscode.Location[] => { diff --git a/packages/typescript/src/features/rename.ts b/packages/typescript/src/features/rename.ts index 52b26863..a1d5ab6f 100644 --- a/packages/typescript/src/features/rename.ts +++ b/packages/typescript/src/features/rename.ts @@ -1,11 +1,11 @@ -import type * as ts from 'typescript/lib/tsserverlibrary'; import type * as vscode from '@volar/language-service'; import * as path from 'path-browserify'; -import { renameInfoOptions } from './prepareRename'; +import type * as ts from 'typescript/lib/tsserverlibrary'; import { getFormatCodeSettings } from '../configs/getFormatCodeSettings'; import { getUserPreferences } from '../configs/getUserPreferences'; -import { SharedContext } from '../types'; import { safeCall } from '../shared'; +import type { SharedContext } from '../types'; +import { renameInfoOptions } from './prepareRename'; export function register(ctx: SharedContext) { diff --git a/packages/typescript/src/features/selectionRanges.ts b/packages/typescript/src/features/selectionRanges.ts index 6d454162..95780226 100644 --- a/packages/typescript/src/features/selectionRanges.ts +++ b/packages/typescript/src/features/selectionRanges.ts @@ -1,8 +1,8 @@ -import { SharedContext } from '../types'; -import type * as ts from 'typescript/lib/tsserverlibrary'; import type * as vscode from '@volar/language-service'; +import type * as ts from 'typescript/lib/tsserverlibrary'; import type { TextDocument } from 'vscode-languageserver-textdocument'; import { safeCall } from '../shared'; +import type { SharedContext } from '../types'; export function register(ctx: SharedContext) { return (uri: string, positions: vscode.Position[]): vscode.SelectionRange[] => { diff --git a/packages/typescript/src/features/semanticTokens.ts b/packages/typescript/src/features/semanticTokens.ts index e200666e..3deef700 100644 --- a/packages/typescript/src/features/semanticTokens.ts +++ b/packages/typescript/src/features/semanticTokens.ts @@ -1,7 +1,7 @@ import type * as vscode from '@volar/language-service'; import type { TextDocument } from 'vscode-languageserver-textdocument'; -import { SharedContext } from '../types'; import { safeCall } from '../shared'; +import type { SharedContext } from '../types'; export function register(ctx: SharedContext) { const { ts } = ctx; diff --git a/packages/typescript/src/features/signatureHelp.ts b/packages/typescript/src/features/signatureHelp.ts index 76527ae4..e42f1e01 100644 --- a/packages/typescript/src/features/signatureHelp.ts +++ b/packages/typescript/src/features/signatureHelp.ts @@ -1,7 +1,7 @@ -import { SharedContext } from '../types'; -import type * as ts from 'typescript/lib/tsserverlibrary'; import type * as vscode from '@volar/language-service'; +import type * as ts from 'typescript/lib/tsserverlibrary'; import { safeCall } from '../shared'; +import type { SharedContext } from '../types'; export function register(ctx: SharedContext) { const { ts } = ctx; diff --git a/packages/typescript/src/features/typeDefinition.ts b/packages/typescript/src/features/typeDefinition.ts index cbec90a8..b5414714 100644 --- a/packages/typescript/src/features/typeDefinition.ts +++ b/packages/typescript/src/features/typeDefinition.ts @@ -1,7 +1,7 @@ import type * as vscode from 'vscode-languageserver-protocol'; -import { entriesToLocationLinks } from '../utils/transforms'; -import { SharedContext } from '../types'; import { safeCall } from '../shared'; +import type { SharedContext } from '../types'; +import { entriesToLocationLinks } from '../utils/transforms'; export function register(ctx: SharedContext) { return (uri: string, position: vscode.Position) => { diff --git a/packages/typescript/src/features/workspaceSymbol.ts b/packages/typescript/src/features/workspaceSymbol.ts index 70bcfb0a..11610efe 100644 --- a/packages/typescript/src/features/workspaceSymbol.ts +++ b/packages/typescript/src/features/workspaceSymbol.ts @@ -1,9 +1,9 @@ +import type * as vscode from '@volar/language-service'; import type * as ts from 'typescript/lib/tsserverlibrary'; import * as PConst from '../protocol.const'; -import type * as vscode from '@volar/language-service'; -import { parseKindModifier } from '../utils/modifiers'; -import { SharedContext } from '../types'; import { safeCall } from '../shared'; +import type { SharedContext } from '../types'; +import { parseKindModifier } from '../utils/modifiers'; function getSymbolKind(item: ts.NavigateToItem): vscode.SymbolKind { switch (item.kind) { diff --git a/packages/typescript/src/index.ts b/packages/typescript/src/index.ts index f36b533e..81247501 100644 --- a/packages/typescript/src/index.ts +++ b/packages/typescript/src/index.ts @@ -1,9 +1,11 @@ -import type { CompletionList, Service, CompletionTriggerKind, FileChangeType, CancellationToken } from '@volar/language-service'; +import type { CancellationToken, CompletionList, CompletionTriggerKind, FileChangeType, ServicePluginInstance, ServicePlugin } from '@volar/language-service'; import * as semver from 'semver'; import type * as ts from 'typescript/lib/tsserverlibrary'; -import { getConfigTitle, isJsonDocument, isTsDocument } from './shared'; import { TextDocument } from 'vscode-languageserver-textdocument'; +import { getConfigTitle, isJsonDocument, isTsDocument } from './shared'; +import { getDocumentRegistry } from '@volar/typescript'; +import * as tsFaster from 'typescript-auto-import-cache'; import * as _callHierarchy from './features/callHierarchy'; import * as codeActions from './features/codeAction'; import * as codeActionResolve from './features/codeActionResolve'; @@ -30,610 +32,598 @@ import * as semanticTokens from './features/semanticTokens'; import * as signatureHelp from './features/signatureHelp'; import * as typeDefinitions from './features/typeDefinition'; import * as workspaceSymbols from './features/workspaceSymbol'; -import { SharedContext } from './types'; -import { createLanguageServiceHost, createSys, getDocumentRegistry } from '@volar/typescript'; -import * as tsFaster from 'typescript-auto-import-cache'; +import type { SharedContext } from './types'; export * from '@volar/typescript'; export interface Provide { 'typescript/typescript': () => typeof import('typescript/lib/tsserverlibrary'); 'typescript/sys': () => ts.System; - 'typescript/sourceFile': (document: TextDocument) => ts.SourceFile | undefined; - 'typescript/textDocument': (uri: string) => TextDocument | undefined; - 'typescript/languageService': (document?: TextDocument) => ts.LanguageService; - 'typescript/languageServiceHost': (document?: TextDocument) => ts.LanguageServiceHost; + 'typescript/languageService': () => ts.LanguageService; + 'typescript/languageServiceHost': () => ts.LanguageServiceHost; 'typescript/syntacticLanguageService': () => ts.LanguageService; 'typescript/syntacticLanguageServiceHost': () => ts.LanguageServiceHost; }; -const jsDocTriggerCharacter = '*'; -const directiveCommentTriggerCharacter = '@'; -const triggerCharacters: ReturnType = { - triggerCharacters: [ - ...getBasicTriggerCharacters('4.3.0'), - jsDocTriggerCharacter, - directiveCommentTriggerCharacter, - ], - signatureHelpTriggerCharacters: ['(', ',', '<'], - signatureHelpRetriggerCharacters: [')'], - // https://github.com/microsoft/vscode/blob/ce119308e8fd4cd3f992d42b297588e7abe33a0c/extensions/typescript-language-features/src/languageFeatures/formatting.ts#L99 - autoFormatTriggerCharacters: [';', '}', '\n'], -}; - -export function create(): Service { - return (contextOrNull, modules): ReturnType> => { - - if (!contextOrNull) { - return triggerCharacters as any; - } - - const context = contextOrNull; - if (!modules?.typescript) { - console.warn('[volar-service-typescript] context.typescript not found, volar-service-typescript is disabled. Make sure you have provide tsdk in language client.'); - return {} as any; - } - - const ts = modules.typescript; - const sys = createSys(ts, context.env); - const languageServiceHost = createLanguageServiceHost(context, ts, sys); - const created = tsFaster.createLanguageService( - ts, - sys, - languageServiceHost, - proxiedHost => ts.createLanguageService(proxiedHost, getDocumentRegistry(ts, sys.useCaseSensitiveFileNames, context.host.workspacePath)), - ); - const { languageService } = created; - - if (created.setPreferences && context.env.getConfiguration) { - - updatePreferences(); - context.env.onDidChangeConfiguration?.(updatePreferences); - - async function updatePreferences() { - const preferences = await context.env.getConfiguration?.('typescript.preferences'); - if (preferences) { - created.setPreferences?.(preferences); - } - } - } - - if (created.projectUpdated) { - let scriptFileNames = new Set(context.host.getScriptFileNames()); - context.env.onDidChangeWatchedFiles?.((params) => { - if (params.changes.some(change => change.type !== 2 satisfies typeof FileChangeType.Changed)) { - scriptFileNames = new Set(context.host.getScriptFileNames()); - } - for (const change of params.changes) { - if (scriptFileNames.has(context.env.uriToFileName(change.uri))) { - created.projectUpdated?.(context.env.uriToFileName(context.env.rootUri.fsPath)); +export function create(ts: typeof import('typescript/lib/tsserverlibrary')): ServicePlugin { + const basicTriggerCharacters = getBasicTriggerCharacters(ts.version); + const jsDocTriggerCharacter = '*'; + const directiveCommentTriggerCharacter = '@'; + return { + triggerCharacters: [ + ...basicTriggerCharacters, + jsDocTriggerCharacter, + directiveCommentTriggerCharacter, + ], + signatureHelpTriggerCharacters: ['(', ',', '<'], + signatureHelpRetriggerCharacters: [')'], + // https://github.com/microsoft/vscode/blob/ce119308e8fd4cd3f992d42b297588e7abe33a0c/extensions/typescript-language-features/src/languageFeatures/formatting.ts#L99 + autoFormatTriggerCharacters: [';', '}', '\n'], + create(context): ServicePluginInstance { + + const syntacticServiceHost: ts.LanguageServiceHost = { + getProjectVersion: () => syntacticHostCtx.projectVersion.toString(), + getScriptFileNames: () => [syntacticHostCtx.fileName], + getScriptVersion: fileName => fileName === syntacticHostCtx.fileName ? syntacticHostCtx.fileVersion.toString() : '', + getScriptSnapshot: fileName => fileName === syntacticHostCtx.fileName ? syntacticHostCtx.snapshot : undefined, + getCompilationSettings: () => ({}), + getCurrentDirectory: () => '/', + getDefaultLibFileName: () => '', + readFile: () => undefined, + fileExists: fileName => fileName === syntacticHostCtx.fileName, + }; + const syntacticCtx: SharedContext = { + ...context, + typescript: { + languageServiceHost: syntacticServiceHost, + languageService: ts.createLanguageService(syntacticServiceHost, undefined, 2 satisfies ts.LanguageServiceMode.Syntactic), + }, + ts, + getTextDocument(uri) { + const virtualFile = context.language.files.getVirtualFile(uri)[0]; + if (virtualFile) { + return context.documents.get(uri, virtualFile.languageId, virtualFile.snapshot); } - } - }); - } - - const basicTriggerCharacters = getBasicTriggerCharacters(ts.version); - const documents = new WeakMap(); - const semanticCtx: SharedContext = { - ...context, - typescript: { - languageServiceHost, - languageService, - }, - ts, - getTextDocument(uri: string) { - const document = context.getTextDocument(uri); - if (document) { - return document; - } - const snapshot = languageServiceHost.getScriptSnapshot(context.env.uriToFileName(uri)); - if (snapshot) { - let document = documents.get(snapshot); - if (!document) { - document = TextDocument.create(uri, '', 0, snapshot.getText(0, snapshot.getLength())); - documents.set(snapshot, document); + const sourceFile = context.language.files.getSourceFile(uri); + if (sourceFile && !sourceFile.virtualFile) { + return context.documents.get(uri, sourceFile.languageId, sourceFile.snapshot); } - return document; - } - }, - }; - const findDefinition = definitions.register(semanticCtx); - const findTypeDefinition = typeDefinitions.register(semanticCtx); - const findReferences = references.register(semanticCtx); - const findFileReferences = fileReferences.register(semanticCtx); - const findImplementations = implementation.register(semanticCtx); - const doPrepareRename = prepareRename.register(semanticCtx); - const doRename = rename.register(semanticCtx); - const getEditsForFileRename = fileRename.register(semanticCtx); - const getCodeActions = codeActions.register(semanticCtx); - const doCodeActionResolve = codeActionResolve.register(semanticCtx); - const getInlayHints = inlayHints.register(semanticCtx); - const findDocumentHighlights = documentHighlight.register(semanticCtx); - const findWorkspaceSymbols = workspaceSymbols.register(semanticCtx); - const doComplete = completions.register(semanticCtx); - const doCompletionResolve = completionResolve.register(semanticCtx); - const doDirectiveCommentComplete = directiveCommentCompletions.register(semanticCtx); - const doJsDocComplete = jsDocCompletions.register(semanticCtx); - const doHover = hover.register(semanticCtx); - const getSignatureHelp = signatureHelp.register(semanticCtx); - const getSelectionRanges = selectionRanges.register(semanticCtx); - const doValidation = diagnostics.register(semanticCtx); - const getDocumentSemanticTokens = semanticTokens.register(semanticCtx); - const callHierarchy = _callHierarchy.register(semanticCtx); - - let syntacticHostCtx = { - projectVersion: 0, - document: undefined as TextDocument | undefined, - fileName: '', - fileVersion: 0, - snapshot: ts.ScriptSnapshot.fromString(''), - }; - const syntacticServiceHost: ts.LanguageServiceHost = { - getProjectVersion: () => syntacticHostCtx.projectVersion.toString(), - getScriptFileNames: () => [syntacticHostCtx.fileName], - getScriptVersion: fileName => fileName === syntacticHostCtx.fileName ? syntacticHostCtx.fileVersion.toString() : '', - getScriptSnapshot: fileName => fileName === syntacticHostCtx.fileName ? syntacticHostCtx.snapshot : undefined, - getCompilationSettings: () => languageServiceHost.getCompilationSettings() ?? {}, - getCurrentDirectory: () => '/', - getDefaultLibFileName: () => '', - readFile: () => undefined, - fileExists: fileName => fileName === syntacticHostCtx.fileName, - }; - const syntacticCtx: SharedContext = { - ...semanticCtx, - typescript: { - ...semanticCtx.typescript, - languageServiceHost: syntacticServiceHost, - languageService: ts.createLanguageService(syntacticServiceHost), - }, - }; - const findDocumentSymbols = documentSymbol.register(syntacticCtx); - const doFormatting = formatting.register(syntacticCtx); - const getFoldingRanges = foldingRanges.register(syntacticCtx); - - return { - - dispose() { - languageService.dispose(); - sys.dispose(); - }, - - provide: { - 'typescript/typescript': () => ts, - 'typescript/sys': () => sys, - 'typescript/sourceFile': document => { - if (isTsDocument(document)) { - const sourceFile = getSemanticServiceSourceFile(document.uri); - if (sourceFile) { - return sourceFile; + const snapshot = syntacticServiceHost.getScriptSnapshot(context.env.uriToFileName(uri)); + if (snapshot) { + let document = documents.get(snapshot); + if (!document) { + document = TextDocument.create(uri, '', 0, snapshot.getText(0, snapshot.getLength())); + documents.set(snapshot, document); } - prepareSyntacticService(document); - return syntacticCtx.typescript.languageService.getProgram()?.getSourceFile(syntacticHostCtx.fileName); + return document; } }, - 'typescript/textDocument': semanticCtx.getTextDocument, - 'typescript/languageService': document => { - if (!document || getSemanticServiceSourceFile(document.uri)) { - return semanticCtx.typescript.languageService; - } - prepareSyntacticService(document); - return syntacticCtx.typescript.languageService; - }, - 'typescript/syntacticLanguageService': () => { - return syntacticCtx.typescript.languageService; + }; + const findDocumentSymbols = documentSymbol.register(syntacticCtx); + const doFormatting = formatting.register(syntacticCtx); + const getFoldingRanges = foldingRanges.register(syntacticCtx); + const syntacticService: ServicePluginInstance = { + + provide: { + 'typescript/typescript': () => ts, + 'typescript/sys': () => sys, + 'typescript/languageService': () => syntacticCtx.typescript.languageService, + 'typescript/languageServiceHost': () => syntacticCtx.typescript.languageServiceHost, + 'typescript/syntacticLanguageService': () => syntacticCtx.typescript.languageService, + 'typescript/syntacticLanguageServiceHost': () => syntacticCtx.typescript.languageServiceHost, }, - 'typescript/languageServiceHost': document => { - if (!document || getSemanticServiceSourceFile(document.uri)) { - return semanticCtx.typescript.languageServiceHost; + + provideAutoInsertionEdit(document, position, lastChange) { + if ( + (document.languageId === 'javascriptreact' || document.languageId === 'typescriptreact') + && lastChange.text.endsWith('>') + ) { + const configName = document.languageId === 'javascriptreact' ? 'javascript.autoClosingTags' : 'typescript.autoClosingTags'; + const config = context.env.getConfiguration?.(configName) ?? true; + if (config) { + + prepareSyntacticService(document); + + const close = syntacticCtx.typescript.languageService.getJsxClosingTagAtPosition(context.env.uriToFileName(document.uri), document.offsetAt(position)); + + if (close) { + return '$0' + close.newText; + } + } } - prepareSyntacticService(document); - return syntacticCtx.typescript.languageServiceHost; - }, - 'typescript/syntacticLanguageServiceHost': () => { - return syntacticCtx.typescript.languageServiceHost; }, - }, - ...triggerCharacters, + provideFoldingRanges(document) { - triggerCharacters: [ - ...basicTriggerCharacters, - jsDocTriggerCharacter, - directiveCommentTriggerCharacter, - ], + if (!isTsDocument(document)) + return; - provideAutoInsertionEdit(document, position, ctx) { - if ( - (document.languageId === 'javascriptreact' || document.languageId === 'typescriptreact') - && ctx.lastChange.text.endsWith('>') - ) { - const configName = document.languageId === 'javascriptreact' ? 'javascript.autoClosingTags' : 'typescript.autoClosingTags'; - const config = context.env.getConfiguration?.(configName) ?? true; - if (config) { + prepareSyntacticService(document); - prepareSyntacticService(document); + return getFoldingRanges(document.uri); + }, - const close = syntacticCtx.typescript.languageService.getJsxClosingTagAtPosition(context.env.uriToFileName(document.uri), document.offsetAt(position)); + provideDocumentSymbols(document) { - if (close) { - return '$0' + close.newText; - } - } - } - }, + if (!isTsDocument(document)) + return; - provideCompletionItems(document, position, context, token) { + prepareSyntacticService(document); - if (!isTsDocument(document)) - return; + return findDocumentSymbols(document.uri); + }, - return worker(token, async () => { + async provideDocumentFormattingEdits(document, range, options_2) { - let result: CompletionList = { - isIncomplete: false, - items: [], - }; + if (!isTsDocument(document)) + return; - if (!context || context.triggerKind !== 2 satisfies typeof CompletionTriggerKind.TriggerCharacter || (context.triggerCharacter && basicTriggerCharacters.includes(context.triggerCharacter))) { + const enable = await context.env.getConfiguration?.(getConfigTitle(document) + '.format.enable'); + if (enable === false) { + return; + } - const completeOptions: ts.GetCompletionsAtPositionOptions = { - triggerCharacter: context?.triggerCharacter as ts.CompletionsTriggerCharacter, - triggerKind: context?.triggerKind, - }; - const basicResult = await doComplete(document.uri, position, completeOptions); + prepareSyntacticService(document); - if (basicResult) { - result = basicResult; - } - } - if (!context || context.triggerKind !== 2 satisfies typeof CompletionTriggerKind.TriggerCharacter || context.triggerCharacter === jsDocTriggerCharacter) { + return await doFormatting.onRange(document, range, options_2); + }, - const jsdocResult = await doJsDocComplete(document.uri, position); + async provideOnTypeFormattingEdits(document, position, key, options_2) { - if (jsdocResult) { - result.items.push(jsdocResult); - } + if (!isTsDocument(document)) + return; + + const enable = await context.env.getConfiguration?.(getConfigTitle(document) + '.format.enable'); + if (enable === false) { + return; } - if (!context || context.triggerKind !== 2 satisfies typeof CompletionTriggerKind.TriggerCharacter || context.triggerCharacter === directiveCommentTriggerCharacter) { - const directiveCommentResult = await doDirectiveCommentComplete(document.uri, position); + prepareSyntacticService(document); - if (directiveCommentResult) { - result.items = result.items.concat(directiveCommentResult); - } - } + return doFormatting.onType(document, options_2, position, key); + }, - return result; - }); - }, + provideFormattingIndentSensitiveLines(document) { - resolveCompletionItem(item, token) { - return worker(token, () => { - return doCompletionResolve(item); - }); - }, + if (!isTsDocument(document)) + return; - provideRenameRange(document, position, token) { + const sourceFile = ts.createSourceFile(context.env.uriToFileName(document.uri), document.getText(), ts.ScriptTarget.ESNext); - if (!isTsDocument(document)) - return; + if (sourceFile) { - return worker(token, () => { - return doPrepareRename(document.uri, position); - }); - }, + const lines: number[] = []; - provideRenameEdits(document, position, newName, token) { + sourceFile.forEachChild(function walk(node) { + if ( + node.kind === ts.SyntaxKind.FirstTemplateToken + || node.kind === ts.SyntaxKind.LastTemplateToken + || node.kind === ts.SyntaxKind.TemplateHead + ) { + const startLine = document.positionAt(node.getStart(sourceFile)).line; + const endLine = document.positionAt(node.getEnd()).line; + for (let i = startLine + 1; i <= endLine; i++) { + lines.push(i); + } + } + node.forEachChild(walk); + }); - if (!isTsDocument(document) && !isJsonDocument(document)) - return; + return lines; + } + }, + }; + + let syntacticHostCtx = { + projectVersion: 0, + document: undefined as TextDocument | undefined, + fileName: '', + fileVersion: 0, + snapshot: ts.ScriptSnapshot.fromString(''), + }; + + if (!context.language.typescript) { + return syntacticService; + } - return worker(token, () => { - return doRename(document.uri, position, newName); - }); - }, + const { sys, languageServiceHost, synchronizeFileSystem } = context.language.typescript; + const created = tsFaster.createLanguageService( + ts, + sys, + languageServiceHost, + proxiedHost => ts.createLanguageService(proxiedHost, getDocumentRegistry(ts, sys.useCaseSensitiveFileNames, languageServiceHost.getCurrentDirectory())), + ); + const { languageService } = created; - provideCodeActions(document, range, context, token) { + if (created.setPreferences && context.env.getConfiguration) { - if (!isTsDocument(document)) - return; + updatePreferences(); + context.env.onDidChangeConfiguration?.(updatePreferences); - return worker(token, () => { - return getCodeActions(document.uri, range, context); - }); - }, + async function updatePreferences() { + const preferences = await context.env.getConfiguration?.('typescript.preferences'); + if (preferences) { + created.setPreferences?.(preferences); + } + } + } - resolveCodeAction(codeAction, token) { - return worker(token, () => { - return doCodeActionResolve(codeAction); - }); - }, + if (created.projectUpdated) { - provideInlayHints(document, range, token) { + const sourceScriptUris = new Set(); + const normalizeUri = sys.useCaseSensitiveFileNames + ? (id: string) => id + : (id: string) => id.toLowerCase(); - if (!isTsDocument(document)) - return; + updateSourceScriptUris(); - return worker(token, () => { - return getInlayHints(document.uri, range); + context.env.onDidChangeWatchedFiles?.((params) => { + const someFileCreateOrDeiete = params.changes.some(change => change.type !== 2 satisfies typeof FileChangeType.Changed); + if (someFileCreateOrDeiete) { + updateSourceScriptUris(); + } + for (const change of params.changes) { + if (sourceScriptUris.has(normalizeUri(change.uri))) { + created.projectUpdated?.(languageServiceHost.getCurrentDirectory()); + } + } }); - }, - provideCallHierarchyItems(document, position, token) { + function updateSourceScriptUris() { + sourceScriptUris.clear(); + for (const fileName of languageServiceHost.getScriptFileNames()) { + const uri = context.env.fileNameToUri(fileName); + const virtualFile = context.language.files.getVirtualFile(uri); + if (virtualFile) { + sourceScriptUris.add(normalizeUri(uri)); + continue; + } + const sourceFile = context.language.files.getSourceFile(uri); + if (sourceFile && !sourceFile.virtualFile) { + sourceScriptUris.add(normalizeUri(uri)); + continue; + } + } + } + } - if (!isTsDocument(document)) - return; + const documents = new WeakMap(); + const semanticCtx: SharedContext = { + ...syntacticCtx, + typescript: { + languageServiceHost, + languageService, + }, + getTextDocument(uri) { + const virtualFile = context.language.files.getVirtualFile(uri)[0]; + if (virtualFile) { + return context.documents.get(uri, virtualFile.languageId, virtualFile.snapshot); + } + const sourceFile = context.language.files.getSourceFile(uri); + if (sourceFile && !sourceFile.virtualFile) { + return context.documents.get(uri, sourceFile.languageId, sourceFile.snapshot); + } + }, + }; + const findDefinition = definitions.register(semanticCtx); + const findTypeDefinition = typeDefinitions.register(semanticCtx); + const findReferences = references.register(semanticCtx); + const findFileReferences = fileReferences.register(semanticCtx); + const findImplementations = implementation.register(semanticCtx); + const doPrepareRename = prepareRename.register(semanticCtx); + const doRename = rename.register(semanticCtx); + const getEditsForFileRename = fileRename.register(semanticCtx); + const getCodeActions = codeActions.register(semanticCtx); + const doCodeActionResolve = codeActionResolve.register(semanticCtx); + const getInlayHints = inlayHints.register(semanticCtx); + const findDocumentHighlights = documentHighlight.register(semanticCtx); + const findWorkspaceSymbols = workspaceSymbols.register(semanticCtx); + const doComplete = completions.register(semanticCtx); + const doCompletionResolve = completionResolve.register(semanticCtx); + const doDirectiveCommentComplete = directiveCommentCompletions.register(semanticCtx); + const doJsDocComplete = jsDocCompletions.register(semanticCtx); + const doHover = hover.register(semanticCtx); + const getSignatureHelp = signatureHelp.register(semanticCtx); + const getSelectionRanges = selectionRanges.register(semanticCtx); + const doValidation = diagnostics.register(semanticCtx); + const getDocumentSemanticTokens = semanticTokens.register(semanticCtx); + const callHierarchy = _callHierarchy.register(semanticCtx); + + return { + + ...syntacticService, + + provide: { + ...syntacticService.provide!, + 'typescript/languageService': () => languageService, + 'typescript/languageServiceHost': () => languageServiceHost, + }, - return worker(token, () => { - return callHierarchy.doPrepare(document.uri, position); - }); - }, + dispose() { + languageService.dispose(); + }, - provideCallHierarchyIncomingCalls(item, token) { - return worker(token, () => { - return callHierarchy.getIncomingCalls(item); - }); - }, + provideCompletionItems(document, position, context, token) { - provideCallHierarchyOutgoingCalls(item, token) { - return worker(token, () => { - return callHierarchy.getOutgoingCalls(item); - }); - }, + if (!isTsDocument(document)) + return; - provideDefinition(document, position, token) { + return worker(token, async () => { - if (!isTsDocument(document)) - return; + let result: CompletionList = { + isIncomplete: false, + items: [], + }; - return worker(token, () => { - return findDefinition(document.uri, position); - }); - }, + if (!context || context.triggerKind !== 2 satisfies typeof CompletionTriggerKind.TriggerCharacter || (context.triggerCharacter && basicTriggerCharacters.includes(context.triggerCharacter))) { - provideTypeDefinition(document, position, token) { + const completeOptions: ts.GetCompletionsAtPositionOptions = { + triggerCharacter: context.triggerCharacter as ts.CompletionsTriggerCharacter, + triggerKind: context.triggerKind, + }; + const basicResult = await doComplete(document.uri, position, completeOptions); - if (!isTsDocument(document)) - return; + if (basicResult) { + result = basicResult; + } + } + if (!context || context.triggerKind !== 2 satisfies typeof CompletionTriggerKind.TriggerCharacter || context.triggerCharacter === jsDocTriggerCharacter) { - return worker(token, () => { - return findTypeDefinition(document.uri, position); - }); - }, + const jsdocResult = await doJsDocComplete(document.uri, position); - provideDiagnostics(document, token) { + if (jsdocResult) { + result.items.push(jsdocResult); + } + } + if (!context || context.triggerKind !== 2 satisfies typeof CompletionTriggerKind.TriggerCharacter || context.triggerCharacter === directiveCommentTriggerCharacter) { - if (!isTsDocument(document)) - return; + const directiveCommentResult = await doDirectiveCommentComplete(document.uri, position); - return worker(token, () => { - return doValidation(document.uri, { syntactic: true, suggestion: true }); - }); - }, + if (directiveCommentResult) { + result.items = result.items.concat(directiveCommentResult); + } + } - provideSemanticDiagnostics(document, token) { + return result; + }); + }, - if (!isTsDocument(document)) - return; + resolveCompletionItem(item, token) { + return worker(token, () => { + return doCompletionResolve(item); + }); + }, - return worker(token, () => { - return doValidation(document.uri, { semantic: true, declaration: true }); - }); - }, + provideRenameRange(document, position, token) { - provideHover(document, position, token) { + if (!isTsDocument(document)) + return; - if (!isTsDocument(document)) - return; + return worker(token, () => { + return doPrepareRename(document.uri, position); + }); + }, - return worker(token, () => { - return doHover(document.uri, position); - }); - }, + provideRenameEdits(document, position, newName, token) { - provideImplementation(document, position, token) { + if (!isTsDocument(document) && !isJsonDocument(document)) + return; - if (!isTsDocument(document)) - return; + return worker(token, () => { + return doRename(document.uri, position, newName); + }); + }, - return worker(token, () => { - return findImplementations(document.uri, position); - }); - }, + provideCodeActions(document, range, context, token) { - provideReferences(document, position, token) { + if (!isTsDocument(document)) + return; - if (!isTsDocument(document) && !isJsonDocument(document)) - return; + return worker(token, () => { + return getCodeActions(document.uri, range, context); + }); + }, - return worker(token, () => { - return findReferences(document.uri, position); - }); - }, + resolveCodeAction(codeAction, token) { + return worker(token, () => { + return doCodeActionResolve(codeAction); + }); + }, - provideFileReferences(document, token) { + provideInlayHints(document, range, token) { - if (!isTsDocument(document) && !isJsonDocument(document)) - return; + if (!isTsDocument(document)) + return; - return worker(token, () => { - return findFileReferences(document.uri); - }); - }, + return worker(token, () => { + return getInlayHints(document.uri, range); + }); + }, - provideDocumentHighlights(document, position, token) { + provideCallHierarchyItems(document, position, token) { - if (!isTsDocument(document)) - return; + if (!isTsDocument(document)) + return; - return worker(token, () => { - return findDocumentHighlights(document.uri, position); - }); - }, + return worker(token, () => { + return callHierarchy.doPrepare(document.uri, position); + }); + }, - provideDocumentSymbols(document) { + provideCallHierarchyIncomingCalls(item, token) { + return worker(token, () => { + return callHierarchy.getIncomingCalls(item); + }); + }, - if (!isTsDocument(document)) - return; + provideCallHierarchyOutgoingCalls(item, token) { + return worker(token, () => { + return callHierarchy.getOutgoingCalls(item); + }); + }, - prepareSyntacticService(document); + provideDefinition(document, position, token) { - return findDocumentSymbols(document.uri); - }, + if (!isTsDocument(document)) + return; - provideDocumentSemanticTokens(document, range, legend, token) { + return worker(token, () => { + return findDefinition(document.uri, position); + }); + }, - if (!isTsDocument(document)) - return; + provideTypeDefinition(document, position, token) { - return worker(token, () => { - return getDocumentSemanticTokens(document.uri, range, legend); - }); - }, + if (!isTsDocument(document)) + return; - provideWorkspaceSymbols(query, token) { - return worker(token, () => { - return findWorkspaceSymbols(query); - }); - }, + return worker(token, () => { + return findTypeDefinition(document.uri, position); + }); + }, - provideFileRenameEdits(oldUri, newUri, token) { - return worker(token, () => { - return getEditsForFileRename(oldUri, newUri); - }); - }, + provideDiagnostics(document, token) { - provideFoldingRanges(document) { + if (!isTsDocument(document)) + return; - if (!isTsDocument(document)) - return; + return worker(token, () => { + return doValidation(document.uri, { syntactic: true, suggestion: true }); + }); + }, + provideSemanticDiagnostics(document, token) { - prepareSyntacticService(document); + if (!isTsDocument(document)) + return; - return getFoldingRanges(document.uri); - }, + return worker(token, () => { + return doValidation(document.uri, { semantic: true, declaration: true }); + }); + }, - provideSelectionRanges(document, positions, token) { + provideHover(document, position, token) { - if (!isTsDocument(document)) - return; + if (!isTsDocument(document)) + return; - return worker(token, () => { - return getSelectionRanges(document.uri, positions); - }); - }, + return worker(token, () => { + return doHover(document.uri, position); + }); + }, - provideSignatureHelp(document, position, context, token) { + provideImplementation(document, position, token) { - if (!isTsDocument(document)) - return; + if (!isTsDocument(document)) + return; - return worker(token, () => { - return getSignatureHelp(document.uri, position, context); - }); - }, + return worker(token, () => { + return findImplementations(document.uri, position); + }); + }, - async provideDocumentFormattingEdits(document, range, options_2) { + provideReferences(document, position, token) { - if (!isTsDocument(document)) - return; + if (!isTsDocument(document) && !isJsonDocument(document)) + return; - const enable = await context.env.getConfiguration?.(getConfigTitle(document) + '.format.enable'); - if (enable === false) { - return; - } + return worker(token, () => { + return findReferences(document.uri, position); + }); + }, - prepareSyntacticService(document); + provideFileReferences(document, token) { - return await doFormatting.onRange(document, range, options_2); - }, + if (!isTsDocument(document) && !isJsonDocument(document)) + return; - async provideOnTypeFormattingEdits(document, position, key, options_2) { + return worker(token, () => { + return findFileReferences(document.uri); + }); + }, - if (!isTsDocument(document)) - return; + provideDocumentHighlights(document, position, token) { - const enable = await context.env.getConfiguration?.(getConfigTitle(document) + '.format.enable'); - if (enable === false) { - return; - } + if (!isTsDocument(document)) + return; - prepareSyntacticService(document); + return worker(token, () => { + return findDocumentHighlights(document.uri, position); + }); + }, - return doFormatting.onType(document, options_2, position, key); - }, + provideDocumentSemanticTokens(document, range, legend, token) { - provideFormattingIndentSensitiveLines(document) { + if (!isTsDocument(document)) + return; - if (!isTsDocument(document)) - return; + return worker(token, () => { + return getDocumentSemanticTokens(document.uri, range, legend); + }); + }, - prepareSyntacticService(document); + provideWorkspaceSymbols(query, token) { + return worker(token, () => { + return findWorkspaceSymbols(query); + }); + }, - const sourceFile = syntacticCtx.typescript.languageService.getProgram()?.getSourceFile(context.env.uriToFileName(document.uri)); + provideFileRenameEdits(oldUri, newUri, token) { + return worker(token, () => { + return getEditsForFileRename(oldUri, newUri); + }); + }, - if (sourceFile) { + provideSelectionRanges(document, positions, token) { - const lines: number[] = []; + if (!isTsDocument(document)) + return; - sourceFile.forEachChild(function walk(node) { - if ( - node.kind === ts.SyntaxKind.FirstTemplateToken - || node.kind === ts.SyntaxKind.LastTemplateToken - || node.kind === ts.SyntaxKind.TemplateHead - ) { - const startLine = document.positionAt(node.getStart(sourceFile)).line; - const endLine = document.positionAt(node.getEnd()).line; - for (let i = startLine + 1; i <= endLine; i++) { - lines.push(i); - } - } - node.forEachChild(walk); + return worker(token, () => { + return getSelectionRanges(document.uri, positions); }); + }, - return lines; - } - }, - }; + provideSignatureHelp(document, position, context, token) { - async function worker(token: CancellationToken, callback: () => T): Promise> { + if (!isTsDocument(document)) + return; - let oldSysVersion = sys.version; - let result = await callback(); - let newSysVersion = await sys.sync(); + return worker(token, () => { + return getSignatureHelp(document.uri, position, context); + }); + }, + }; - while (newSysVersion !== oldSysVersion && !token.isCancellationRequested) { - oldSysVersion = newSysVersion; - result = await callback(); - newSysVersion = await sys.sync(); - } + async function worker(token: CancellationToken, callback: () => T): Promise> { - return result; - } + let oldSysVersion = await synchronizeFileSystem?.(); + let result = await callback(); + let newSysVersion = await synchronizeFileSystem?.(); - function getSemanticServiceSourceFile(uri: string) { - const sourceFile = semanticCtx.typescript.languageService.getProgram()?.getSourceFile(context.env.uriToFileName(uri)); - if (sourceFile) { - return sourceFile; + while (newSysVersion !== oldSysVersion && !token.isCancellationRequested) { + oldSysVersion = newSysVersion; + result = await callback(); + newSysVersion = await synchronizeFileSystem?.(); + } + + return result; } - } - function prepareSyntacticService(document: TextDocument) { - if (syntacticHostCtx.document === document && syntacticHostCtx.fileVersion === document.version) { - return; + function prepareSyntacticService(document: TextDocument) { + if (syntacticHostCtx.document === document && syntacticHostCtx.fileVersion === document.version) { + return; + } + syntacticHostCtx.fileName = context.env.uriToFileName(document.uri); + syntacticHostCtx.fileVersion = document.version; + syntacticHostCtx.snapshot = ts.ScriptSnapshot.fromString(document.getText()); + syntacticHostCtx.projectVersion++; } - syntacticHostCtx.fileName = context.env.uriToFileName(document.uri); - syntacticHostCtx.fileVersion = document.version; - syntacticHostCtx.snapshot = ts.ScriptSnapshot.fromString(document.getText()); - syntacticHostCtx.projectVersion++; - } + }, }; } -export default create; - function getBasicTriggerCharacters(tsVersion: string) { const triggerCharacters = ['.', '"', '\'', '`', '/', '<']; diff --git a/packages/typescript/src/types.ts b/packages/typescript/src/types.ts index ff275c59..680861ab 100644 --- a/packages/typescript/src/types.ts +++ b/packages/typescript/src/types.ts @@ -1,5 +1,6 @@ import type { ServiceContext } from '@volar/language-service'; import type * as ts from 'typescript/lib/tsserverlibrary'; +import type { TextDocument } from 'vscode-languageserver-textdocument'; export type SharedContext = ServiceContext & { typescript: { @@ -7,4 +8,5 @@ export type SharedContext = ServiceContext & { languageService: ts.LanguageService; }; ts: typeof import('typescript/lib/tsserverlibrary'); + getTextDocument: (uri: string) => TextDocument | undefined; }; diff --git a/packages/typescript/src/utils/previewer.ts b/packages/typescript/src/utils/previewer.ts index 7741ba1b..614db876 100644 --- a/packages/typescript/src/utils/previewer.ts +++ b/packages/typescript/src/utils/previewer.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SharedContext } from '../types'; import type * as ts from 'typescript/lib/tsserverlibrary'; import type * as Proto from '../protocol'; +import type { SharedContext } from '../types'; export interface IFilePathToResourceConverter { /** diff --git a/packages/typescript/src/utils/transforms.ts b/packages/typescript/src/utils/transforms.ts index 4734860b..42ee871a 100644 --- a/packages/typescript/src/utils/transforms.ts +++ b/packages/typescript/src/utils/transforms.ts @@ -1,7 +1,7 @@ -import { SharedContext } from '../types'; -import type * as ts from 'typescript/lib/tsserverlibrary'; import type * as vscode from '@volar/language-service'; +import type * as ts from 'typescript/lib/tsserverlibrary'; import type { TextDocument } from 'vscode-languageserver-textdocument'; +import type { SharedContext } from '../types'; export function entriesToLocations( entries: { fileName: string, textSpan: ts.TextSpan; }[], diff --git a/packages/vetur/package.json b/packages/vetur/package.json index 28a40119..1d0fc549 100644 --- a/packages/vetur/package.json +++ b/packages/vetur/package.json @@ -29,7 +29,7 @@ "vscode-html-languageservice": "^5.1.0" }, "peerDependencies": { - "@volar/language-service": "~1.11.0" + "@volar/language-service": "2.0.0-alpha.0" }, "peerDependenciesMeta": { "@volar/language-service": { diff --git a/packages/vetur/src/index.ts b/packages/vetur/src/index.ts index 262035d6..3145a07f 100644 --- a/packages/vetur/src/index.ts +++ b/packages/vetur/src/index.ts @@ -1,285 +1,276 @@ -import type { Service, SemanticToken } from '@volar/language-service'; -import * as vls from 'vls'; -import * as html from 'vscode-html-languageservice'; -import { TextDocument } from 'vscode-html-languageservice'; +import type { SemanticToken, ServicePluginInstance, ServicePlugin } from '@volar/language-service'; import * as fs from 'fs'; import * as path from 'path'; +import * as vls from 'vls'; +import type { TextDocument } from 'vscode-html-languageservice'; +import * as html from 'vscode-html-languageservice'; import { getGlobalSnippetDir } from './userSnippetDir'; -const triggerCharacters: ReturnType = { - // https://github.com/microsoft/vscode/blob/09850876e652688fb142e2e19fd00fd38c0bc4ba/extensions/html-language-features/server/src/htmlServer.ts#L183 - triggerCharacters: ['.', ':', '<', '"', '=', '/', /* vue event shorthand */'@'], -}; +export function create(): ServicePlugin { + return { + // https://github.com/microsoft/vscode/blob/09850876e652688fb142e2e19fd00fd38c0bc4ba/extensions/html-language-features/server/src/htmlServer.ts#L183 + triggerCharacters: ['.', ':', '<', '"', '=', '/', /* vue event shorthand */'@'], + create(context): ServicePluginInstance { -export function create(): Service { - return (ctx): ReturnType => { + const htmlDocuments = new WeakMap(); + const uriToPackageJsonPath = new Map(); + const htmlDataProviders = new Map(); + const htmlLs = html.getLanguageService(); + const snippetManager = new vls.SnippetManager(getSnippetsPath() ?? ''/* TODO: find snippets folder from document path */, getGlobalSnippetDir(false)); + const scaffoldSnippetSources: vls.ScaffoldSnippetSources = { + workspace: '💼', + user: '🗒️', + vetur: '✌' + }; - if (!ctx) { - return triggerCharacters; - } + return { - const htmlDocuments = new WeakMap(); - const uriToPackageJsonPath = new Map(); - const htmlDataProviders = new Map(); - const htmlLs = html.getLanguageService(); - const snippetManager = new vls.SnippetManager(getSnippetsPath() ?? ''/* TODO: find snippets folder from document path */, getGlobalSnippetDir(false)); - const scaffoldSnippetSources: vls.ScaffoldSnippetSources = { - workspace: '💼', - user: '🗒️', - vetur: '✌' - }; + isAdditionalCompletion: true, - return { + provideCompletionItems(document, position, context) { - ...triggerCharacters, + let result: html.CompletionList | undefined; - isAdditionalCompletion: true, - - provideCompletionItems(document, position, context) { + htmlWorker(document, htmlDocument => { + result = htmlLs.doComplete(document, position, htmlDocument); + }); - let result: html.CompletionList | undefined; + if (!context.triggerCharacter) { + vueWorker(document, () => { + const items = snippetManager.completeSnippets(scaffoldSnippetSources); + if (items.length) { + result = { + isIncomplete: false, + items: items, + }; + } + }); + } - htmlWorker(document, htmlDocument => { - result = htmlLs.doComplete(document, position, htmlDocument); - }); + return result; + }, - if (!context?.triggerCharacter) { - vueWorker(document, () => { - const items = snippetManager.completeSnippets(scaffoldSnippetSources); - if (items.length) { - result = { - isIncomplete: false, - items: items, - }; - } + provideHover(document, position) { + return htmlWorker(document, htmlDocument => { + return htmlLs.doHover(document, position, htmlDocument); }); - } + }, - return result; - }, + provideDocumentSemanticTokens(document, range) { + return htmlWorker(document, () => { - provideHover(document, position) { - return htmlWorker(document, htmlDocument => { - return htmlLs.doHover(document, position, htmlDocument); - }); - }, + const packageJsonPath = getPackageJsonPath(document); + if (!packageJsonPath) + return; - provideDocumentSemanticTokens(document, range) { - return htmlWorker(document, () => { + const dtmlDataProviders = getHtmlDataProviders(packageJsonPath); + const components = new Set(dtmlDataProviders.map(provider => provider.getId() === 'html5' ? [] : provider.provideTags().map(tag => tag.name)).flat()); + const offsetRange = { + start: document.offsetAt(range.start), + end: document.offsetAt(range.end), + }; + const scanner = htmlLs.createScanner(document.getText()); + const result: SemanticToken[] = []; - const packageJsonPath = getPackageJsonPath(document); - if (!packageJsonPath) - return; + let token = scanner.scan(); - const dtmlDataProviders = getHtmlDataProviders(packageJsonPath); - const components = new Set(dtmlDataProviders.map(provider => provider.getId() === 'html5' ? [] : provider.provideTags().map(tag => tag.name)).flat()); - const offsetRange = { - start: document.offsetAt(range.start), - end: document.offsetAt(range.end), - }; - const scanner = htmlLs.createScanner(document.getText()); - const result: SemanticToken[] = []; + while (token !== html.TokenType.EOS) { - let token = scanner.scan(); + const tokenOffset = scanner.getTokenOffset(); - while (token !== html.TokenType.EOS) { + // TODO: fix source map perf and break in while condition + if (tokenOffset > offsetRange.end) + break; - const tokenOffset = scanner.getTokenOffset(); + if (tokenOffset >= offsetRange.start && (token === html.TokenType.StartTag || token === html.TokenType.EndTag)) { - // TODO: fix source map perf and break in while condition - if (tokenOffset > offsetRange.end) - break; + const tokenText = scanner.getTokenText(); - if (tokenOffset >= offsetRange.start && (token === html.TokenType.StartTag || token === html.TokenType.EndTag)) { + if (components.has(tokenText) || tokenText.indexOf('.') >= 0) { - const tokenText = scanner.getTokenText(); + const tokenLength = scanner.getTokenLength(); + const tokenPosition = document.positionAt(tokenOffset); - if (components.has(tokenText) || tokenText.indexOf('.') >= 0) { - - const tokenLength = scanner.getTokenLength(); - const tokenPosition = document.positionAt(tokenOffset); - - if (components.has(tokenText)) { - result.push([tokenPosition.line, tokenPosition.character, tokenLength, 10/* 10: function, 12: component */, 0]); + if (components.has(tokenText)) { + result.push([tokenPosition.line, tokenPosition.character, tokenLength, 10/* 10: function, 12: component */, 0]); + } } } + token = scanner.scan(); } - token = scanner.scan(); - } - return result; - }); - }, - }; + return result; + }); + }, + }; - function htmlWorker(document: TextDocument, callback: (htmlDocument: html.HTMLDocument) => T) { + function htmlWorker(document: TextDocument, callback: (htmlDocument: html.HTMLDocument) => T) { - const htmlDocument = getHtmlDocument(document); - if (!htmlDocument) - return; + const htmlDocument = getHtmlDocument(document); + if (!htmlDocument) + return; - const packageJsonPath = getPackageJsonPath(document); - if (!packageJsonPath) - return; + const packageJsonPath = getPackageJsonPath(document); + if (!packageJsonPath) + return; - htmlLs.setDataProviders( - false, - getHtmlDataProviders(packageJsonPath), - ); + htmlLs.setDataProviders( + false, + getHtmlDataProviders(packageJsonPath), + ); - return callback(htmlDocument); - } + return callback(htmlDocument); + } - function vueWorker(document: TextDocument, callback: () => T) { - if (document.languageId === 'vue') { - return callback(); + function vueWorker(document: TextDocument, callback: () => T) { + if (document.languageId === 'vue') { + return callback(); + } } - } - function getPackageJsonPath(document: TextDocument) { + function getPackageJsonPath(document: TextDocument) { - let packageJsonPath = uriToPackageJsonPath.get(document.uri); + let packageJsonPath = uriToPackageJsonPath.get(document.uri); - if (!packageJsonPath) { + if (!packageJsonPath) { - let lastDirname = ctx!.env.uriToFileName(document.uri); + let lastDirname = context.env.uriToFileName(document.uri); - while (true) { + while (true) { - const dirname = path.dirname(lastDirname); - if (dirname === lastDirname) { - break; - } + const dirname = path.dirname(lastDirname); + if (dirname === lastDirname) { + break; + } - if (fs.existsSync(dirname + '/package.json')) { - packageJsonPath = dirname + '/package.json'; - break; + if (fs.existsSync(dirname + '/package.json')) { + packageJsonPath = dirname + '/package.json'; + break; + } + + lastDirname = dirname; } - lastDirname = dirname; + uriToPackageJsonPath.set(document.uri, packageJsonPath); } - uriToPackageJsonPath.set(document.uri, packageJsonPath); + return packageJsonPath; } - return packageJsonPath; - } + function getSnippetsPath() { - function getSnippetsPath() { + const fsPath = __filename; - const fsPath = __filename; + let lastDirname = fsPath; + let snippetsPath: string | undefined; - let lastDirname = fsPath; - let snippetsPath: string | undefined; + while (true) { - while (true) { + const dirname = path.dirname(lastDirname); + if (dirname === lastDirname) { + break; + } - const dirname = path.dirname(lastDirname); - if (dirname === lastDirname) { - break; - } + if (fs.existsSync(dirname + '/.vscode/vetur/snippets')) { + snippetsPath = dirname + '/.vscode/vetur/snippets'; + break; + } - if (fs.existsSync(dirname + '/.vscode/vetur/snippets')) { - snippetsPath = dirname + '/.vscode/vetur/snippets'; - break; + lastDirname = dirname; } - lastDirname = dirname; + return snippetsPath; } - return snippetsPath; - } - - function getHtmlDataProviders(packageJsonPath: string) { + function getHtmlDataProviders(packageJsonPath: string) { - let dataProviders = htmlDataProviders.get(packageJsonPath); + let dataProviders = htmlDataProviders.get(packageJsonPath); - if (!dataProviders) { + if (!dataProviders) { - const tagProviderSettings = vls.getTagProviderSettings(packageJsonPath); - const enabledTagProviders = vls.getEnabledTagProviders(tagProviderSettings); + const tagProviderSettings = vls.getTagProviderSettings(packageJsonPath); + const enabledTagProviders = vls.getEnabledTagProviders(tagProviderSettings); - dataProviders = enabledTagProviders.map(provider => { - const htmlProvider: html.IHTMLDataProvider = { - getId: provider.getId, - isApplicable() { - return true; - }, - provideTags() { - const tags: html.ITagData[] = []; - provider.collectTags((tag, documentation) => { - tags.push({ - name: tag, - description: documentation, - attributes: [], - }); - }); - return tags; - }, - provideAttributes(tag) { - const attributes: html.IAttributeData[] = []; - provider.collectAttributes(tag, (attribute, type, documentation) => { - if (attribute.startsWith('v-') || attribute.startsWith('@')) { - attributes.push({ - name: attribute, - valueSet: type, + dataProviders = enabledTagProviders.map(provider => { + const htmlProvider: html.IHTMLDataProvider = { + getId: provider.getId, + isApplicable() { + return true; + }, + provideTags() { + const tags: html.ITagData[] = []; + provider.collectTags((tag, documentation) => { + tags.push({ + name: tag, description: documentation, + attributes: [], }); - } - else { - attributes.push({ - name: 'v-bind:' + attribute, - valueSet: type, - description: documentation, - }); - attributes.push({ - name: ':' + attribute, - valueSet: type, - description: documentation, - }); - attributes.push({ - name: attribute, - valueSet: type, - description: documentation, + }); + return tags; + }, + provideAttributes(tag) { + const attributes: html.IAttributeData[] = []; + provider.collectAttributes(tag, (attribute, type, documentation) => { + if (attribute.startsWith('v-') || attribute.startsWith('@')) { + attributes.push({ + name: attribute, + valueSet: type, + description: documentation, + }); + } + else { + attributes.push({ + name: 'v-bind:' + attribute, + valueSet: type, + description: documentation, + }); + attributes.push({ + name: ':' + attribute, + valueSet: type, + description: documentation, + }); + attributes.push({ + name: attribute, + valueSet: type, + description: documentation, + }); + } + }); + return attributes; + }, + provideValues(tag, attribute) { + const values: html.IValueData[] = []; + provider.collectValues(tag, attribute, value => { + values.push({ + name: value, }); - } - }); - return attributes; - }, - provideValues(tag, attribute) { - const values: html.IValueData[] = []; - provider.collectValues(tag, attribute, value => { - values.push({ - name: value, }); - }); - return values; - }, - }; - return htmlProvider; - }); - - htmlDataProviders.set(packageJsonPath, dataProviders); + return values; + }, + }; + return htmlProvider; + }); + + htmlDataProviders.set(packageJsonPath, dataProviders); + } + + return dataProviders; } - return dataProviders; - } + function getHtmlDocument(document: TextDocument) { - function getHtmlDocument(document: TextDocument) { + if (document.languageId !== 'html') + return; - if (document.languageId !== 'html') - return; + let htmlDocument = htmlDocuments.get(document); - let htmlDocument = htmlDocuments.get(document); + if (!htmlDocument) { + htmlDocument = htmlLs.parseHTMLDocument(document); + htmlDocuments.set(document, htmlDocument); + } - if (!htmlDocument) { - htmlDocument = htmlLs.parseHTMLDocument(document); - htmlDocuments.set(document, htmlDocument); + return htmlDocument; } - - return htmlDocument; - } + }, }; } - -export default create; diff --git a/packages/yaml/package.json b/packages/yaml/package.json index 31e9cf84..83437eee 100644 --- a/packages/yaml/package.json +++ b/packages/yaml/package.json @@ -31,7 +31,7 @@ "vscode-languageserver-textdocument": "^1.0.11" }, "peerDependencies": { - "@volar/language-service": "~1.11.0" + "@volar/language-service": "2.0.0-alpha.0" }, "peerDependenciesMeta": { "@volar/language-service": { diff --git a/packages/yaml/src/index.ts b/packages/yaml/src/index.ts index 1c84b936..e5985128 100644 --- a/packages/yaml/src/index.ts +++ b/packages/yaml/src/index.ts @@ -1,6 +1,6 @@ -import { type Service } from '@volar/language-service'; -import { type TextDocument } from 'vscode-languageserver-textdocument'; -import { type LanguageSettings, type LanguageService } from 'yaml-language-server'; +import type { ServicePluginInstance, ServicePlugin } 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'; export interface Provide { @@ -13,121 +13,114 @@ function isYaml(document: TextDocument): boolean { function noop(): undefined { } -const triggerCharacters = [' ', ':']; - /** * Create a Volar language service for YAML documents. */ -export function create(settings?: LanguageSettings): Service { - return (context): ReturnType> => { - - if (!context) { - return { triggerCharacters } as any; - } - - const ls = getLanguageService({ - schemaRequestService: async (uri) => await context.env.fs?.readFile(uri) ?? '', - telemetry: { - send: noop, - sendError: noop, - sendTrack: noop - }, - // @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)); - } - }, - }); - - ls.configure({ - completion: true, - customTags: [], - format: true, - hover: true, - isKubernetes: false, - validate: true, - yamlVersion: '1.2', - ...settings - }); - - return { - provide: { - 'yaml/languageService': () => ls - }, - - triggerCharacters, - - provideCodeActions(document, range, context) { - if (isYaml(document)) { - return ls.getCodeAction(document, { - context, - range, - textDocument: document - }); - } - }, - - provideCodeLenses(document) { - if (isYaml(document)) { - return ls.getCodeLens(document); - } - }, - - provideCompletionItems(document, position) { - if (isYaml(document)) { - return ls.doComplete(document, position, false); - } - }, - - provideDefinition(document, position) { - if (isYaml(document)) { - return ls.doDefinition(document, { position, textDocument: document }); - } - }, - - provideDiagnostics(document) { - if (isYaml(document)) { - return ls.doValidation(document, false); - } - }, - - provideDocumentSymbols(document) { - if (isYaml(document)) { - return ls.findDocumentSymbols2(document, {}); - } - }, - - provideHover(document, position) { - if (isYaml(document)) { - return ls.doHover(document, position); - } - }, - - provideDocumentLinks(document) { - if (isYaml(document)) { - return ls.findLinks(document); - } - }, - - provideFoldingRanges(document) { - if (isYaml(document)) { - return ls.getFoldingRanges(document, {}); - } - }, - - provideSelectionRanges(document, positions) { - if (isYaml(document)) { - return ls.getSelectionRanges(document, positions); - } - }, - - resolveCodeLens(codeLens) { - return ls.resolveCodeLens(codeLens); - }, - }; +export function create(settings?: LanguageSettings): ServicePlugin { + return { + triggerCharacters: [' ', ':'], + create(context): ServicePluginInstance { + + const ls = getLanguageService({ + schemaRequestService: async (uri) => await context.env.fs?.readFile(uri) ?? '', + telemetry: { + send: noop, + sendError: noop, + sendTrack: noop + }, + // @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)); + } + }, + }); + + ls.configure({ + completion: true, + customTags: [], + format: true, + hover: true, + isKubernetes: false, + validate: true, + yamlVersion: '1.2', + ...settings + }); + + return { + provide: { + 'yaml/languageService': () => ls + }, + + provideCodeActions(document, range, context) { + if (isYaml(document)) { + return ls.getCodeAction(document, { + context, + range, + textDocument: document + }); + } + }, + + provideCodeLenses(document) { + if (isYaml(document)) { + return ls.getCodeLens(document); + } + }, + + provideCompletionItems(document, position) { + if (isYaml(document)) { + return ls.doComplete(document, position, false); + } + }, + + provideDefinition(document, position) { + if (isYaml(document)) { + return ls.doDefinition(document, { position, textDocument: document }); + } + }, + + provideDiagnostics(document) { + if (isYaml(document)) { + return ls.doValidation(document, false); + } + }, + + provideDocumentSymbols(document) { + if (isYaml(document)) { + return ls.findDocumentSymbols2(document, {}); + } + }, + + provideHover(document, position) { + if (isYaml(document)) { + return ls.doHover(document, position); + } + }, + + provideDocumentLinks(document) { + if (isYaml(document)) { + return ls.findLinks(document); + } + }, + + provideFoldingRanges(document) { + if (isYaml(document)) { + return ls.getFoldingRanges(document, {}); + } + }, + + provideSelectionRanges(document, positions) { + if (isYaml(document)) { + return ls.getSelectionRanges(document, positions); + } + }, + + resolveCodeLens(codeLens) { + return ls.resolveCodeLens(codeLens); + }, + }; + }, }; } - -export default create; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 557eee5f..e6968ed8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,8 +15,8 @@ importers: specifier: latest version: 3.0.0(typescript@5.3.2) '@volar/language-service': - specifier: ~1.11.0 - version: 1.11.1 + specifier: 2.0.0-alpha.0 + version: 2.0.0-alpha.0 typescript: specifier: latest version: 5.3.2 @@ -27,8 +27,8 @@ importers: packages/css: dependencies: '@volar/language-service': - specifier: ~1.11.0 - version: 1.11.1 + specifier: 2.0.0-alpha.0 + version: 2.0.0-alpha.0 vscode-css-languageservice: specifier: ^6.2.10 version: 6.2.11 @@ -46,8 +46,8 @@ importers: packages/emmet: dependencies: '@volar/language-service': - specifier: ~1.11.0 - version: 1.11.1 + specifier: 2.0.0-alpha.0 + version: 2.0.0-alpha.0 '@vscode/emmet-helper': specifier: ^2.9.2 version: 2.9.2 @@ -58,8 +58,8 @@ importers: packages/eslint: dependencies: '@volar/language-service': - specifier: ~1.11.0 - version: 1.11.1 + specifier: 2.0.0-alpha.0 + version: 2.0.0-alpha.0 eslint: specifier: '*' version: 8.54.0 @@ -74,8 +74,8 @@ importers: packages/html: dependencies: '@volar/language-service': - specifier: ~1.11.0 - version: 1.11.1 + specifier: 2.0.0-alpha.0 + version: 2.0.0-alpha.0 vscode-html-languageservice: specifier: ^5.1.0 version: 5.1.1 @@ -93,8 +93,8 @@ importers: packages/json: dependencies: '@volar/language-service': - specifier: ~1.11.0 - version: 1.11.1 + specifier: 2.0.0-alpha.0 + version: 2.0.0-alpha.0 vscode-json-languageservice: specifier: ^5.3.7 version: 5.3.7 @@ -109,8 +109,8 @@ importers: packages/markdown: dependencies: '@volar/language-service': - specifier: ~1.11.0 - version: 1.11.1 + specifier: 2.0.0-alpha.0 + version: 2.0.0-alpha.0 markdown-it: specifier: ^13.0.2 version: 13.0.2 @@ -134,8 +134,8 @@ importers: packages/prettier: dependencies: '@volar/language-service': - specifier: ~1.11.0 - version: 1.11.1 + specifier: 2.0.0-alpha.0 + version: 2.0.0-alpha.0 devDependencies: '@types/node': specifier: latest @@ -147,8 +147,8 @@ importers: packages/pretty-ts-errors: dependencies: '@volar/language-service': - specifier: ~1.11.0 - version: 1.11.1 + specifier: 2.0.0-alpha.0 + version: 2.0.0-alpha.0 pretty-ts-errors-lsp: specifier: ^0.0.3 version: 0.0.3 @@ -159,14 +159,14 @@ importers: specifier: ^0.10.0 version: 0.10.0 '@volar/language-service': - specifier: ~1.11.0 - version: 1.11.1 + specifier: 2.0.0-alpha.0 + version: 2.0.0-alpha.0 packages/pug: dependencies: '@volar/language-service': - specifier: ~1.11.0 - version: 1.11.1 + specifier: 2.0.0-alpha.0 + version: 2.0.0-alpha.0 pug-lexer: specifier: ^5.0.1 version: 5.0.1 @@ -193,8 +193,8 @@ importers: specifier: ^0.2.2 version: 0.2.2 '@volar/language-service': - specifier: ~1.11.0 - version: 1.11.1 + specifier: 2.0.0-alpha.0 + version: 2.0.0-alpha.0 devDependencies: '@types/node': specifier: latest @@ -203,8 +203,8 @@ importers: packages/sass-formatter: dependencies: '@volar/language-service': - specifier: ~1.11.0 - version: 1.11.1 + specifier: 2.0.0-alpha.0 + version: 2.0.0-alpha.0 sass-formatter: specifier: ^0.7.8 version: 0.7.8 @@ -212,8 +212,8 @@ importers: packages/tsconfig: dependencies: '@volar/language-service': - specifier: ~1.11.0 - version: 1.11.1 + specifier: 2.0.0-alpha.0 + version: 2.0.0-alpha.0 jsonc-parser: specifier: ^3.2.0 version: 3.2.0 @@ -231,8 +231,8 @@ importers: packages/tslint: dependencies: '@volar/language-service': - specifier: ~1.11.0 - version: 1.11.1 + specifier: 2.0.0-alpha.0 + version: 2.0.0-alpha.0 devDependencies: tslint: specifier: latest @@ -244,8 +244,8 @@ importers: packages/typescript: dependencies: '@volar/language-service': - specifier: ~1.11.0 - version: 1.11.1 + specifier: 2.0.0-alpha.0 + version: 2.0.0-alpha.0 path-browserify: specifier: ^1.0.1 version: 1.0.1 @@ -278,8 +278,8 @@ importers: packages/typescript-twoslash-queries: dependencies: '@volar/language-service': - specifier: ~1.11.0 - version: 1.11.1 + specifier: 2.0.0-alpha.0 + version: 2.0.0-alpha.0 devDependencies: volar-service-typescript: specifier: 0.0.17 @@ -288,8 +288,8 @@ importers: packages/vetur: dependencies: '@volar/language-service': - specifier: ~1.11.0 - version: 1.11.1 + specifier: 2.0.0-alpha.0 + version: 2.0.0-alpha.0 vls: specifier: ^0.8.5 version: 0.8.5 @@ -304,8 +304,8 @@ importers: packages/yaml: dependencies: '@volar/language-service': - specifier: ~1.11.0 - version: 1.11.1 + specifier: 2.0.0-alpha.0 + version: 2.0.0-alpha.0 yaml-language-server: specifier: 1.14.0 version: 1.14.0 @@ -1188,12 +1188,17 @@ packages: resolution: {integrity: sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==} dependencies: '@volar/source-map': 1.11.1 + dev: true - /@volar/language-service@1.11.1: - resolution: {integrity: sha512-dKo8z1UzQRPHnlXxwfONGrasS1wEWXMoLQiohZ8KgWqZALbekZCwdGImLZD4DeFGNjk3HTTdfeCzo3KjwohjEQ==} + /@volar/language-core@2.0.0-alpha.0: + resolution: {integrity: sha512-d/cLyXRJOOj6lNm3E9sUHjy/FafqlPGik9YnOls3KaA/aWRztyKN341V/mFEAk+fiwP+0wa8vpUkBbP9N31Zeg==} dependencies: - '@volar/language-core': 1.11.1 - '@volar/source-map': 1.11.1 + '@volar/source-map': 2.0.0-alpha.0 + + /@volar/language-service@2.0.0-alpha.0: + resolution: {integrity: sha512-m9w4xHr2BB0Hp8BmkyBB2H/kYVmKvL3JXWKFwCr+BO3zsOYn3jxiVX0yYHJQ+jIVAOBEbrnvBFWmr0x2a985Lg==} + dependencies: + '@volar/language-core': 2.0.0-alpha.0 vscode-languageserver-protocol: 3.17.5 vscode-languageserver-textdocument: 1.0.11 vscode-uri: 3.0.8 @@ -1202,6 +1207,12 @@ packages: resolution: {integrity: sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==} dependencies: muggle-string: 0.3.1 + dev: true + + /@volar/source-map@2.0.0-alpha.0: + resolution: {integrity: sha512-4Jfz27az7xiI+USAKvYuXLHqhfvuGtTLssXMlZsmsiPLpYECF7wRmjsPlFOo19vlrnSOj0ysB7ZYE61X+hPZRQ==} + dependencies: + muggle-string: 0.4.0 /@volar/typescript@1.11.1: resolution: {integrity: sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==} @@ -3748,6 +3759,10 @@ packages: /muggle-string@0.3.1: resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==} + dev: true + + /muggle-string@0.4.0: + resolution: {integrity: sha512-ymN6exGtXrNnDb0ae4VP34y5bSKmBm6+TMGHmKoFDE5saXxtszv1EHs4Tt3glo61rCA/Zum4AwM19pCOGAjjRQ==} /mute-stream@1.0.0: resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} diff --git a/tsconfig.base.json b/tsconfig.base.json index d0c6f5f4..42501040 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -14,6 +14,9 @@ "noUnusedLocals": true, "noUnusedParameters": true, "esModuleInterop": false, + "forceConsistentCasingInFileNames": true, + "ignoreDeprecations": "5.0", + "importsNotUsedAsValues": "error", }, "include": [ ], }