Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vscode): automatically enable Hybrid Mode #4206

Merged
merged 6 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading