Skip to content

Commit

Permalink
feat(vscode): automatically enable Hybrid Mode (#4206)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsoncodehk authored Apr 4, 2024
1 parent aab3a8a commit 9965d56
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 42 deletions.
7 changes: 6 additions & 1 deletion extensions/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
157 changes: 141 additions & 16 deletions extensions/vscode/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -17,8 +20,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);
Expand All @@ -36,17 +37,137 @@ export async function activate(context: vscode.ExtensionContext, createLc: Creat
}
}

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) {
if (
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'
|| extension.id === 'VisualStudioExptTeam.vscodeintellicode'
) {
continue;
}
else {
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.`,
'Open Settings',
'Report a false positive',
).then(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<string>('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) {

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
);
Expand All @@ -72,20 +193,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;
}

Expand Down Expand Up @@ -122,12 +243,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);
Expand All @@ -139,7 +263,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) {
Expand Down Expand Up @@ -167,18 +291,19 @@ export function getDocumentSelector(): lsp.DocumentFilter[] {

async function getInitializationOptions(
context: vscode.ExtensionContext,
hybridMode: boolean,
): Promise<VueInitializationOptions> {
return {
// volar
diagnosticModel: config.server.diagnosticModel === 'pull' ? DiagnosticModel.Pull : DiagnosticModel.Push,
typescript: { tsdk: (await lsp.getTsdk(context)).tsdk },
maxFileSize: config.server.maxFileSize,
semanticTokensLegend: {
tokenTypes: ['component'],
tokenTypes: [],
tokenModifiers: [],
},
vue: {
hybridMode: beginHybridMode,
hybridMode,
additionalExtensions: [
...config.server.additionalExtensions,
...!config.server.petiteVue.supportHtmlFile ? [] : ['html'],
Expand Down
2 changes: 1 addition & 1 deletion extensions/vscode/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
21 changes: 0 additions & 21 deletions extensions/vscode/src/features/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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...
Expand Down
5 changes: 2 additions & 3 deletions extensions/vscode/src/nodeClientMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -133,15 +133,14 @@ 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) => {
if (args[0] === extensionJsPath) {
// @ts-expect-error
let text = readFileSync(...args) as string;

if (!hybridMode) {
if (!currentHybridModeStatus) {
// patch readPlugins
text = text.replace(
'languages:Array.isArray(e.languages)',
Expand Down

0 comments on commit 9965d56

Please sign in to comment.