diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json index b7f119a812..9566246842 100644 --- a/extensions/vscode/package.json +++ b/extensions/vscode/package.json @@ -90,10 +90,7 @@ { "name": "typescript-vue-plugin-bundle", "enableForWorkspaceTypeScriptVersions": true, - "configNamespace": "typescript", - "languages": [ - "vue" - ] + "configNamespace": "typescript" } ], "grammars": [ @@ -236,32 +233,20 @@ ], "description": "Vue language server only handles CSS and HTML language support, and tsserver takes over TS language support via TS plugin." }, + "vue.server.includeLanguages": { + "type": "array", + "items": { + "type": "string" + }, + "default": [ + "vue" + ] + }, "vue.server.maxFileSize": { "type": "number", "default": 20971520, "description": "Maximum file size for Vue Language Server to load. (default: 20MB)" }, - "vue.server.petiteVue.supportHtmlFile": { - "type": "boolean", - "default": false - }, - "vue.server.vitePress.supportMdFile": { - "type": "boolean", - "default": false - }, - "vue.server.diagnosticModel": { - "type": "string", - "default": "push", - "enum": [ - "push", - "pull" - ], - "enumDescriptions": [ - "Diagnostic push by language server.", - "Diagnostic pull by language client." - ], - "description": "Diagnostic update model." - }, "vue.server.maxOldSpaceSize": { "type": [ "number", @@ -270,14 +255,6 @@ "default": null, "description": "Set --max-old-space-size option on server process. If you have problem on frequently \"Request textDocument/** failed.\" error, try setting higher memory(MB) on it." }, - "vue.server.additionalExtensions": { - "type": "array", - "items": { - "type": "string" - }, - "default": [], - "description": "List any additional file extensions that should be processed as Vue files (requires restart)." - }, "vue.doctor.status": { "type": "boolean", "default": true, @@ -290,6 +267,9 @@ }, "vue.splitEditors.layout.left": { "type": "array", + "items": { + "type": "string" + }, "default": [ "script", "scriptSetup", @@ -298,6 +278,9 @@ }, "vue.splitEditors.layout.right": { "type": "array", + "items": { + "type": "string" + }, "default": [ "template", "customBlocks" diff --git a/extensions/vscode/src/common.ts b/extensions/vscode/src/common.ts index a9e74319bf..ffbb8312b1 100644 --- a/extensions/vscode/src/common.ts +++ b/extensions/vscode/src/common.ts @@ -15,7 +15,7 @@ let client: lsp.BaseLanguageClient; type CreateLanguageClient = ( id: string, name: string, - langs: lsp.DocumentFilter[], + langs: lsp.DocumentSelector, initOptions: VueInitializationOptions, port: number, outputChannel: vscode.OutputChannel, @@ -27,11 +27,7 @@ export async function activate(context: vscode.ExtensionContext, createLc: Creat tryActivate(); function tryActivate() { - if ( - vscode.window.visibleTextEditors.some(editor => editor.document.languageId === 'vue') - || (config.server.vitePress.supportMdFile && vscode.window.visibleTextEditors.some(editor => editor.document.languageId === 'markdown')) - || (config.server.petiteVue.supportHtmlFile && vscode.window.visibleTextEditors.some(editor => editor.document.languageId === 'html')) - ) { + if (vscode.window.visibleTextEditors.some(editor => config.server.includeLanguages.includes(editor.document.languageId))) { doActivate(context, createLc); stopCheck.dispose(); } @@ -210,30 +206,22 @@ function getCurrentHybridModeStatus(report = false) { async function doActivate(context: vscode.ExtensionContext, createLc: CreateLanguageClient) { + vscode.commands.executeCommand('setContext', 'vue.activated', true); + getCurrentHybridModeStatus(true); const outputChannel = vscode.window.createOutputChannel('Vue Language Server'); - - vscode.commands.executeCommand('setContext', 'vue.activated', true); + const selectors = config.server.includeLanguages; client = createLc( 'vue', 'Vue', - getDocumentSelector(), + selectors, await getInitializationOptions(context, enabledHybridMode), 6009, outputChannel ); - const selectors: vscode.DocumentFilter[] = [{ language: 'vue' }]; - - if (config.server.petiteVue.supportHtmlFile) { - selectors.push({ language: 'html' }); - } - if (config.server.vitePress.supportMdFile) { - selectors.push({ language: 'markdown' }); - } - activateConfigWatcher(); activateRestartRequest(); @@ -410,6 +398,16 @@ async function doActivate(context: vscode.ExtensionContext, createLc: CreateLang ); } } + else if (e.affectsConfiguration('vue.server')) { + if (enabledHybridMode) { + if (e.affectsConfiguration('vue.server.includeLanguages')) { + requestReloadVscode('Please reload VSCode to apply the new language settings.'); + } + } + else { + vscode.commands.executeCommand('vue.action.restartServer', false); + } + } else if (e.affectsConfiguration('vue')) { vscode.commands.executeCommand('vue.action.restartServer', false); } @@ -434,25 +432,12 @@ export function deactivate(): Thenable | undefined { return client?.stop(); } -export function getDocumentSelector(): lsp.DocumentFilter[] { - const selectors: lsp.DocumentFilter[] = []; - selectors.push({ language: 'vue' }); - if (config.server.petiteVue.supportHtmlFile) { - selectors.push({ language: 'html' }); - } - if (config.server.vitePress.supportMdFile) { - selectors.push({ language: 'markdown' }); - } - return selectors; -} - async function getInitializationOptions( context: vscode.ExtensionContext, hybridMode: boolean, ): Promise { return { - // volar - diagnosticModel: config.server.diagnosticModel === 'pull' ? DiagnosticModel.Pull : DiagnosticModel.Push, + diagnosticModel: enabledHybridMode ? DiagnosticModel.Pull : DiagnosticModel.Push, typescript: { tsdk: (await lsp.getTsdk(context)).tsdk }, maxFileSize: config.server.maxFileSize, semanticTokensLegend: { @@ -461,11 +446,6 @@ async function getInitializationOptions( }, vue: { hybridMode, - additionalExtensions: [ - ...config.server.additionalExtensions, - ...!config.server.petiteVue.supportHtmlFile ? [] : ['html'], - ...!config.server.vitePress.supportMdFile ? [] : ['md'], - ], }, }; }; diff --git a/extensions/vscode/src/config.ts b/extensions/vscode/src/config.ts index 5a2848c3fa..bd2846b50b 100644 --- a/extensions/vscode/src/config.ts +++ b/extensions/vscode/src/config.ts @@ -16,17 +16,10 @@ export const config = { return _config().get('doctor')!; }, get server(): Readonly<{ + includeLanguages: string[]; hybridMode: 'auto' | 'typeScriptPluginOnly' | boolean; maxOldSpaceSize: number; maxFileSize: number; - diagnosticModel: 'push' | 'pull'; - additionalExtensions: string[]; - vitePress: { - supportMdFile: boolean; - }; - petiteVue: { - supportHtmlFile: boolean; - }; }> { return _config().get('server')!; }, diff --git a/extensions/vscode/src/features/doctor.ts b/extensions/vscode/src/features/doctor.ts index 4a4e5b34b9..9f7e46be2c 100644 --- a/extensions/vscode/src/features/doctor.ts +++ b/extensions/vscode/src/features/doctor.ts @@ -231,9 +231,20 @@ export async function register(context: vscode.ExtensionContext, client: BaseLan }); } - // check outdated vue language plugins - // check node_modules has more than one vue versions - // check ESLint, Prettier... + if ( + vscode.workspace.getConfiguration('vue').has('server.additionalExtensions') + || vscode.workspace.getConfiguration('vue').has('server.petiteVue.supportHtmlFile') + || vscode.workspace.getConfiguration('vue').has('server.vitePress.supportMdFile') + ) { + problems.push({ + title: 'Deprecated configuration', + message: [ + '`vue.server.additionalExtensions`, `vue.server.petiteVue.supportHtmlFile`, and `vue.server.vitePress.supportMdFile` are deprecated. Please remove them from your settings.', + '', + '- PR: https://github.com/vuejs/language-tools/pull/4321', + ].join('\n'), + }); + } return problems; } diff --git a/extensions/vscode/src/features/nameCasing.ts b/extensions/vscode/src/features/nameCasing.ts index 89e0d4b974..e3ddc41fdc 100644 --- a/extensions/vscode/src/features/nameCasing.ts +++ b/extensions/vscode/src/features/nameCasing.ts @@ -136,11 +136,7 @@ export async function activate(_context: vscode.ExtensionContext, client: BaseLa } async function update(document: vscode.TextDocument | undefined) { - if ( - document?.languageId === 'vue' - || (config.server.vitePress.supportMdFile && document?.languageId === 'markdown') - || (config.server.petiteVue.supportHtmlFile && document?.languageId === 'html') - ) { + if (document && vscode.languages.match(selector, document)) { let detected: Awaited> | undefined; let attrNameCasing = attrNameCasings.get(document.uri.toString()); let tagNameCasing = tagNameCasings.get(document.uri.toString()); diff --git a/extensions/vscode/src/nodeClientMain.ts b/extensions/vscode/src/nodeClientMain.ts index a5f0cf8a05..b53d3d1fd4 100644 --- a/extensions/vscode/src/nodeClientMain.ts +++ b/extensions/vscode/src/nodeClientMain.ts @@ -35,7 +35,7 @@ export async function activate(context: vscode.ExtensionContext) { runOptions.execArgv.push("--max-old-space-size=" + config.server.maxOldSpaceSize); } const debugOptions: lsp.ForkOptions = { execArgv: ['--nolazy', '--inspect=' + port] }; - let serverOptions: lsp.ServerOptions = { + const serverOptions: lsp.ServerOptions = { run: { module: serverModule.fsPath, transport: lsp.TransportKind.ipc, @@ -55,7 +55,7 @@ export async function activate(context: vscode.ExtensionContext) { isTrusted: true, supportHtml: true, }, - outputChannel + outputChannel, }; const client = new _LanguageClient( id, @@ -145,18 +145,17 @@ try { s => s + `.filter(p=>p.name!=='typescript-vue-plugin-bundle')` ); } - else if (!enabledHybridMode) { + else if (enabledHybridMode) { // patch readPlugins text = text.replace( 'languages:Array.isArray(e.languages)', [ 'languages:', - `e.name==='typescript-vue-plugin-bundle'?[]:`, - 'Array.isArray(e.languages)', + `e.name==='typescript-vue-plugin-bundle'?[${config.server.includeLanguages.map(lang => `"${lang}"`).join(',')}]`, + ':Array.isArray(e.languages)', ].join(''), ); - } - else { + // VSCode < 1.87.0 text = text.replace('t.$u=[t.$r,t.$s,t.$p,t.$q]', s => s + '.concat("vue")'); // patch jsTsLanguageModes text = text.replace('.languages.match([t.$p,t.$q,t.$r,t.$s]', s => s + '.concat("vue")'); // patch isSupportedLanguageMode diff --git a/packages/component-meta/lib/base.ts b/packages/component-meta/lib/base.ts index 13f8cd84b1..cba4b852ea 100644 --- a/packages/component-meta/lib/base.ts +++ b/packages/component-meta/lib/base.ts @@ -92,6 +92,12 @@ function createCheckerWorker( if (parsedCommandLine.vueOptions.extensions.some(ext => fileName.endsWith(ext))) { return 'vue'; } + if (parsedCommandLine.vueOptions.vitePressExtensions.some(ext => fileName.endsWith(ext))) { + return 'markdown'; + } + if (parsedCommandLine.vueOptions.petiteVueExtensions.some(ext => fileName.endsWith(ext))) { + return 'html'; + } return vue.resolveCommonLanguageId(fileName); }, scriptIdToFileName: id => id, diff --git a/packages/language-core/lib/languageModule.ts b/packages/language-core/lib/languageModule.ts index bedeab4a68..6d5c65b0e3 100644 --- a/packages/language-core/lib/languageModule.ts +++ b/packages/language-core/lib/languageModule.ts @@ -1,10 +1,13 @@ import { forEachEmbeddedCode, type LanguagePlugin } from '@volar/language-core'; import type * as ts from 'typescript'; -import { getDefaultVueLanguagePlugins } from './plugins'; +import { getBasePlugins } from './plugins'; import type { VueCompilerOptions, VueLanguagePlugin } from './types'; import { VueVirtualCode } from './virtualFile/vueFile'; import * as CompilerDOM from '@vue/compiler-dom'; import * as CompilerVue2 from './utils/vue2TemplateCompiler'; +import useHtmlFilePlugin from './plugins/file-html'; +import useMdFilePlugin from './plugins/file-md'; +import useVueFilePlugin from './plugins/file-vue'; const normalFileRegistries: { key: string; @@ -62,7 +65,6 @@ export function createVueLanguagePlugin( compilerOptions: ts.CompilerOptions, vueCompilerOptions: VueCompilerOptions, ): _Plugin { - const allowLanguageIds = new Set(['vue']); const pluginContext: Parameters[0] = { modules: { '@vue/compiler-dom': vueCompilerOptions.target < 3 @@ -77,18 +79,14 @@ export function createVueLanguagePlugin( vueCompilerOptions, globalTypesHolder: undefined, }; - const plugins = getDefaultVueLanguagePlugins(pluginContext); - - if (vueCompilerOptions.extensions.includes('.md')) { - allowLanguageIds.add('markdown'); - } - if (vueCompilerOptions.extensions.includes('.html')) { - allowLanguageIds.add('html'); - } - + const basePlugins = getBasePlugins(pluginContext); + const vueSfcPlugin = useVueFilePlugin(pluginContext); + const vitePressSfcPlugin = useMdFilePlugin(pluginContext); + const petiteVueSfcPlugin = useHtmlFilePlugin(pluginContext); const getCanonicalFileName = useCaseSensitiveFileNames ? (fileName: string) => fileName : (fileName: string) => fileName.toLowerCase(); + let canonicalRootFileNames = new Set(); let canonicalRootFileNamesVersion: string | undefined; @@ -96,7 +94,13 @@ export function createVueLanguagePlugin( getCanonicalFileName, pluginContext, createVirtualCode(fileId, languageId, snapshot) { - if (allowLanguageIds.has(languageId)) { + const isVueFile = vueCompilerOptions.extensions.some(ext => fileId.endsWith(ext)) + || (vueCompilerOptions.extensions.length && languageId === 'vue'); + const isVitePressFile = vueCompilerOptions.vitePressExtensions.some(ext => fileId.endsWith(ext)) + || (vueCompilerOptions.vitePressExtensions.length && languageId === 'markdown'); + const isPetiteVueFile = vueCompilerOptions.petiteVueExtensions.some(ext => fileId.endsWith(ext)) + || (vueCompilerOptions.petiteVueExtensions.length && languageId === 'html'); + if (isVueFile || isVitePressFile || isPetiteVueFile) { const fileName = getFileName(fileId); const projectVersion = getProjectVersion(); if (projectVersion !== canonicalRootFileNamesVersion) { @@ -118,7 +122,11 @@ export function createVueLanguagePlugin( languageId, snapshot, vueCompilerOptions, - plugins, + isPetiteVueFile + ? [petiteVueSfcPlugin, ...basePlugins] + : isVitePressFile + ? [vitePressSfcPlugin, ...basePlugins] + : [vueSfcPlugin, ...basePlugins], ts, ); fileRegistry.set(fileId, code); @@ -155,7 +163,11 @@ export function createVueLanguagePlugin( // } // }, typescript: { - extraFileExtensions: vueCompilerOptions.extensions.map(ext => ({ + extraFileExtensions: [ + ...vueCompilerOptions.extensions, + ...vueCompilerOptions.vitePressExtensions, + ...vueCompilerOptions.petiteVueExtensions, + ].map(ext => ({ extension: ext.slice(1), isMixedContent: true, scriptKind: 7 satisfies ts.ScriptKind.Deferred, @@ -181,7 +193,7 @@ export function createVueLanguagePlugin( function getFileRegistry(isGlobalTypesHolder: boolean) { return getVueFileRegistry( isGlobalTypesHolder, - getFileRegistryKey(compilerOptions, vueCompilerOptions, plugins), + getFileRegistryKey(compilerOptions, vueCompilerOptions, basePlugins), vueCompilerOptions.plugins, ); } diff --git a/packages/language-core/lib/plugins.ts b/packages/language-core/lib/plugins.ts index 0f9e145a77..6cbdaa247d 100644 --- a/packages/language-core/lib/plugins.ts +++ b/packages/language-core/lib/plugins.ts @@ -1,6 +1,3 @@ -import useHtmlFilePlugin from './plugins/file-html'; -import useMdFilePlugin from './plugins/file-md'; -import useVueFilePlugin from './plugins/file-vue'; import useVueSfcCustomBlocks from './plugins/vue-sfc-customblocks'; import useVueSfcScriptsFormat from './plugins/vue-sfc-scripts'; import useVueSfcStyles from './plugins/vue-sfc-styles'; @@ -11,12 +8,9 @@ import useVueTemplateInlineTsPlugin from './plugins/vue-template-inline-ts'; import useVueTsx from './plugins/vue-tsx'; import { pluginVersion, type VueLanguagePlugin } from './types'; -export function getDefaultVueLanguagePlugins(pluginContext: Parameters[0]) { +export function getBasePlugins(pluginContext: Parameters[0]) { const plugins: VueLanguagePlugin[] = [ - useMdFilePlugin, // .md for VitePress - useHtmlFilePlugin, // .html for PetiteVue - useVueFilePlugin, // .vue and others for Vue useVueTemplateHtmlPlugin, useVueTemplateInlineCssPlugin, useVueTemplateInlineTsPlugin, diff --git a/packages/language-core/lib/plugins/file-html.ts b/packages/language-core/lib/plugins/file-html.ts index 72440aebc7..353cb3d8a8 100644 --- a/packages/language-core/lib/plugins/file-html.ts +++ b/packages/language-core/lib/plugins/file-html.ts @@ -12,81 +12,78 @@ const plugin: VueLanguagePlugin = () => { parseSFC(fileName, content) { - if (fileName.endsWith('.html')) { - - let sfc: SFCParseResult = { - descriptor: { - filename: fileName, - source: content, - template: null, - script: null, - scriptSetup: null, - styles: [], - customBlocks: [], - cssVars: [], - shouldForceReload: () => false, - slotted: false, - }, - errors: [], - }; - - let templateContent = content; + let sfc: SFCParseResult = { + descriptor: { + filename: fileName, + source: content, + template: null, + script: null, + scriptSetup: null, + styles: [], + customBlocks: [], + cssVars: [], + shouldForceReload: () => false, + slotted: false, + }, + errors: [], + }; - for (const match of content.matchAll(sfcBlockReg)) { + let templateContent = content; - const matchText = match[0]; - const tag = match[1]; - const attrs = match[2]; - const lang = attrs.match(langReg)?.[2]; - const content = match[3]; - const contentStart = match.index + matchText.indexOf(content); + for (const match of content.matchAll(sfcBlockReg)) { - if (tag === 'style') { - sfc.descriptor.styles.push({ - attrs: {}, - content, - loc: { - start: { column: -1, line: -1, offset: contentStart }, - end: { column: -1, line: -1, offset: contentStart + content.length }, - source: content, - }, - type: 'style', - lang, - }); - } - // ignore `