From 4820a7cda3f7647c94a717e2fde25d04be9cbbda Mon Sep 17 00:00:00 2001 From: johnsoncodehk Date: Tue, 3 Jan 2023 18:17:34 +0800 Subject: [PATCH] refactor: simplify language server params for vue --- .../angular-language-server/src/index.ts | 46 +-- .../src/index.ts | 2 +- .../language-server/src/common/project.ts | 3 +- .../language-server/src/common/workspace.ts | 2 +- packages/language-server/src/types.ts | 3 +- .../src/baseLanguageService.ts | 29 +- .../src/documentFeatures/format.ts | 6 +- packages/language-service/src/types.ts | 19 +- plugins/pug/src/index.ts | 4 +- .../typescript-vue-plugin/src/index.ts | 2 +- .../vue-component-meta/src/index.ts | 8 +- .../vue-language-core/src/types.ts | 2 +- .../src/languageServerPlugin.ts | 28 +- .../src/ideFeatures/nameCasing.ts | 6 +- .../src/languageService.ts | 82 ++-- .../vue-language-service/src/plugins/empty.ts | 4 +- .../src/plugins/vue-autoinsert-dotvalue.ts | 5 +- .../src/plugins/vue-autoinsert-parentheses.ts | 146 ++++--- .../src/plugins/vue-autoinsert-space.ts | 4 +- .../src/plugins/vue-codelens-references.ts | 17 +- .../src/plugins/vue-convert-htmlpug.ts | 137 ++++--- .../src/plugins/vue-convert-refsugar.ts | 29 +- .../src/plugins/vue-convert-scriptsetup.ts | 22 +- .../src/plugins/vue-template.ts | 40 +- .../src/plugins/vue-twoslash-queries.ts | 106 +++--- .../vue-language-service/src/plugins/vue.ts | 356 +++++++++--------- .../vue-language-service/src/types.ts | 5 + .../tests/utils/createTester.ts | 4 +- vue-language-tools/vue-tsc/src/index.ts | 6 +- .../vue-typescript/src/index.ts | 2 +- 30 files changed, 526 insertions(+), 599 deletions(-) diff --git a/angular-language-tools/angular-language-server/src/index.ts b/angular-language-tools/angular-language-server/src/index.ts index f7b5d3125..c7d58d284 100644 --- a/angular-language-tools/angular-language-server/src/index.ts +++ b/angular-language-tools/angular-language-server/src/index.ts @@ -1,7 +1,7 @@ import { createTsLanguageModule, createHtmlLanguageModule, HTMLTemplateFile } from '@volar-examples/angular-language-core'; import createTsPlugin from '@volar-plugins/typescript'; import { createLanguageServer, LanguageServerPlugin } from '@volar/language-server/node'; -import type { LanguageServicePlugin, DocumentsAndSourceMaps, Diagnostic } from '@volar/language-service'; +import type { LanguageServicePlugin, Diagnostic } from '@volar/language-service'; const plugin: LanguageServerPlugin = () => ({ extraFileExtensions: [{ extension: 'html', isMixedContent: true, scriptKind: 7 }], @@ -15,41 +15,35 @@ const plugin: LanguageServerPlugin = () => ({ } return []; }, - getServicePlugins(_host, service) { + getServicePlugins() { return [ createTsPlugin(), - createNgTemplateLsPlugin(service.context.documents), + ngTemplatePlugin, ]; }, }); -function createNgTemplateLsPlugin(docs: DocumentsAndSourceMaps): LanguageServicePlugin { +const ngTemplatePlugin: LanguageServicePlugin = (context) => ({ - return () => { + validation: { - return { + onSyntactic(document) { - validation: { + const file = context.documents.getVirtualFileByUri(document.uri); - onSyntactic(document) { - - const file = docs.getRootFileBySourceFileUri(document.uri); - - if (file instanceof HTMLTemplateFile) { - return (file.parsed.errors ?? []).map(error => ({ - range: { - start: { line: error.span.start.line, character: error.span.start.col }, - end: { line: error.span.end.line, character: error.span.end.col }, - }, - severity: error.level === 1 ? 1 : 2, - source: 'ng-template', - message: error.msg, - })); - } - }, + if (file instanceof HTMLTemplateFile) { + return (file.parsed.errors ?? []).map(error => ({ + range: { + start: { line: error.span.start.line, character: error.span.start.col }, + end: { line: error.span.end.line, character: error.span.end.col }, + }, + severity: error.level === 1 ? 1 : 2, + source: 'ng-template', + message: error.msg, + })); } - }; - }; -} + }, + } +}); createLanguageServer([plugin]); diff --git a/examples/vue-and-svelte-language-server/src/index.ts b/examples/vue-and-svelte-language-server/src/index.ts index 03a3c42cf..4fa84ab2d 100644 --- a/examples/vue-and-svelte-language-server/src/index.ts +++ b/examples/vue-and-svelte-language-server/src/index.ts @@ -3,7 +3,7 @@ import useTsPlugin from '@volar-plugins/typescript'; import { createLanguageServer, LanguageServerInitializationOptions, LanguageServerPlugin } from '@volar/language-server/node'; import * as vue from '@volar/vue-language-core'; -const plugin: LanguageServerPlugin = () => { +const plugin: LanguageServerPlugin = () => { return { extraFileExtensions: [ { extension: 'vue', isMixedContent: true, scriptKind: 7 }, diff --git a/packages/language-server/src/common/project.ts b/packages/language-server/src/common/project.ts index cf62234e5..cfbb6d233 100644 --- a/packages/language-server/src/common/project.ts +++ b/packages/language-server/src/common/project.ts @@ -85,7 +85,7 @@ export async function createProject(context: ProjectContext) { getPlugins() { return [ ...context.serverConfig?.plugins ?? [], - ...context.workspace.workspaces.plugins.map(plugin => plugin.getServicePlugins?.(languageServiceHost, languageService!) ?? []).flat(), + ...context.workspace.workspaces.plugins.map(plugin => plugin.getServicePlugins?.(languageServiceHost, languageServiceContext) ?? []).flat(), ]; }, env: { @@ -105,6 +105,7 @@ export async function createProject(context: ProjectContext) { }, }, documentRegistry: context.documentRegistry, + getLanguageService: () => languageService!, }); languageService = embeddedLS.createLanguageService(languageServiceContext); } diff --git a/packages/language-server/src/common/workspace.ts b/packages/language-server/src/common/workspace.ts index d3b230174..aeeba213c 100644 --- a/packages/language-server/src/common/workspace.ts +++ b/packages/language-server/src/common/workspace.ts @@ -131,7 +131,7 @@ export async function createWorkspace(context: WorkspaceContext) { return findTsconfig(async tsconfig => { const project = await projects.pathGet(tsconfig); const ls = project?.getLanguageServiceDontCreate(); - const validDoc = ls?.context.pluginContext.typescript?.languageService.getProgram()?.getSourceFile(shared.getPathOfUri(uri.toString())); + const validDoc = ls?.context.typescript?.languageService.getProgram()?.getSourceFile(shared.getPathOfUri(uri.toString())); return !!validDoc; }); } diff --git a/packages/language-server/src/types.ts b/packages/language-server/src/types.ts index 4a13a3b60..05d40d898 100644 --- a/packages/language-server/src/types.ts +++ b/packages/language-server/src/types.ts @@ -4,6 +4,7 @@ import type { FileSystemProvider } from 'vscode-html-languageservice'; import type * as ts from 'typescript/lib/tsserverlibrary'; import * as vscode from 'vscode-languageserver'; import { URI } from 'vscode-uri'; +import { LanguageServiceRuntimeContext } from '@volar/language-service'; export type FileSystemHost = { ready(connection: vscode.Connection): void, @@ -54,7 +55,7 @@ export type LanguageServerPlugin< getServicePlugins?( host: B, - service: embeddedLS.LanguageService, + context: LanguageServiceRuntimeContext, ): embeddedLS.LanguageServicePlugin[]; onInitialize?( diff --git a/packages/language-service/src/baseLanguageService.ts b/packages/language-service/src/baseLanguageService.ts index d5b28a669..a17831b38 100644 --- a/packages/language-service/src/baseLanguageService.ts +++ b/packages/language-service/src/baseLanguageService.ts @@ -26,7 +26,7 @@ import * as renamePrepare from './languageFeatures/renamePrepare'; import * as signatureHelp from './languageFeatures/signatureHelp'; import * as diagnostics from './languageFeatures/validation'; import * as workspaceSymbol from './languageFeatures/workspaceSymbols'; -import { LanguageServicePlugin, LanguageServicePluginInstance, LanguageServicePluginContext, LanguageServiceRuntimeContext } from './types'; +import { LanguageServicePlugin, LanguageServicePluginInstance, LanguageServiceRuntimeContext } from './types'; import type * as ts from 'typescript/lib/tsserverlibrary'; import * as colorPresentations from './documentFeatures/colorPresentations'; @@ -46,8 +46,9 @@ export function createLanguageServiceContext(options: { host: LanguageServiceHost, context: ReturnType, getPlugins(): (LanguageServicePlugin | LanguageServicePluginInstance)[], - env: LanguageServicePluginContext['env']; + env: LanguageServiceRuntimeContext['env']; documentRegistry: ts.DocumentRegistry | undefined, + getLanguageService: () => LanguageService, }) { const ts = options.host.getTypeScriptModule(); @@ -59,37 +60,35 @@ export function createLanguageServiceContext(options: { let plugins: LanguageServicePluginInstance[] | undefined; - const pluginContext: LanguageServicePluginContext = { - env: options.env, - typescript: ts && tsLs ? { - module: ts, - languageServiceHost: options.context.typescript.languageServiceHost, - languageService: tsLs, - } : undefined, - }; const textDocumentMapper = createDocumentsAndSourceMaps(options.context.virtualFiles); const documents = new WeakMap(); const documentVersions = new Map(); const context: LanguageServiceRuntimeContext = { host: options.host, core: options.context, + env: options.env, get plugins() { if (!plugins) { + plugins = []; // avoid infinite loop plugins = options.getPlugins().map(plugin => { if (plugin instanceof Function) { - const _plugin = plugin(pluginContext); - _plugin.setup?.(pluginContext); + const _plugin = plugin(this, options.getLanguageService()); + _plugin.setup?.(this); return _plugin; } else { - plugin.setup?.(pluginContext); + plugin.setup?.(this); return plugin; } }); } return plugins; }, - pluginContext, + typescript: ts && tsLs ? { + module: ts, + languageServiceHost: options.context.typescript.languageServiceHost, + languageService: tsLs, + } : undefined, documents: textDocumentMapper, getTextDocument, }; @@ -162,7 +161,7 @@ export function createLanguageService(context: LanguageServiceRuntimeContext) { doExecuteCommand: executeCommand.register(context), getInlayHints: inlayHints.register(context), callHierarchy: callHierarchy.register(context), - dispose: () => context.pluginContext.typescript?.languageService.dispose(), + dispose: () => context.typescript?.languageService.dispose(), context, }; } diff --git a/packages/language-service/src/documentFeatures/format.ts b/packages/language-service/src/documentFeatures/format.ts index 31c122225..9f56f3896 100644 --- a/packages/language-service/src/documentFeatures/format.ts +++ b/packages/language-service/src/documentFeatures/format.ts @@ -33,7 +33,7 @@ export function register(context: LanguageServiceRuntimeContext) { const originalSnapshot = source[0]; const rootVirtualFile = source[1]; const originalDocument = document; - const initialIndentLanguageId = await context.pluginContext.env.configurationHost?.getConfiguration>('volar.format.initialIndent') ?? { html: true }; + const initialIndentLanguageId = await context.env.configurationHost?.getConfiguration>('volar.format.initialIndent') ?? { html: true }; let level = 0; let edited = false; @@ -229,10 +229,10 @@ export function register(context: LanguageServiceRuntimeContext) { let edits: vscode.TextEdit[] | null | undefined; let recover: (() => void) | undefined; - if (formatDocument !== document && isTsDocument(formatDocument) && context.pluginContext.typescript) { + if (formatDocument !== document && isTsDocument(formatDocument) && context.typescript) { const formatFileName = shared.getPathOfUri(formatDocument.uri); const formatSnapshot = stringToSnapshot(formatDocument.getText()); - const host = context.pluginContext.typescript.languageServiceHost; + const host = context.typescript.languageServiceHost; const original = { getProjectVersion: host.getProjectVersion, getScriptVersion: host.getScriptVersion, diff --git a/packages/language-service/src/types.ts b/packages/language-service/src/types.ts index 4dd3c6c49..7aeda1ffa 100644 --- a/packages/language-service/src/types.ts +++ b/packages/language-service/src/types.ts @@ -6,19 +6,15 @@ import type * as vscode from 'vscode-languageserver-protocol'; import type { TextDocument } from 'vscode-languageserver-textdocument'; import { URI } from 'vscode-uri'; import { DocumentsAndSourceMaps } from './documents'; +import { LanguageService } from '@volar/language-service'; export * from 'vscode-languageserver-protocol'; -export interface LanguageServiceRuntimeContext { - host: LanguageServiceHost; +export interface LanguageServiceRuntimeContext { + host: Host; core: LanguageContext; documents: DocumentsAndSourceMaps; plugins: LanguageServicePluginInstance[]; - pluginContext: LanguageServicePluginContext; - getTextDocument(uri: string): TextDocument | undefined; -}; - -export interface LanguageServicePluginContext { typescript: { module: typeof import('typescript/lib/tsserverlibrary'); languageServiceHost: ts.LanguageServiceHost; @@ -30,8 +26,9 @@ export interface LanguageServicePluginContext { documentContext?: DocumentContext; fileSystemProvider?: FileSystemProvider; schemaRequestService?: SchemaRequestService; - }, -} + }; + getTextDocument(uri: string): TextDocument | undefined; +}; export interface ConfigurationHost { getConfiguration: ( (section: string, scopeUri?: string) => Promise), @@ -63,11 +60,11 @@ export interface ExecuteCommandContext { applyEdit(paramOrEdit: vscode.ApplyWorkspaceEditParams | vscode.WorkspaceEdit): Promise; } -export type LanguageServicePlugin = ((context: LanguageServicePluginContext) => LanguageServicePluginInstance & T); +export type LanguageServicePlugin = ((context: LanguageServiceRuntimeContext, service: LanguageService) => LanguageServicePluginInstance & T); export interface LanguageServicePluginInstance { - setup?(context: LanguageServicePluginContext): void; + setup?(context: LanguageServiceRuntimeContext): void; validation?: { onSemantic?(document: TextDocument): NullableResult; diff --git a/plugins/pug/src/index.ts b/plugins/pug/src/index.ts index 8833d0fbc..cf6e11156 100644 --- a/plugins/pug/src/index.ts +++ b/plugins/pug/src/index.ts @@ -9,10 +9,10 @@ const plugin: LanguageServicePlugin<{ updateCustomData(extraData: html.IHTMLDataProvider[]): void, getPugLs: () => pug.LanguageService, getPugDocument: (document: TextDocument) => pug.PugDocument | undefined, -}> = (context) => { +}> = (context, service) => { const pugDocuments = new WeakMap(); - const htmlPlugin = useHtmlPlugin()(context); + const htmlPlugin = useHtmlPlugin()(context, service); const pugLs = pug.getLanguageService(htmlPlugin.getHtmlLs()); return { diff --git a/vue-language-tools/typescript-vue-plugin/src/index.ts b/vue-language-tools/typescript-vue-plugin/src/index.ts index 50120ff6e..46f3d96df 100644 --- a/vue-language-tools/typescript-vue-plugin/src/index.ts +++ b/vue-language-tools/typescript-vue-plugin/src/index.ts @@ -37,7 +37,7 @@ const init: ts.server.PluginModuleFactory = (modules) => { return info.project.__vue_getScriptKind(fileName); }; - const vueTsLsHost: vue.LanguageServiceHost = { + const vueTsLsHost: vue.VueLanguageServiceHost = { getNewLine: () => info.project.getNewLine(), useCaseSensitiveFileNames: () => info.project.useCaseSensitiveFileNames(), readFile: path => info.project.readFile(path), diff --git a/vue-language-tools/vue-component-meta/src/index.ts b/vue-language-tools/vue-component-meta/src/index.ts index 1b2fe1dc3..c0b47a8e7 100644 --- a/vue-language-tools/vue-component-meta/src/index.ts +++ b/vue-language-tools/vue-component-meta/src/index.ts @@ -77,7 +77,7 @@ function createComponentMetaCheckerWorker( const scriptSnapshots = new Map(); const scriptVersions = new Map(); - const _host: vue.LanguageServiceHost = { + const _host: vue.VueLanguageServiceHost = { ...ts.sys, getProjectVersion: () => projectVersion.toString(), getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options), // should use ts.getDefaultLibFilePath not ts.getDefaultLibFileName @@ -126,7 +126,7 @@ function createComponentMetaCheckerWorker( } export function baseCreate( - _host: vue.LanguageServiceHost, + _host: vue.VueLanguageServiceHost, checkerOptions: MetaCheckerOptions, globalComponentName: string, ts: typeof import('typescript/lib/tsserverlibrary'), @@ -136,7 +136,7 @@ export function baseCreate( */ const globalComponentSnapshot = ts.ScriptSnapshot.fromString(''); const metaSnapshots: Record = {}; - const host = new Proxy>({ + const host = new Proxy>({ getScriptFileNames: () => { const names = _host.getScriptFileNames(); return [ @@ -167,7 +167,7 @@ export function baseCreate( } return _host[prop as keyof typeof _host]; }, - }) as vue.LanguageServiceHost; + }) as vue.VueLanguageServiceHost; const vueLanguageModules = ts ? vue.createLanguageModules( ts, host.getCompilationSettings(), diff --git a/vue-language-tools/vue-language-core/src/types.ts b/vue-language-tools/vue-language-core/src/types.ts index 7d03f3cff..ea27f2fb6 100644 --- a/vue-language-tools/vue-language-core/src/types.ts +++ b/vue-language-tools/vue-language-core/src/types.ts @@ -7,7 +7,7 @@ import { VueEmbeddedFile } from './sourceFile'; export type { SFCParseResult } from '@vue/compiler-sfc'; -export type LanguageServiceHost = embedded.LanguageServiceHost & { +export type VueLanguageServiceHost = embedded.LanguageServiceHost & { getVueCompilationSettings(): VueCompilerOptions, }; diff --git a/vue-language-tools/vue-language-server/src/languageServerPlugin.ts b/vue-language-tools/vue-language-server/src/languageServerPlugin.ts index 41a6ef88b..cba97c70c 100644 --- a/vue-language-tools/vue-language-server/src/languageServerPlugin.ts +++ b/vue-language-tools/vue-language-server/src/languageServerPlugin.ts @@ -9,7 +9,7 @@ import { VueServerInitializationOptions } from './types'; import type * as ts from 'typescript/lib/tsserverlibrary'; import * as meta from 'vue-component-meta'; -const plugin: LanguageServerPlugin = (initOptions) => { +const plugin: LanguageServerPlugin = (initOptions) => { const extraFileExtensions: ts.FileExtensionInfo[] = [{ extension: 'vue', isMixedContent: true, scriptKind: 7 }]; @@ -52,7 +52,7 @@ const plugin: LanguageServerPlugin { const languageService = await getService(params.uri); - const host = languageService.context.host as vue.LanguageServiceHost; + const host = languageService.context.host as vue.VueLanguageServiceHost; return host.getVueCompilationSettings?.(); }); connection.onRequest(DetectNameCasingRequest.type, async params => { const languageService = await getService(params.textDocument.uri); - if (languageService.context.pluginContext.typescript) { - return nameCasing.detect(languageService.context, languageService.context.pluginContext.typescript, params.textDocument.uri); + if (languageService.context.typescript) { + return nameCasing.detect(languageService.context, languageService.context.typescript, params.textDocument.uri); } }); connection.onRequest(GetConvertTagCasingEditsRequest.type, async params => { const languageService = await getService(params.textDocument.uri); - if (languageService.context.pluginContext.typescript) { - return nameCasing.convertTagName(languageService.context, languageService.context.pluginContext.typescript, params.textDocument.uri, params.casing); + if (languageService.context.typescript) { + return nameCasing.convertTagName(languageService.context, languageService.context.typescript, params.textDocument.uri, params.casing); } }); connection.onRequest(GetConvertAttrCasingEditsRequest.type, async params => { const languageService = await getService(params.textDocument.uri); - if (languageService.context.pluginContext.typescript) { - return nameCasing.convertAttrName(languageService.context, languageService.context.pluginContext.typescript, params.textDocument.uri, params.casing); + if (languageService.context.typescript) { + return nameCasing.convertAttrName(languageService.context, languageService.context.typescript, params.textDocument.uri, params.casing); } }); @@ -104,16 +104,16 @@ const plugin: LanguageServerPlugin { const languageService = await getService(params.uri); - if (!languageService.context.pluginContext.typescript) + if (!languageService.context.typescript) return; let checker = checkers.get(languageService.context.host); if (!checker) { checker = meta.baseCreate( - languageService.context.host as vue.LanguageServiceHost, + languageService.context.host as vue.VueLanguageServiceHost, {}, languageService.context.host.getCurrentDirectory() + '/tsconfig.json.global.vue', - languageService.context.pluginContext.typescript.module, + languageService.context.typescript.module, ); checkers.set(languageService.context.host, checker); } diff --git a/vue-language-tools/vue-language-service/src/ideFeatures/nameCasing.ts b/vue-language-tools/vue-language-service/src/ideFeatures/nameCasing.ts index df5356774..6537b6989 100644 --- a/vue-language-tools/vue-language-service/src/ideFeatures/nameCasing.ts +++ b/vue-language-tools/vue-language-service/src/ideFeatures/nameCasing.ts @@ -7,7 +7,7 @@ import { AttrNameCasing, TagNameCasing } from '../types'; export async function convertTagName( context: LanguageServiceRuntimeContext, - _ts: NonNullable, + _ts: NonNullable, uri: string, casing: TagNameCasing, ) { @@ -48,7 +48,7 @@ export async function convertTagName( export async function convertAttrName( context: LanguageServiceRuntimeContext, - _ts: NonNullable, + _ts: NonNullable, uri: string, casing: AttrNameCasing, ) { @@ -95,7 +95,7 @@ export async function convertAttrName( export function detect( context: LanguageServiceRuntimeContext, - _ts: NonNullable, + _ts: NonNullable, uri: string, ): { tag: TagNameCasing[], diff --git a/vue-language-tools/vue-language-service/src/languageService.ts b/vue-language-tools/vue-language-service/src/languageService.ts index dc1e974de..aec461b74 100644 --- a/vue-language-tools/vue-language-service/src/languageService.ts +++ b/vue-language-tools/vue-language-service/src/languageService.ts @@ -8,7 +8,7 @@ import useTsTqPlugin from '@volar-plugins/typescript-twoslash-queries'; import * as embedded from '@volar/language-core'; import * as embeddedLS from '@volar/language-service'; import * as vue from '@volar/vue-language-core'; -import { LanguageServiceHost, VueFile } from '@volar/vue-language-core'; +import { VueLanguageServiceHost } from '@volar/vue-language-core'; import type * as html from 'vscode-html-languageservice'; import * as vscode from 'vscode-languageserver-protocol'; import useVuePlugin from './plugins/vue'; @@ -21,25 +21,22 @@ import useTwoslashQueries from './plugins/vue-twoslash-queries'; import useVueTemplateLanguagePlugin from './plugins/vue-template'; import type { Data } from '@volar-plugins/typescript/src/services/completions/basic'; import type * as ts from 'typescript/lib/tsserverlibrary'; +import { LanguageServicePlugin } from '@volar/language-service'; export interface Settings { json?: Parameters[0]; } -export function getLanguageServicePlugins( - host: vue.LanguageServiceHost, - apis: embeddedLS.LanguageService, - settings?: Settings, -): embeddedLS.LanguageServicePlugin[] { +export function getLanguageServicePlugins(settings?: Settings) { // plugins - const tsPlugin: embeddedLS.LanguageServicePlugin = (context) => { + const tsPlugin: embeddedLS.LanguageServicePlugin = (_context, service) => { - if (!context.typescript) + if (!_context.typescript) return {}; - const ts = context.typescript.module; - const base = createTsPlugin()(context); + const ts = _context.typescript.module; + const base = createTsPlugin()(_context, service); const autoImportPositions = new WeakSet(); return { @@ -53,8 +50,8 @@ export function getLanguageServicePlugins( async on(document, position, context) { const result = await base.complete!.on!(document, position, context); if (result) { - for (const [_, map] of apis.context.documents.getMapsByVirtualFileUri(document.uri)) { - const virtualFile = apis.context.documents.getRootFileBySourceFileUri(map.sourceFileDocument.uri); + for (const [_, map] of _context.documents.getMapsByVirtualFileUri(document.uri)) { + const virtualFile = _context.documents.getRootFileBySourceFileUri(map.sourceFileDocument.uri); if (virtualFile instanceof vue.VueFile) { if (map.toSourcePosition(position, data => typeof data.completion === 'object' && !!data.completion.autoImportOnly)) { result.items.forEach(item => { @@ -72,7 +69,7 @@ export function getLanguageServicePlugins( if ( item.textEdit?.newText && /\w*Vue$/.test(item.textEdit.newText) && item.additionalTextEdits?.length === 1 && item.additionalTextEdits[0].newText.indexOf('import ' + item.textEdit.newText + ' from ') >= 0 - && (await context.env.configurationHost?.getConfiguration('volar.completion.normalizeComponentAutoImportName') ?? true) + && (await _context.env.configurationHost?.getConfiguration('volar.completion.normalizeComponentAutoImportName') ?? true) ) { let newName = item.textEdit.newText.slice(0, -'Vue'.length); newName = newName[0].toUpperCase() + newName.substring(1); @@ -85,12 +82,12 @@ export function getLanguageServicePlugins( const data: Data = item.data; if (item.data?.__isComponentAutoImport && data && item.additionalTextEdits?.length && item.textEdit) { - for (const [_, map] of apis.context.documents.getMapsByVirtualFileUri(data.uri)) { - const virtualFile = apis.context.documents.getRootFileBySourceFileUri(map.sourceFileDocument.uri); + for (const [_, map] of _context.documents.getMapsByVirtualFileUri(data.uri)) { + const virtualFile = _context.documents.getRootFileBySourceFileUri(map.sourceFileDocument.uri); if (virtualFile instanceof vue.VueFile) { const sfc = virtualFile.sfc; const componentName = item.textEdit.newText; - const textDoc = apis.context.documents.getDocumentByFileName(virtualFile.snapshot, virtualFile.fileName); + const textDoc = _context.documents.getDocumentByFileName(virtualFile.snapshot, virtualFile.fileName); if (sfc.scriptAst && sfc.script) { const _scriptRanges = vue.scriptRanges.parseScriptRanges(ts, sfc.scriptAst, !!sfc.scriptSetup, true); const exportDefault = _scriptRanges.exportDefault; @@ -148,39 +145,15 @@ export function getLanguageServicePlugins( }, }; }; - const vuePlugin = useVuePlugin({ - getVueFile(document) { - const virtualFile = apis.context.documents.getVirtualFileByUri(document.uri); - if (virtualFile instanceof VueFile) { - return virtualFile; - } - } - }); + const vuePlugin = useVuePlugin(); const cssPlugin = useCssPlugin(); const jsonPlugin = useJsonPlugin(settings?.json); const emmetPlugin = useEmmetPlugin(); const autoDotValuePlugin = useAutoDotValuePlugin(); - const referencesCodeLensPlugin = useReferencesCodeLensPlugin({ - documents: apis.context.documents, - findReference: apis.findReferences, - }); - const htmlPugConversionsPlugin = useHtmlPugConversionsPlugin({ - documents: apis.context.documents, - }); - const scriptSetupConversionsPlugin = useScriptSetupConversionsPlugin({ - documents: apis.context.documents, - doCodeActions: apis.doCodeActions, - doCodeActionResolve: apis.doCodeActionResolve, - }); - const refSugarConversionsPlugin = useRefSugarConversionsPlugin({ - documents: apis.context.documents, - doCodeActions: apis.doCodeActions, - doCodeActionResolve: apis.doCodeActionResolve, - findReferences: apis.findReferences, - doValidation: apis.doValidation, - doRename: apis.doRename, - findTypeDefinition: apis.findTypeDefinition, - }); + const referencesCodeLensPlugin = useReferencesCodeLensPlugin(); + const htmlPugConversionsPlugin = useHtmlPugConversionsPlugin(); + const scriptSetupConversionsPlugin = useScriptSetupConversionsPlugin(); + const refSugarConversionsPlugin = useRefSugarConversionsPlugin(); // template plugins const htmlPlugin = useVueTemplateLanguagePlugin({ @@ -189,8 +162,6 @@ export function getLanguageServicePlugins( return htmlPlugin.getHtmlLs().createScanner(document.getText()); }, isSupportedDocument: (document) => document.languageId === 'html', - vueLsHost: host, - context: apis.context, }); const pugPlugin = useVueTemplateLanguagePlugin({ templateLanguagePlugin: usePugPlugin(), @@ -201,13 +172,9 @@ export function getLanguageServicePlugins( } }, isSupportedDocument: (document) => document.languageId === 'jade', - vueLsHost: host, - context: apis.context, }); const tsTwoslashQueriesPlugin = useTsTqPlugin(); - const vueTwoslashQueriesPlugin = useTwoslashQueries({ - documents: apis.context.documents, - }); + const vueTwoslashQueriesPlugin = useTwoslashQueries(); return [ vuePlugin, @@ -225,12 +192,12 @@ export function getLanguageServicePlugins( vueTwoslashQueriesPlugin, // put emmet plugin at last to fix https://github.com/johnsoncodehk/volar/issues/1088 emmetPlugin, - ]; + ] as LanguageServicePlugin[]; } export function createLanguageService( - host: LanguageServiceHost, - env: embeddedLS.LanguageServicePluginContext['env'], + host: VueLanguageServiceHost, + env: embeddedLS.LanguageServiceRuntimeContext['env'], documentRegistry?: ts.DocumentRegistry, settings?: Settings, ) { @@ -246,10 +213,9 @@ export function createLanguageService( env, host, context: core, - getPlugins() { - return getLanguageServicePlugins(host, languageService, settings); - }, documentRegistry, + getPlugins: () => getLanguageServicePlugins(settings), + getLanguageService: () => languageService, }); const languageService = embeddedLS.createLanguageService(languageServiceContext); diff --git a/vue-language-tools/vue-language-service/src/plugins/empty.ts b/vue-language-tools/vue-language-service/src/plugins/empty.ts index adbcc39f1..03e232fd9 100644 --- a/vue-language-tools/vue-language-service/src/plugins/empty.ts +++ b/vue-language-tools/vue-language-service/src/plugins/empty.ts @@ -1,6 +1,6 @@ -import { LanguageServicePlugin } from '@volar/language-service'; +import { VueLanguageServicePlugin } from '../types'; -export default function (): LanguageServicePlugin { +export default function (): VueLanguageServicePlugin { return () => ({}); } diff --git a/vue-language-tools/vue-language-service/src/plugins/vue-autoinsert-dotvalue.ts b/vue-language-tools/vue-language-service/src/plugins/vue-autoinsert-dotvalue.ts index 2da09de92..7a23cc641 100644 --- a/vue-language-tools/vue-language-service/src/plugins/vue-autoinsert-dotvalue.ts +++ b/vue-language-tools/vue-language-service/src/plugins/vue-autoinsert-dotvalue.ts @@ -4,9 +4,10 @@ import * as shared from '@volar/shared'; import type * as ts from 'typescript/lib/tsserverlibrary'; import { hyphenate } from '@vue/shared'; import { isTsDocument } from '@volar-plugins/typescript'; -import { LanguageServicePlugin, LanguageServicePluginInstance } from '@volar/language-service'; +import { LanguageServicePluginInstance } from '@volar/language-service'; +import { VueLanguageServicePlugin } from '../types'; -const plugin: LanguageServicePlugin = (context) => { +const plugin: VueLanguageServicePlugin = (context) => { if (!context.typescript) return {}; diff --git a/vue-language-tools/vue-language-service/src/plugins/vue-autoinsert-parentheses.ts b/vue-language-tools/vue-language-service/src/plugins/vue-autoinsert-parentheses.ts index 14a11387e..1ed589bb9 100644 --- a/vue-language-tools/vue-language-service/src/plugins/vue-autoinsert-parentheses.ts +++ b/vue-language-tools/vue-language-service/src/plugins/vue-autoinsert-parentheses.ts @@ -1,96 +1,90 @@ import * as vscode from 'vscode-languageserver-protocol'; -import { LanguageServicePlugin } from '@volar/language-service'; import { isCharacterTyping } from './vue-autoinsert-dotvalue'; import * as embedded from '@volar/language-core'; import { VirtualFile } from '@volar/language-core'; import { VueFile } from '@volar/vue-language-core'; -import { TextDocument } from 'vscode-languageserver-textdocument'; +import { VueLanguageServicePlugin } from '../types'; -export default function (options: { - getVueFile: (document: TextDocument) => VueFile | undefined, -}): LanguageServicePlugin { +const plugin: VueLanguageServicePlugin = (context) => { + if (!context.typescript) { + return {}; + } - return (context) => { + const ts = context.typescript.module; - if (!context.typescript) { - return {}; - } + return { - const ts = context.typescript.module; + async doAutoInsert(document, position, options_2) { - return { + const enabled = await context.env.configurationHost?.getConfiguration('volar.autoWrapParentheses') ?? false; + if (!enabled) + return; - async doAutoInsert(document, position, options_2) { + if (!isCharacterTyping(document, options_2)) + return; - const enabled = await context.env.configurationHost?.getConfiguration('volar.autoWrapParentheses') ?? false; - if (!enabled) - return; + const vueFile = context.documents.getVirtualFileByUri(document.uri); + if (!(vueFile instanceof VueFile)) + return; - if (!isCharacterTyping(document, options_2)) - return; + let templateFormatScript: VirtualFile | undefined; - const vueFile = options.getVueFile(document); - if (!vueFile) - return; - - - let templateFormatScript: VirtualFile | undefined; - - embedded.forEachEmbeddedFile(vueFile, embedded => { - if (embedded.fileName.endsWith('.__VLS_template_format.ts')) { - templateFormatScript = embedded; - } - }); - - if (!templateFormatScript) - return; - - const offset = document.offsetAt(position); - - for (const mappedRange of templateFormatScript.mappings) { - if (mappedRange.sourceRange[1] === offset) { - const text = document.getText().substring(mappedRange.sourceRange[0], mappedRange.sourceRange[1]); - const ast = ts.createSourceFile(templateFormatScript.fileName, text, ts.ScriptTarget.Latest); - if (ast.statements.length === 1) { - const statement = ast.statements[0]; - if ( - ts.isExpressionStatement(statement) - && ( - ( - ts.isAsExpression(statement.expression) - && ts.isTypeReferenceNode(statement.expression.type) - && ts.isIdentifier(statement.expression.type.typeName) - && statement.expression.type.typeName.text - ) - || ( - ts.isBinaryExpression(statement.expression) - && statement.expression.right.getText(ast) - && statement.expression.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword - ) - || ( - ts.isTypeOfExpression(statement.expression) - && statement.expression.expression.getText(ast) - ) + embedded.forEachEmbeddedFile(vueFile, embedded => { + if (embedded.fileName.endsWith('.__VLS_template_format.ts')) { + templateFormatScript = embedded; + } + }); + + if (!templateFormatScript) + return; + + const offset = document.offsetAt(position); + + for (const mappedRange of templateFormatScript.mappings) { + if (mappedRange.sourceRange[1] === offset) { + const text = document.getText().substring(mappedRange.sourceRange[0], mappedRange.sourceRange[1]); + const ast = ts.createSourceFile(templateFormatScript.fileName, text, ts.ScriptTarget.Latest); + if (ast.statements.length === 1) { + const statement = ast.statements[0]; + if ( + ts.isExpressionStatement(statement) + && ( + ( + ts.isAsExpression(statement.expression) + && ts.isTypeReferenceNode(statement.expression.type) + && ts.isIdentifier(statement.expression.type.typeName) + && statement.expression.type.typeName.text + ) + || ( + ts.isBinaryExpression(statement.expression) + && statement.expression.right.getText(ast) + && statement.expression.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword ) - ) { - // https://code.visualstudio.com/docs/editor/userdefinedsnippets#_grammar - const escapedText = text - .replaceAll('\\', '\\\\') - .replaceAll('$', '\\$') - .replaceAll('}', '\\}'); - return vscode.TextEdit.replace( - { - start: document.positionAt(mappedRange.sourceRange[0]), - end: document.positionAt(mappedRange.sourceRange[1]), - }, - '(' + escapedText + '$0' + ')', - ); - } + || ( + ts.isTypeOfExpression(statement.expression) + && statement.expression.expression.getText(ast) + ) + ) + ) { + // https://code.visualstudio.com/docs/editor/userdefinedsnippets#_grammar + const escapedText = text + .replaceAll('\\', '\\\\') + .replaceAll('$', '\\$') + .replaceAll('}', '\\}'); + return vscode.TextEdit.replace( + { + start: document.positionAt(mappedRange.sourceRange[0]), + end: document.positionAt(mappedRange.sourceRange[1]), + }, + '(' + escapedText + '$0' + ')', + ); } } } - }, - }; + } + }, }; -} +}; + +export default () => plugin; diff --git a/vue-language-tools/vue-language-service/src/plugins/vue-autoinsert-space.ts b/vue-language-tools/vue-language-service/src/plugins/vue-autoinsert-space.ts index 244e4d926..b4020c55e 100644 --- a/vue-language-tools/vue-language-service/src/plugins/vue-autoinsert-space.ts +++ b/vue-language-tools/vue-language-service/src/plugins/vue-autoinsert-space.ts @@ -1,6 +1,6 @@ -import { LanguageServicePlugin } from '@volar/language-service'; +import { VueLanguageServicePlugin } from '../types'; -const plugin: LanguageServicePlugin = (context) => { +const plugin: VueLanguageServicePlugin = (context) => { return { diff --git a/vue-language-tools/vue-language-service/src/plugins/vue-codelens-references.ts b/vue-language-tools/vue-language-service/src/plugins/vue-codelens-references.ts index b65ea98c6..4d558299d 100644 --- a/vue-language-tools/vue-language-service/src/plugins/vue-codelens-references.ts +++ b/vue-language-tools/vue-language-service/src/plugins/vue-codelens-references.ts @@ -1,6 +1,6 @@ import * as vscode from 'vscode-languageserver-protocol'; -import { LanguageServicePlugin, DocumentsAndSourceMaps } from '@volar/language-service'; import { VueFile } from '@volar/vue-language-core'; +import { VueLanguageServicePlugin } from '../types'; const showReferencesCommand = 'volar.show-references'; @@ -13,12 +13,9 @@ export interface ReferencesCodeLensData { position: vscode.Position, } -export default function (options: { - documents: DocumentsAndSourceMaps, - findReference(uri: string, position: vscode.Position): Promise, -}): LanguageServicePlugin { +export default function (): VueLanguageServicePlugin { - return (context) => { + return (context, service) => { return { @@ -34,7 +31,7 @@ export default function (options: { const result: vscode.CodeLens[] = []; - for (const [_, map] of options.documents.getMapsBySourceFileUri(document.uri)?.maps ?? []) { + for (const [_, map] of context.documents.getMapsBySourceFileUri(document.uri)?.maps ?? []) { for (const mapping of map.map.mappings) { if (!mapping.data.referencesCodeLens) @@ -63,7 +60,7 @@ export default function (options: { await worker(data.uri, async (vueFile) => { - const document = options.documents.getDocumentByFileName(vueFile.snapshot, vueFile.fileName); + const document = context.documents.getDocumentByFileName(vueFile.snapshot, vueFile.fileName); const offset = document.offsetAt(data.position); const blocks = [ vueFile.sfc.script, @@ -72,7 +69,7 @@ export default function (options: { ...vueFile.sfc.styles, ...vueFile.sfc.customBlocks, ]; - const allRefs = await options.findReference(data.uri, data.position) ?? []; + const allRefs = await service.findReferences?.(data.uri, data.position) ?? []; const sourceBlock = blocks.find(block => block && offset >= block.startTagEnd && offset <= block.endTagStart); const diffDocRefs = allRefs.filter(reference => reference.uri !== data.uri // different file @@ -107,7 +104,7 @@ export default function (options: { function worker(uri: string, callback: (vueSourceFile: VueFile) => T) { - const virtualFile = options.documents.getVirtualFileByUri(uri); + const virtualFile = context.documents.getVirtualFileByUri(uri); if (!(virtualFile instanceof VueFile)) return; diff --git a/vue-language-tools/vue-language-service/src/plugins/vue-convert-htmlpug.ts b/vue-language-tools/vue-language-service/src/plugins/vue-convert-htmlpug.ts index 730347c99..d97925a71 100644 --- a/vue-language-tools/vue-language-service/src/plugins/vue-convert-htmlpug.ts +++ b/vue-language-tools/vue-language-service/src/plugins/vue-convert-htmlpug.ts @@ -1,7 +1,7 @@ import * as vscode from 'vscode-languageserver-protocol'; -import { LanguageServicePlugin, DocumentsAndSourceMaps } from '@volar/language-service'; import { htmlToPug, pugToHtml } from '@johnsoncodehk/html2pug'; import * as vue from '@volar/vue-language-core'; +import { VueLanguageServicePlugin } from '../types'; const toggleConvertCommand = 'htmlPugConversions.toggle'; @@ -12,95 +12,92 @@ export interface ReferencesCodeLensData { type CommandArgs = [string]; -export default function (options: { - documents: DocumentsAndSourceMaps, -}): LanguageServicePlugin { +const plugin: VueLanguageServicePlugin = (context) => { - return (context) => { + return { - return { + codeLens: { - codeLens: { + on(document) { + return worker(document.uri, async (vueSourceFile) => { - on(document) { - return worker(document.uri, async (vueSourceFile) => { + const isEnabled = await context.env.configurationHost?.getConfiguration('volar.codeLens.pugTools') ?? true; - const isEnabled = await context.env.configurationHost?.getConfiguration('volar.codeLens.pugTools') ?? true; + if (!isEnabled) + return; - if (!isEnabled) - return; + const descriptor = vueSourceFile.sfc; - const descriptor = vueSourceFile.sfc; + if (descriptor.template && (descriptor.template.lang === 'html' || descriptor.template.lang === 'pug')) { - if (descriptor.template && (descriptor.template.lang === 'html' || descriptor.template.lang === 'pug')) { - - return [{ - range: { - start: document.positionAt(descriptor.template.start), - end: document.positionAt(descriptor.template.startTagEnd), - }, - command: { - title: 'pug ' + (descriptor.template.lang === 'pug' ? '☑' : '☐'), - command: toggleConvertCommand, - arguments: [document.uri], - }, - }]; - } - }); - }, + return [{ + range: { + start: document.positionAt(descriptor.template.start), + end: document.positionAt(descriptor.template.startTagEnd), + }, + command: { + title: 'pug ' + (descriptor.template.lang === 'pug' ? '☑' : '☐'), + command: toggleConvertCommand, + arguments: [document.uri], + }, + }]; + } + }); }, + }, - doExecuteCommand(command, args, host) { + doExecuteCommand(command, args, host) { - if (command === toggleConvertCommand) { + if (command === toggleConvertCommand) { - const [uri] = args as CommandArgs; + const [uri] = args as CommandArgs; - return worker(uri, (vueFile) => { + return worker(uri, (vueFile) => { - const document = options.documents.getDocumentByFileName(vueFile.snapshot, vueFile.fileName); - const desc = vueFile.sfc; - if (!desc.template) - return; + const document = context.documents.getDocumentByFileName(vueFile.snapshot, vueFile.fileName); + const desc = vueFile.sfc; + if (!desc.template) + return; - const lang = desc.template.lang; + const lang = desc.template.lang; - if (lang === 'html') { + if (lang === 'html') { - const pug = htmlToPug(desc.template.content) + '\n'; - const newTemplate = `