Skip to content

Commit

Permalink
feat(vscode, language-server, typescript-plugin): communicate with ts…
Browse files Browse the repository at this point in the history
…server based on request forwarding (#5252)
  • Loading branch information
johnsoncodehk authored Mar 6, 2025
1 parent fb9edd2 commit 691715f
Show file tree
Hide file tree
Showing 21 changed files with 325 additions and 872 deletions.
5 changes: 4 additions & 1 deletion extensions/vscode/src/languageClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ async function activateLc(

async function getInitializationOptions(context: vscode.ExtensionContext): Promise<VueInitializationOptions> {
return {
typescript: { tsdk: (await lsp.getTsdk(context))!.tsdk },
typescript: {
tsdk: (await lsp.getTsdk(context))!.tsdk,
requestForwardingCommand: 'forwardingTsRequest',
},
};
}
20 changes: 20 additions & 0 deletions extensions/vscode/src/nodeClientMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,20 @@ export const { activate, deactivate } = defineExtension(async () => {

updateProviders(client);

client.onRequest('forwardingTsRequest', async ([command, args]) => {
const tsserver = (globalThis as any).__TSSERVER__?.semantic;
if (!tsserver) {
return;
}
const res = await tsserver.executeImpl(command, args, {
isAsync: true,
expectsResult: true,
lowPriority: true,
requireSemantic: true,
})[0];
return res.body;
});

return client;
}
);
Expand Down Expand Up @@ -140,6 +154,12 @@ try {
].join('')
);

// Expose tsserver process in SingleTsServer constructor
text = text.replace(
',this._callbacks.destroy("server errored")}))',
s => s + ',globalThis.__TSSERVER__||={},globalThis.__TSSERVER__[arguments[1]]=this'
);

/**
* VSCode < 1.87.0
*/
Expand Down
50 changes: 6 additions & 44 deletions packages/language-server/lib/hybridModeProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@ import type { Language, LanguagePlugin, LanguageServer, LanguageServerProject, P
import { createLanguageServiceEnvironment } from '@volar/language-server/lib/project/simpleProject';
import { createLanguage } from '@vue/language-core';
import { createLanguageService, createUriMap, LanguageService } from '@vue/language-service';
import { configuredServers, getBestServer, inferredServers, onServerReady } from '@vue/typescript-plugin/lib/utils';
import { URI } from 'vscode-uri';

export function createHybridModeProject(
create: (params: {
configFileName?: string;
asFileName: (scriptId: URI) => string;
}) => ProviderResult<{
create: () => ProviderResult<{
languagePlugins: LanguagePlugin<URI>[];
setup?(options: {
language: Language;
Expand All @@ -24,9 +20,6 @@ export function createHybridModeProject(
const project: LanguageServerProject = {
setup(_server) {
server = _server;
onServerReady.push(() => {
server.languageFeatures.requestRefresh(false);
});
server.fileWatcher.onDidChangeWatchedFiles(({ changes }) => {
for (const change of changes) {
const changeUri = URI.parse(change.uri);
Expand All @@ -36,34 +29,10 @@ export function createHybridModeProject(
}
}
});
const end = Date.now() + 60000;
const pipeWatcher = setInterval(() => {
for (const server of configuredServers) {
server.update();
}
for (const server of inferredServers) {
server.update();
}
if (Date.now() > end) {
clearInterval(pipeWatcher);
}
}, 2500);
},
async getLanguageService(uri) {
const fileName = asFileName(uri);
const namedPipeServer = await getBestServer(fileName);
if (namedPipeServer?.projectInfo?.kind === 1) {
const tsconfig = namedPipeServer.projectInfo.name;
const tsconfigUri = URI.file(tsconfig);
if (!tsconfigProjects.has(tsconfigUri)) {
tsconfigProjects.set(tsconfigUri, createLs(server, tsconfig));
}
return await tsconfigProjects.get(tsconfigUri)!;
}
else {
simpleLs ??= createLs(server, undefined);
return await simpleLs;
}
async getLanguageService() {
simpleLs ??= createLs(server);
return await simpleLs;
},
getExistingLanguageServices() {
return Promise.all([
Expand All @@ -85,15 +54,8 @@ export function createHybridModeProject(

return project;

function asFileName(uri: URI) {
return uri.fsPath.replace(/\\/g, '/');
}

async function createLs(server: LanguageServer, tsconfig: string | undefined) {
const { languagePlugins, setup } = await create({
configFileName: tsconfig,
asFileName,
});
async function createLs(server: LanguageServer) {
const { languagePlugins, setup } = await create();
const language = createLanguage([
{ getLanguageId: uri => server.documents.get(uri)?.languageId },
...languagePlugins,
Expand Down
1 change: 1 addition & 0 deletions packages/language-server/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export type VueInitializationOptions = {
typescript: {
tsdk: string;
requestForwardingCommand?: string;
};
};

Expand Down
55 changes: 44 additions & 11 deletions packages/language-server/node.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { createConnection, createServer, loadTsdkByPath } from '@volar/language-server/node';
import { createParsedCommandLine, createVueLanguagePlugin, getDefaultCompilerOptions } from '@vue/language-core';
import { createVueLanguagePlugin, getDefaultCompilerOptions } from '@vue/language-core';
import { getHybridModeLanguageServicePlugins } from '@vue/language-service';
import * as namedPipeClient from '@vue/typescript-plugin/lib/client';
import { createHybridModeProject } from './lib/hybridModeProject';
import type { VueInitializationOptions } from './lib/types';

Expand All @@ -12,24 +11,30 @@ connection.listen();

connection.onInitialize(params => {
const options: VueInitializationOptions = params.initializationOptions;

if (!options.typescript?.tsdk) {
throw new Error('typescript.tsdk is required');
}
if (!options.typescript?.requestForwardingCommand) {
connection.console.warn('typescript.requestForwardingCommand is required since >= 3.0 for complete TS features');
}

const { typescript: ts } = loadTsdkByPath(options.typescript.tsdk, params.locale);
return server.initialize(
params,
createHybridModeProject(
({ asFileName, configFileName }) => {
const commandLine = configFileName
? createParsedCommandLine(ts, ts.sys, configFileName)
: {
vueOptions: getDefaultCompilerOptions(),
options: ts.getDefaultCompilerOptions(),
};
() => {
const commandLine = {
vueOptions: getDefaultCompilerOptions(),
options: ts.getDefaultCompilerOptions(),
};
return {
languagePlugins: [
createVueLanguagePlugin(
ts,
commandLine.options,
commandLine.vueOptions,
asFileName
uri => uri.fsPath.replace(/\\/g, '/')
),
],
setup({ project }) {
Expand All @@ -38,7 +43,35 @@ connection.onInitialize(params => {
};
}
),
getHybridModeLanguageServicePlugins(ts, namedPipeClient)
getHybridModeLanguageServicePlugins(ts, options.typescript.requestForwardingCommand ? {
collectExtractProps(...args) {
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:collectExtractProps', args]);
},
getComponentDirectives(...args) {
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getComponentDirectives', args]);
},
getComponentEvents(...args) {
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getComponentEvents', args]);
},
getComponentNames(...args) {
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getComponentNames', args]);
},
getComponentProps(...args) {
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getComponentProps', args]);
},
getElementAttrs(...args) {
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getElementAttrs', args]);
},
getImportPathForFile(...args) {
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getImportPathForFile', args]);
},
getPropertiesAtLocation(...args) {
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getPropertiesAtLocation', args]);
},
getQuickInfoAtPosition(...args) {
return connection.sendRequest(options.typescript.requestForwardingCommand!, ['vue:getQuickInfoAtPosition', args]);
},
} : undefined)
);
});

Expand Down
3 changes: 0 additions & 3 deletions packages/language-server/tests/completions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,6 @@ test('HTML tags and built-in components', async () => {
"component",
"slot",
"template",
"BaseTransition",
"Fixture",
"foo",
]
`);
});
Expand Down
Loading

0 comments on commit 691715f

Please sign in to comment.