Skip to content

Commit 9965d56

Browse files
feat(vscode): automatically enable Hybrid Mode (#4206)
1 parent aab3a8a commit 9965d56

File tree

5 files changed

+150
-42
lines changed

5 files changed

+150
-42
lines changed

extensions/vscode/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,12 @@
221221
},
222222
"vue.server.hybridMode": {
223223
"type": "boolean",
224-
"default": false,
224+
"default": "auto",
225+
"enum": [
226+
"auto",
227+
true,
228+
false
229+
],
225230
"description": "Vue language server only handles CSS and HTML language support, and tsserver takes over TS language support via TS plugin."
226231
},
227232
"vue.server.maxFileSize": {

extensions/vscode/src/common.ts

Lines changed: 141 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import { config } from './config';
55
import * as doctor from './features/doctor';
66
import * as nameCasing from './features/nameCasing';
77
import * as splitEditors from './features/splitEditors';
8+
import * as semver from 'semver';
9+
import * as fs from 'fs';
10+
import * as path from 'path';
811

912
let client: lsp.BaseLanguageClient;
1013

@@ -17,8 +20,6 @@ type CreateLanguageClient = (
1720
outputChannel: vscode.OutputChannel,
1821
) => lsp.BaseLanguageClient;
1922

20-
const beginHybridMode = config.server.hybridMode;
21-
2223
export async function activate(context: vscode.ExtensionContext, createLc: CreateLanguageClient) {
2324

2425
const stopCheck = vscode.window.onDidChangeActiveTextEditor(tryActivate);
@@ -36,17 +37,137 @@ export async function activate(context: vscode.ExtensionContext, createLc: Creat
3637
}
3738
}
3839

40+
export const currentHybridModeStatus = getCurrentHybridModeStatus();
41+
42+
function getCurrentHybridModeStatus(report = false) {
43+
if (config.server.hybridMode === 'auto') {
44+
const unknownExtensions: string[] = [];
45+
for (const extension of vscode.extensions.all) {
46+
const hasTsPlugin = !!extension.packageJSON?.contributes?.typescriptServerPlugins;
47+
if (hasTsPlugin) {
48+
if (
49+
extension.id === 'Vue.volar'
50+
|| extension.id === 'unifiedjs.vscode-mdx'
51+
|| extension.id === 'astro-build.astro-vscode'
52+
|| extension.id === 'ije.esm-vscode'
53+
|| extension.id === 'johnsoncodehk.vscode-tsslint'
54+
|| extension.id === 'VisualStudioExptTeam.vscodeintellicode'
55+
) {
56+
continue;
57+
}
58+
else {
59+
unknownExtensions.push(extension.id);
60+
}
61+
}
62+
}
63+
if (unknownExtensions.length) {
64+
if (report) {
65+
vscode.window.showInformationMessage(
66+
`Hybrid Mode is disabled automatically because there is a potentially incompatible ${unknownExtensions.join(', ')} TypeScript plugin installed.`,
67+
'Open Settings',
68+
'Report a false positive',
69+
).then(value => {
70+
if (value === 'Open Settings') {
71+
vscode.commands.executeCommand('workbench.action.openSettings', 'vue.server.hybridMode');
72+
}
73+
else if (value == 'Report a false positive') {
74+
vscode.env.openExternal(vscode.Uri.parse('https://github.com/vuejs/language-tools/pull/4206'));
75+
}
76+
});
77+
}
78+
return false;
79+
}
80+
const vscodeTsdkVersion = getVScodeTsdkVersion();
81+
const workspaceTsdkVersion = getWorkspaceTsdkVersion();
82+
if (
83+
(vscodeTsdkVersion && !semver.gte(vscodeTsdkVersion, '5.3.0'))
84+
|| (workspaceTsdkVersion && !semver.gte(workspaceTsdkVersion, '5.3.0'))
85+
) {
86+
if (report) {
87+
let msg = `Hybrid Mode is disabled automatically because TSDK >= 5.3.0 is required (VSCode TSDK: ${vscodeTsdkVersion}`;
88+
if (workspaceTsdkVersion) {
89+
msg += `, Workspace TSDK: ${workspaceTsdkVersion}`;
90+
}
91+
msg += `).`;
92+
vscode.window.showInformationMessage(msg, 'Open Settings').then(value => {
93+
if (value === 'Open Settings') {
94+
vscode.commands.executeCommand('workbench.action.openSettings', 'vue.server.hybridMode');
95+
}
96+
});
97+
}
98+
return false;
99+
}
100+
return true;
101+
}
102+
else {
103+
return config.server.hybridMode;
104+
}
105+
106+
function getVScodeTsdkVersion() {
107+
const nightly = vscode.extensions.getExtension('ms-vscode.vscode-typescript-next');
108+
if (nightly) {
109+
const libPath = path.join(
110+
nightly.extensionPath.replace(/\\/g, '/'),
111+
'node_modules/typescript/lib',
112+
);
113+
return getTsVersion(libPath);
114+
}
115+
116+
if (vscode.env.appRoot) {
117+
const libPath = path.join(
118+
vscode.env.appRoot.replace(/\\/g, '/'),
119+
'extensions/node_modules/typescript/lib',
120+
);
121+
return getTsVersion(libPath);
122+
}
123+
}
124+
125+
function getWorkspaceTsdkVersion() {
126+
const libPath = vscode.workspace.getConfiguration('typescript').get<string>('tsdk')?.replace(/\\/g, '/');
127+
if (libPath) {
128+
return getTsVersion(libPath);
129+
}
130+
}
131+
132+
function getTsVersion(libPath: string): string | undefined {
133+
134+
const p = libPath.toString().split('/');
135+
const p2 = p.slice(0, -1);
136+
const modulePath = p2.join('/');
137+
const filePath = modulePath + '/package.json';
138+
const contents = fs.readFileSync(filePath, 'utf-8');
139+
140+
if (contents === undefined) {
141+
return;
142+
}
143+
144+
let desc: any = null;
145+
try {
146+
desc = JSON.parse(contents);
147+
} catch (err) {
148+
return;
149+
}
150+
if (!desc || !desc.version) {
151+
return;
152+
}
153+
154+
return desc.version;
155+
}
156+
}
157+
39158
async function doActivate(context: vscode.ExtensionContext, createLc: CreateLanguageClient) {
40159

41-
vscode.commands.executeCommand('setContext', 'vue.activated', true);
160+
getCurrentHybridModeStatus(true);
42161

43162
const outputChannel = vscode.window.createOutputChannel('Vue Language Server');
44163

164+
vscode.commands.executeCommand('setContext', 'vue.activated', true);
165+
45166
client = createLc(
46167
'vue',
47168
'Vue',
48169
getDocumentSelector(),
49-
await getInitializationOptions(context),
170+
await getInitializationOptions(context, currentHybridModeStatus),
50171
6009,
51172
outputChannel
52173
);
@@ -72,20 +193,20 @@ async function doActivate(context: vscode.ExtensionContext, createLc: CreateLang
72193
lsp.activateWriteVirtualFiles('vue.action.writeVirtualFiles', client);
73194
lsp.activateServerSys(client);
74195

75-
if (!config.server.hybridMode) {
196+
if (!currentHybridModeStatus) {
76197
lsp.activateTsConfigStatusItem(selectors, 'vue.tsconfig', client);
77198
lsp.activateTsVersionStatusItem(selectors, 'vue.tsversion', context, client, text => 'TS ' + text);
78199
}
79200

80201
const hybridModeStatus = vscode.languages.createLanguageStatusItem('vue-hybrid-mode', selectors);
81202
hybridModeStatus.text = 'Hybrid Mode';
82-
hybridModeStatus.detail = config.server.hybridMode ? 'Enabled' : 'Disabled';
203+
hybridModeStatus.detail = (currentHybridModeStatus ? 'Enabled' : 'Disabled') + (config.server.hybridMode === 'auto' ? ' (Auto)' : '');
83204
hybridModeStatus.command = {
84205
title: 'Open Setting',
85206
command: 'workbench.action.openSettings',
86207
arguments: ['vue.server.hybridMode'],
87208
};
88-
if (!config.server.hybridMode) {
209+
if (!currentHybridModeStatus) {
89210
hybridModeStatus.severity = vscode.LanguageStatusSeverity.Warning;
90211
}
91212

@@ -122,12 +243,15 @@ async function doActivate(context: vscode.ExtensionContext, createLc: CreateLang
122243

123244
function activateConfigWatcher() {
124245
context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(e => {
125-
if (e.affectsConfiguration('vue.server.hybridMode') && config.server.hybridMode !== beginHybridMode) {
126-
requestReloadVscode(
127-
config.server.hybridMode
128-
? 'Please reload VSCode to enable Hybrid Mode.'
129-
: 'Please reload VSCode to disable Hybrid Mode.'
130-
);
246+
if (e.affectsConfiguration('vue.server.hybridMode')) {
247+
const newStatus = getCurrentHybridModeStatus();
248+
if (newStatus !== currentHybridModeStatus) {
249+
requestReloadVscode(
250+
newStatus
251+
? 'Please reload VSCode to enable Hybrid Mode.'
252+
: 'Please reload VSCode to disable Hybrid Mode.'
253+
);
254+
}
131255
}
132256
else if (e.affectsConfiguration('vue')) {
133257
vscode.commands.executeCommand('vue.action.restartServer', false);
@@ -139,7 +263,7 @@ async function doActivate(context: vscode.ExtensionContext, createLc: CreateLang
139263
context.subscriptions.push(vscode.commands.registerCommand('vue.action.restartServer', async (restartTsServer: boolean = true) => {
140264
await client.stop();
141265
outputChannel.clear();
142-
client.clientOptions.initializationOptions = await getInitializationOptions(context);
266+
client.clientOptions.initializationOptions = await getInitializationOptions(context, currentHybridModeStatus);
143267
await client.start();
144268
nameCasing.activate(context, client, selectors);
145269
if (restartTsServer) {
@@ -167,18 +291,19 @@ export function getDocumentSelector(): lsp.DocumentFilter[] {
167291

168292
async function getInitializationOptions(
169293
context: vscode.ExtensionContext,
294+
hybridMode: boolean,
170295
): Promise<VueInitializationOptions> {
171296
return {
172297
// volar
173298
diagnosticModel: config.server.diagnosticModel === 'pull' ? DiagnosticModel.Pull : DiagnosticModel.Push,
174299
typescript: { tsdk: (await lsp.getTsdk(context)).tsdk },
175300
maxFileSize: config.server.maxFileSize,
176301
semanticTokensLegend: {
177-
tokenTypes: ['component'],
302+
tokenTypes: [],
178303
tokenModifiers: [],
179304
},
180305
vue: {
181-
hybridMode: beginHybridMode,
306+
hybridMode,
182307
additionalExtensions: [
183308
...config.server.additionalExtensions,
184309
...!config.server.petiteVue.supportHtmlFile ? [] : ['html'],

extensions/vscode/src/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export const config = {
1616
return _config().get('doctor')!;
1717
},
1818
get server(): Readonly<{
19-
hybridMode: boolean;
19+
hybridMode: 'auto' | boolean;
2020
maxOldSpaceSize: number;
2121
maxFileSize: number;
2222
diagnosticModel: 'push' | 'pull';

extensions/vscode/src/features/doctor.ts

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -231,27 +231,6 @@ export async function register(context: vscode.ExtensionContext, client: BaseLan
231231
});
232232
}
233233

234-
if (config.server.hybridMode) {
235-
// #3942, https://github.com/microsoft/TypeScript/issues/57633
236-
for (const extId of [
237-
'svelte.svelte-vscode',
238-
'styled-components.vscode-styled-components',
239-
'Divlo.vscode-styled-jsx-languageserver',
240-
]) {
241-
const ext = vscode.extensions.getExtension(extId);
242-
if (ext) {
243-
problems.push({
244-
title: `Recommended to disable "${ext.packageJSON.displayName || extId}" in Vue workspace`,
245-
message: [
246-
`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.`,
247-
'',
248-
'Issues: https://github.com/vuejs/language-tools/issues/3942, https://github.com/microsoft/TypeScript/issues/57633',
249-
].join('\n'),
250-
});
251-
}
252-
}
253-
}
254-
255234
// check outdated vue language plugins
256235
// check node_modules has more than one vue versions
257236
// check ESLint, Prettier...

extensions/vscode/src/nodeClientMain.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as serverLib from '@vue/language-server';
33
import * as fs from 'fs';
44
import * as vscode from 'vscode';
55
import * as lsp from '@volar/vscode/node';
6-
import { activate as commonActivate, deactivate as commonDeactivate } from './common';
6+
import { activate as commonActivate, deactivate as commonDeactivate, currentHybridModeStatus } from './common';
77
import { config } from './config';
88
import { middleware } from './middleware';
99

@@ -133,15 +133,14 @@ try {
133133
const tsExtension = vscode.extensions.getExtension('vscode.typescript-language-features')!;
134134
const readFileSync = fs.readFileSync;
135135
const extensionJsPath = require.resolve('./dist/extension.js', { paths: [tsExtension.extensionPath] });
136-
const { hybridMode } = config.server;
137136

138137
// @ts-expect-error
139138
fs.readFileSync = (...args) => {
140139
if (args[0] === extensionJsPath) {
141140
// @ts-expect-error
142141
let text = readFileSync(...args) as string;
143142

144-
if (!hybridMode) {
143+
if (!currentHybridModeStatus) {
145144
// patch readPlugins
146145
text = text.replace(
147146
'languages:Array.isArray(e.languages)',

0 commit comments

Comments
 (0)