From dd04a91c93b767220f1e5dfad7f9a9746c8f2a6d Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 4 Apr 2024 17:55:30 +0800 Subject: [PATCH 1/6] feat(vscode): automatically enable Hybrid Mode --- extensions/vscode/package.json | 7 ++- extensions/vscode/src/common.ts | 70 +++++++++++++++++++------ extensions/vscode/src/config.ts | 2 +- extensions/vscode/src/nodeClientMain.ts | 5 +- 4 files changed, 64 insertions(+), 20 deletions(-) diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json index 74d4b5edf6..f9a33f814c 100644 --- a/extensions/vscode/package.json +++ b/extensions/vscode/package.json @@ -221,7 +221,12 @@ }, "vue.server.hybridMode": { "type": "boolean", - "default": false, + "default": "auto", + "enum": [ + "auto", + true, + false + ], "description": "Vue language server only handles CSS and HTML language support, and tsserver takes over TS language support via TS plugin." }, "vue.server.maxFileSize": { diff --git a/extensions/vscode/src/common.ts b/extensions/vscode/src/common.ts index 31d8f03a60..4b96ce1dbd 100644 --- a/extensions/vscode/src/common.ts +++ b/extensions/vscode/src/common.ts @@ -17,8 +17,6 @@ type CreateLanguageClient = ( outputChannel: vscode.OutputChannel, ) => lsp.BaseLanguageClient; -const beginHybridMode = config.server.hybridMode; - export async function activate(context: vscode.ExtensionContext, createLc: CreateLanguageClient) { const stopCheck = vscode.window.onDidChangeActiveTextEditor(tryActivate); @@ -36,17 +34,55 @@ export async function activate(context: vscode.ExtensionContext, createLc: Creat } } +export const currentHybridModeStatus = getCurrentHybridModeStatus(); + +function getCurrentHybridModeStatus(report = false) { + if (config.server.hybridMode === 'auto') { + for (const extension of vscode.extensions.all) { + const hasTsPlugin = !!extension.packageJSON?.contributes?.typescriptServerPlugins; + if (hasTsPlugin) { + if ( + extension.id === 'Vue.volar' + || extension.id === 'unifiedjs.vscode-mdx' + || extension.id === 'astro-build.astro-vscode' + ) { + continue; + } + else { + if (report) { + vscode.window.showInformationMessage( + `Hybrid Mode is disabled automatically because there is a potentially incompatible "${extension.id}" TypeScript plugin installed.`, + 'Report a false positive', + ).then(value => { + if (value) { + vscode.env.openExternal(vscode.Uri.parse('')); + } + }); + } + return false; + } + } + } + return true; + } + else { + return config.server.hybridMode; + } +} + 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); + client = createLc( 'vue', 'Vue', getDocumentSelector(), - await getInitializationOptions(context), + await getInitializationOptions(context, currentHybridModeStatus), 6009, outputChannel ); @@ -72,20 +108,20 @@ async function doActivate(context: vscode.ExtensionContext, createLc: CreateLang lsp.activateWriteVirtualFiles('vue.action.writeVirtualFiles', client); lsp.activateServerSys(client); - if (!config.server.hybridMode) { + if (!currentHybridModeStatus) { lsp.activateTsConfigStatusItem(selectors, 'vue.tsconfig', client); lsp.activateTsVersionStatusItem(selectors, 'vue.tsversion', context, client, text => 'TS ' + text); } const hybridModeStatus = vscode.languages.createLanguageStatusItem('vue-hybrid-mode', selectors); hybridModeStatus.text = 'Hybrid Mode'; - hybridModeStatus.detail = config.server.hybridMode ? 'Enabled' : 'Disabled'; + hybridModeStatus.detail = (currentHybridModeStatus ? 'Enabled' : 'Disabled') + (config.server.hybridMode === 'auto' ? ' (Auto)' : ''); hybridModeStatus.command = { title: 'Open Setting', command: 'workbench.action.openSettings', arguments: ['vue.server.hybridMode'], }; - if (!config.server.hybridMode) { + if (currentHybridModeStatus) { hybridModeStatus.severity = vscode.LanguageStatusSeverity.Warning; } @@ -122,12 +158,15 @@ async function doActivate(context: vscode.ExtensionContext, createLc: CreateLang function activateConfigWatcher() { context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('vue.server.hybridMode') && config.server.hybridMode !== beginHybridMode) { - requestReloadVscode( - config.server.hybridMode - ? 'Please reload VSCode to enable Hybrid Mode.' - : 'Please reload VSCode to disable Hybrid Mode.' - ); + if (e.affectsConfiguration('vue.server.hybridMode')) { + const newStatus = getCurrentHybridModeStatus(); + if (newStatus !== currentHybridModeStatus) { + requestReloadVscode( + newStatus + ? 'Please reload VSCode to enable Hybrid Mode.' + : 'Please reload VSCode to disable Hybrid Mode.' + ); + } } else if (e.affectsConfiguration('vue')) { vscode.commands.executeCommand('vue.action.restartServer', false); @@ -139,7 +178,7 @@ async function doActivate(context: vscode.ExtensionContext, createLc: CreateLang context.subscriptions.push(vscode.commands.registerCommand('vue.action.restartServer', async (restartTsServer: boolean = true) => { await client.stop(); outputChannel.clear(); - client.clientOptions.initializationOptions = await getInitializationOptions(context); + client.clientOptions.initializationOptions = await getInitializationOptions(context, currentHybridModeStatus); await client.start(); nameCasing.activate(context, client, selectors); if (restartTsServer) { @@ -167,6 +206,7 @@ export function getDocumentSelector(): lsp.DocumentFilter[] { async function getInitializationOptions( context: vscode.ExtensionContext, + hybridMode: boolean, ): Promise { return { // volar @@ -178,7 +218,7 @@ async function getInitializationOptions( tokenModifiers: [], }, vue: { - hybridMode: beginHybridMode, + hybridMode, additionalExtensions: [ ...config.server.additionalExtensions, ...!config.server.petiteVue.supportHtmlFile ? [] : ['html'], diff --git a/extensions/vscode/src/config.ts b/extensions/vscode/src/config.ts index 826bcf4a84..bad43b03a1 100644 --- a/extensions/vscode/src/config.ts +++ b/extensions/vscode/src/config.ts @@ -16,7 +16,7 @@ export const config = { return _config().get('doctor')!; }, get server(): Readonly<{ - hybridMode: boolean; + hybridMode: 'auto' | boolean; maxOldSpaceSize: number; maxFileSize: number; diagnosticModel: 'push' | 'pull'; diff --git a/extensions/vscode/src/nodeClientMain.ts b/extensions/vscode/src/nodeClientMain.ts index 1d2d9d36b7..85f2ae3561 100644 --- a/extensions/vscode/src/nodeClientMain.ts +++ b/extensions/vscode/src/nodeClientMain.ts @@ -3,7 +3,7 @@ import * as serverLib from '@vue/language-server'; import * as fs from 'fs'; import * as vscode from 'vscode'; import * as lsp from '@volar/vscode/node'; -import { activate as commonActivate, deactivate as commonDeactivate } from './common'; +import { activate as commonActivate, deactivate as commonDeactivate, currentHybridModeStatus } from './common'; import { config } from './config'; import { middleware } from './middleware'; @@ -133,7 +133,6 @@ try { const tsExtension = vscode.extensions.getExtension('vscode.typescript-language-features')!; const readFileSync = fs.readFileSync; const extensionJsPath = require.resolve('./dist/extension.js', { paths: [tsExtension.extensionPath] }); - const { hybridMode } = config.server; // @ts-expect-error fs.readFileSync = (...args) => { @@ -141,7 +140,7 @@ try { // @ts-expect-error let text = readFileSync(...args) as string; - if (!hybridMode) { + if (!currentHybridModeStatus) { // patch readPlugins text = text.replace( 'languages:Array.isArray(e.languages)', From f071b9d2bfa8d8fe71c51025e85b7a63a3e9d66f Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 4 Apr 2024 18:22:53 +0800 Subject: [PATCH 2/6] Add report link --- extensions/vscode/src/common.ts | 28 ++++++++++++++---------- extensions/vscode/src/features/doctor.ts | 21 ------------------ 2 files changed, 17 insertions(+), 32 deletions(-) diff --git a/extensions/vscode/src/common.ts b/extensions/vscode/src/common.ts index 4b96ce1dbd..d5568f9a0e 100644 --- a/extensions/vscode/src/common.ts +++ b/extensions/vscode/src/common.ts @@ -38,6 +38,7 @@ export const currentHybridModeStatus = getCurrentHybridModeStatus(); function getCurrentHybridModeStatus(report = false) { if (config.server.hybridMode === 'auto') { + const unknownExtensions: string[] = []; for (const extension of vscode.extensions.all) { const hasTsPlugin = !!extension.packageJSON?.contributes?.typescriptServerPlugins; if (hasTsPlugin) { @@ -45,24 +46,29 @@ function getCurrentHybridModeStatus(report = false) { extension.id === 'Vue.volar' || extension.id === 'unifiedjs.vscode-mdx' || extension.id === 'astro-build.astro-vscode' + || extension.id === 'ije.esm-vscode' + || extension.id === 'johnsoncodehk.vscode-tsslint' ) { continue; } else { - if (report) { - vscode.window.showInformationMessage( - `Hybrid Mode is disabled automatically because there is a potentially incompatible "${extension.id}" TypeScript plugin installed.`, - 'Report a false positive', - ).then(value => { - if (value) { - vscode.env.openExternal(vscode.Uri.parse('')); - } - }); - } - return false; + unknownExtensions.push(extension.id); } } } + if (unknownExtensions.length) { + if (report) { + vscode.window.showInformationMessage( + `Hybrid Mode is disabled automatically because there is a potentially incompatible ${unknownExtensions.join(', ')} TypeScript plugin installed.`, + 'Report a false positive', + ).then(value => { + if (value) { + vscode.env.openExternal(vscode.Uri.parse('https://github.com/vuejs/language-tools/pull/4206')); + } + }); + } + return false; + } return true; } else { diff --git a/extensions/vscode/src/features/doctor.ts b/extensions/vscode/src/features/doctor.ts index 2d79630625..4a4e5b34b9 100644 --- a/extensions/vscode/src/features/doctor.ts +++ b/extensions/vscode/src/features/doctor.ts @@ -231,27 +231,6 @@ export async function register(context: vscode.ExtensionContext, client: BaseLan }); } - if (config.server.hybridMode) { - // #3942, https://github.com/microsoft/TypeScript/issues/57633 - for (const extId of [ - 'svelte.svelte-vscode', - 'styled-components.vscode-styled-components', - 'Divlo.vscode-styled-jsx-languageserver', - ]) { - const ext = vscode.extensions.getExtension(extId); - if (ext) { - problems.push({ - title: `Recommended to disable "${ext.packageJSON.displayName || extId}" in Vue workspace`, - message: [ - `This extension's TypeScript Plugin and Vue's TypeScript Plugin are known to cause some conflicts. Until the problem is resolved, it is recommended that you temporarily disable the this extension in the Vue workspace.`, - '', - 'Issues: https://github.com/vuejs/language-tools/issues/3942, https://github.com/microsoft/TypeScript/issues/57633', - ].join('\n'), - }); - } - } - } - // check outdated vue language plugins // check node_modules has more than one vue versions // check ESLint, Prettier... From 2a7f6e60f178fc409160a55bb366f02c5683951d Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 4 Apr 2024 18:37:45 +0800 Subject: [PATCH 3/6] Add VisualStudioExptTeam.vscodeintellicode --- extensions/vscode/src/common.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/vscode/src/common.ts b/extensions/vscode/src/common.ts index d5568f9a0e..96f520ce9a 100644 --- a/extensions/vscode/src/common.ts +++ b/extensions/vscode/src/common.ts @@ -48,6 +48,7 @@ function getCurrentHybridModeStatus(report = false) { || extension.id === 'astro-build.astro-vscode' || extension.id === 'ije.esm-vscode' || extension.id === 'johnsoncodehk.vscode-tsslint' + || extension.id === 'VisualStudioExptTeam.vscodeintellicode' ) { continue; } @@ -127,7 +128,7 @@ async function doActivate(context: vscode.ExtensionContext, createLc: CreateLang command: 'workbench.action.openSettings', arguments: ['vue.server.hybridMode'], }; - if (currentHybridModeStatus) { + if (!currentHybridModeStatus) { hybridModeStatus.severity = vscode.LanguageStatusSeverity.Warning; } From 280925b1210ba1c9e41ae378629fe49de90c56db Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 4 Apr 2024 18:52:31 +0800 Subject: [PATCH 4/6] Check TSDK version --- extensions/vscode/src/common.ts | 82 ++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/extensions/vscode/src/common.ts b/extensions/vscode/src/common.ts index 96f520ce9a..5a2626cd74 100644 --- a/extensions/vscode/src/common.ts +++ b/extensions/vscode/src/common.ts @@ -5,6 +5,9 @@ import { config } from './config'; import * as doctor from './features/doctor'; import * as nameCasing from './features/nameCasing'; import * as splitEditors from './features/splitEditors'; +import * as semver from 'semver'; +import * as fs from 'fs'; +import * as path from 'path'; let client: lsp.BaseLanguageClient; @@ -34,7 +37,7 @@ export async function activate(context: vscode.ExtensionContext, createLc: Creat } } -export const currentHybridModeStatus = getCurrentHybridModeStatus(); +export let currentHybridModeStatus = getCurrentHybridModeStatus(); function getCurrentHybridModeStatus(report = false) { if (config.server.hybridMode === 'auto') { @@ -61,20 +64,95 @@ function getCurrentHybridModeStatus(report = false) { if (report) { vscode.window.showInformationMessage( `Hybrid Mode is disabled automatically because there is a potentially incompatible ${unknownExtensions.join(', ')} TypeScript plugin installed.`, + 'Open Settings', 'Report a false positive', ).then(value => { - if (value) { + if (value === 'Open Settings') { + vscode.commands.executeCommand('workbench.action.openSettings', 'vue.server.hybridMode'); + } + else if (value == 'Report a false positive') { vscode.env.openExternal(vscode.Uri.parse('https://github.com/vuejs/language-tools/pull/4206')); } }); } return false; } + const vscodeTsdkVersion = getVScodeTsdkVersion(); + const workspaceTsdkVersion = getWorkspaceTsdkVersion(); + if ( + (vscodeTsdkVersion && !semver.gte(vscodeTsdkVersion, '5.3.0')) + || (workspaceTsdkVersion && !semver.gte(workspaceTsdkVersion, '5.3.0')) + ) { + if (report) { + let msg = `Hybrid Mode is disabled automatically because TSDK >= 5.3.0 is required (VSCode TSDK: ${vscodeTsdkVersion}`; + if (workspaceTsdkVersion) { + msg += `, Workspace TSDK: ${workspaceTsdkVersion}`; + } + msg += `).`; + vscode.window.showInformationMessage(msg, 'Open Settings').then(value => { + if (value === 'Open Settings') { + vscode.commands.executeCommand('workbench.action.openSettings', 'vue.server.hybridMode'); + } + }); + } + return false; + } return true; } else { return config.server.hybridMode; } + + function getVScodeTsdkVersion() { + const nightly = vscode.extensions.getExtension('ms-vscode.vscode-typescript-next'); + if (nightly) { + const libPath = path.join( + nightly.extensionPath.replace(/\\/g, '/'), + 'node_modules/typescript/lib', + ); + return getTsVersion(libPath); + } + + if (vscode.env.appRoot) { + const libPath = path.join( + vscode.env.appRoot.replace(/\\/g, '/'), + 'extensions/node_modules/typescript/lib', + ); + return getTsVersion(libPath); + } + } + + function getWorkspaceTsdkVersion() { + const libPath = vscode.workspace.getConfiguration('typescript').get('tsdk')?.replace(/\\/g, '/'); + if (libPath) { + return getTsVersion(libPath); + } + } + + function getTsVersion(libPath: string): string | undefined { + + const p = libPath.toString().split('/'); + const p2 = p.slice(0, -1); + const modulePath = p2.join('/'); + const filePath = modulePath + '/package.json'; + const contents = fs.readFileSync(filePath, 'utf-8'); + + if (contents === undefined) { + return; + } + + let desc: any = null; + try { + desc = JSON.parse(contents); + } catch (err) { + return; + } + if (!desc || !desc.version) { + return; + } + + return desc.version; + } } async function doActivate(context: vscode.ExtensionContext, createLc: CreateLanguageClient) { From 5b9e0fae1ee4b0cf45ac2ce07651140a195d47ab Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 4 Apr 2024 18:54:05 +0800 Subject: [PATCH 5/6] Update common.ts --- extensions/vscode/src/common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/vscode/src/common.ts b/extensions/vscode/src/common.ts index 5a2626cd74..78089285b5 100644 --- a/extensions/vscode/src/common.ts +++ b/extensions/vscode/src/common.ts @@ -299,7 +299,7 @@ async function getInitializationOptions( typescript: { tsdk: (await lsp.getTsdk(context)).tsdk }, maxFileSize: config.server.maxFileSize, semanticTokensLegend: { - tokenTypes: ['component'], + tokenTypes: [], tokenModifiers: [], }, vue: { From f432862e3f86ecd23a54799b563a382e12b6a394 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Thu, 4 Apr 2024 18:58:50 +0800 Subject: [PATCH 6/6] Update common.ts --- extensions/vscode/src/common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/vscode/src/common.ts b/extensions/vscode/src/common.ts index 78089285b5..ab63b7bb76 100644 --- a/extensions/vscode/src/common.ts +++ b/extensions/vscode/src/common.ts @@ -37,7 +37,7 @@ export async function activate(context: vscode.ExtensionContext, createLc: Creat } } -export let currentHybridModeStatus = getCurrentHybridModeStatus(); +export const currentHybridModeStatus = getCurrentHybridModeStatus(); function getCurrentHybridModeStatus(report = false) { if (config.server.hybridMode === 'auto') {