Skip to content

Commit 77430b1

Browse files
feat(language-server): support --tsdk command line arg (#5691)
1 parent 9835ea7 commit 77430b1

File tree

6 files changed

+236
-226
lines changed

6 files changed

+236
-226
lines changed

extensions/vscode/index.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -90,16 +90,6 @@ export = defineExtension(() => {
9090
}
9191
});
9292

93-
// Setup typescript.js in production mode
94-
if (fs.existsSync(path.join(__dirname, 'language-server.js'))) {
95-
fs.writeFileSync(
96-
path.join(__dirname, 'typescript.js'),
97-
`module.exports = require("${
98-
vscode.env.appRoot.replace(/\\/g, '/')
99-
}/extensions/node_modules/typescript/lib/typescript.js");`,
100-
);
101-
}
102-
10393
if (config.server.path) {
10494
if (!serverPath) {
10595
vscode.window.showErrorMessage('Cannot find @vue/language-server.');
@@ -115,7 +105,10 @@ export = defineExtension(() => {
115105
});
116106
}
117107

118-
client = launch(serverPath ?? vscode.Uri.joinPath(context.extensionUri, 'dist', 'language-server.js').fsPath);
108+
client = launch(
109+
serverPath ?? vscode.Uri.joinPath(context.extensionUri, 'dist', 'language-server.js').fsPath,
110+
vscode.env.appRoot.replace(/\\/g, '/') + '/extensions/node_modules/typescript/lib',
111+
);
119112

120113
volarLabs.addLanguageClient(client);
121114

@@ -145,18 +138,21 @@ export = defineExtension(() => {
145138
return volarLabs.extensionExports;
146139
});
147140

148-
function launch(serverPath: string) {
141+
function launch(serverPath: string, tsdk: string) {
142+
const args = ['--tsdk=' + tsdk];
149143
const client = new lsp.LanguageClient(
150144
'vue',
151145
'Vue',
152146
{
153147
run: {
154148
module: serverPath,
149+
args,
155150
transport: lsp.TransportKind.ipc,
156151
options: {},
157152
},
158153
debug: {
159154
module: serverPath,
155+
args,
160156
transport: lsp.TransportKind.ipc,
161157
options: { execArgv: ['--nolazy', '--inspect=' + 6009] },
162158
},

extensions/vscode/rolldown.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const config: RolldownOptions = {
7474
id: /^typescript$/,
7575
},
7676
handler() {
77-
return { id: './typescript.js', external: true };
77+
return { id: 'typescript', external: true };
7878
},
7979
},
8080
},
Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,2 @@
11
#!/usr/bin/env node
2-
3-
if (process.argv.includes('--version')) {
4-
console.log(require('../package.json').version);
5-
return;
6-
}
7-
82
require('../index.js');

packages/language-server/index.ts

Lines changed: 16 additions & 206 deletions
Original file line numberDiff line numberDiff line change
@@ -1,208 +1,18 @@
1-
import type { LanguageServer } from '@volar/language-server';
2-
import { createLanguageServiceEnvironment } from '@volar/language-server/lib/project/simpleProject';
3-
import { createConnection, createServer } from '@volar/language-server/node';
4-
import {
5-
createLanguage,
6-
createParsedCommandLine,
7-
createParsedCommandLineByJson,
8-
createVueLanguagePlugin,
9-
} from '@vue/language-core';
10-
import {
11-
createLanguageService,
12-
createUriMap,
13-
createVueLanguageServicePlugins,
14-
type LanguageService,
15-
} from '@vue/language-service';
16-
import * as ts from 'typescript';
17-
import { URI } from 'vscode-uri';
18-
19-
const connection = createConnection();
20-
const server = createServer(connection);
21-
const tsserverRequestHandlers = new Map<number, (res: any) => void>();
22-
23-
let tsserverRequestId = 0;
24-
25-
connection.listen();
26-
27-
connection.onNotification('tsserver/response', ([id, res]) => {
28-
tsserverRequestHandlers.get(id)?.(res);
29-
tsserverRequestHandlers.delete(id);
30-
});
31-
32-
connection.onInitialize(params => {
33-
const tsconfigProjects = createUriMap<LanguageService>();
34-
const file2ProjectInfo = new Map<string, Promise<ts.server.protocol.ProjectInfo | null>>();
35-
36-
server.fileWatcher.onDidChangeWatchedFiles(({ changes }) => {
37-
for (const change of changes) {
38-
const changeUri = URI.parse(change.uri);
39-
if (tsconfigProjects.has(changeUri)) {
40-
tsconfigProjects.get(changeUri)!.dispose();
41-
tsconfigProjects.delete(changeUri);
42-
file2ProjectInfo.clear();
43-
}
1+
import { startServer } from './lib/server';
2+
3+
if (process.argv.includes('--version')) {
4+
console.log(require('./package.json').version);
5+
}
6+
else {
7+
let ts;
8+
for (const arg of process.argv) {
9+
if (arg.startsWith('--tsdk=')) {
10+
const tsdk = arg.substring('--tsdk='.length);
11+
const tsPath = require.resolve('./typescript.js', { paths: [tsdk] });
12+
ts = require(tsPath);
13+
break;
4414
}
45-
});
46-
47-
let simpleLanguageService: LanguageService | undefined;
48-
49-
return server.initialize(
50-
params,
51-
{
52-
setup() {},
53-
async getLanguageService(uri) {
54-
if (uri.scheme === 'file') {
55-
const fileName = uri.fsPath.replace(/\\/g, '/');
56-
let projectInfoPromise = file2ProjectInfo.get(fileName);
57-
if (!projectInfoPromise) {
58-
projectInfoPromise = sendTsServerRequest<ts.server.protocol.ProjectInfo>(
59-
'_vue:' + ts.server.protocol.CommandTypes.ProjectInfo,
60-
{
61-
file: fileName,
62-
needFileNameList: false,
63-
} satisfies ts.server.protocol.ProjectInfoRequestArgs,
64-
);
65-
file2ProjectInfo.set(fileName, projectInfoPromise);
66-
}
67-
const projectInfo = await projectInfoPromise;
68-
if (projectInfo) {
69-
const { configFileName } = projectInfo;
70-
let languageService = tsconfigProjects.get(URI.file(configFileName));
71-
if (!languageService) {
72-
languageService = createProjectLanguageService(server, configFileName);
73-
tsconfigProjects.set(URI.file(configFileName), languageService);
74-
}
75-
return languageService;
76-
}
77-
}
78-
return simpleLanguageService ??= createProjectLanguageService(server, undefined);
79-
},
80-
getExistingLanguageServices() {
81-
return Promise.all([
82-
...tsconfigProjects.values(),
83-
simpleLanguageService,
84-
].filter(promise => !!promise));
85-
},
86-
reload() {
87-
for (const languageService of tsconfigProjects.values()) {
88-
languageService.dispose();
89-
}
90-
tsconfigProjects.clear();
91-
if (simpleLanguageService) {
92-
simpleLanguageService.dispose();
93-
simpleLanguageService = undefined;
94-
}
95-
},
96-
},
97-
createVueLanguageServicePlugins(ts, {
98-
collectExtractProps(...args) {
99-
return sendTsServerRequest('_vue:collectExtractProps', args);
100-
},
101-
getComponentDirectives(...args) {
102-
return sendTsServerRequest('_vue:getComponentDirectives', args);
103-
},
104-
getComponentEvents(...args) {
105-
return sendTsServerRequest('_vue:getComponentEvents', args);
106-
},
107-
getComponentNames(...args) {
108-
return sendTsServerRequest('_vue:getComponentNames', args);
109-
},
110-
getComponentProps(...args) {
111-
return sendTsServerRequest('_vue:getComponentProps', args);
112-
},
113-
getComponentSlots(...args) {
114-
return sendTsServerRequest('_vue:getComponentSlots', args);
115-
},
116-
getElementAttrs(...args) {
117-
return sendTsServerRequest('_vue:getElementAttrs', args);
118-
},
119-
getElementNames(...args) {
120-
return sendTsServerRequest('_vue:getElementNames', args);
121-
},
122-
getImportPathForFile(...args) {
123-
return sendTsServerRequest('_vue:getImportPathForFile', args);
124-
},
125-
isRefAtPosition(...args) {
126-
return sendTsServerRequest('_vue:isRefAtPosition', args);
127-
},
128-
getDocumentHighlights(fileName, position) {
129-
return sendTsServerRequest(
130-
'_vue:documentHighlights-full',
131-
{
132-
file: fileName,
133-
...{ position } as unknown as { line: number; offset: number },
134-
filesToSearch: [fileName],
135-
} satisfies ts.server.protocol.DocumentHighlightsRequestArgs,
136-
);
137-
},
138-
getEncodedSemanticClassifications(fileName, span) {
139-
return sendTsServerRequest(
140-
'_vue:encodedSemanticClassifications-full',
141-
{
142-
file: fileName,
143-
...span,
144-
format: ts.SemanticClassificationFormat.TwentyTwenty,
145-
} satisfies ts.server.protocol.EncodedSemanticClassificationsRequestArgs,
146-
);
147-
},
148-
async getQuickInfoAtPosition(fileName, { line, character }) {
149-
const result = await sendTsServerRequest<ts.server.protocol.QuickInfoResponseBody>(
150-
'_vue:' + ts.server.protocol.CommandTypes.Quickinfo,
151-
{
152-
file: fileName,
153-
line: line + 1,
154-
offset: character + 1,
155-
} satisfies ts.server.protocol.FileLocationRequestArgs,
156-
);
157-
return result?.displayString;
158-
},
159-
}),
160-
);
161-
162-
async function sendTsServerRequest<T>(command: string, args: any): Promise<T | null> {
163-
return await new Promise<T | null>(resolve => {
164-
const requestId = ++tsserverRequestId;
165-
tsserverRequestHandlers.set(requestId, resolve);
166-
connection.sendNotification('tsserver/request', [requestId, command, args]);
167-
});
168-
}
169-
170-
function createProjectLanguageService(server: LanguageServer, tsconfig: string | undefined) {
171-
const commonLine = tsconfig && !ts.server.isInferredProjectName(tsconfig)
172-
? createParsedCommandLine(ts, ts.sys, tsconfig)
173-
: createParsedCommandLineByJson(ts, ts.sys, ts.sys.getCurrentDirectory(), {});
174-
const language = createLanguage<URI>(
175-
[
176-
{
177-
getLanguageId: uri => server.documents.get(uri)?.languageId,
178-
},
179-
createVueLanguagePlugin(
180-
ts,
181-
commonLine.options,
182-
commonLine.vueOptions,
183-
uri => uri.fsPath.replace(/\\/g, '/'),
184-
),
185-
],
186-
createUriMap(),
187-
uri => {
188-
const document = server.documents.get(uri);
189-
if (document) {
190-
language.scripts.set(uri, document.getSnapshot(), document.languageId);
191-
}
192-
else {
193-
language.scripts.delete(uri);
194-
}
195-
},
196-
);
197-
return createLanguageService(
198-
language,
199-
server.languageServicePlugins,
200-
createLanguageServiceEnvironment(server, [...server.workspaceFolders.all]),
201-
{},
202-
);
20315
}
204-
});
205-
206-
connection.onInitialized(server.initialized);
207-
208-
connection.onShutdown(server.shutdown);
16+
ts ??= require('typescript');
17+
startServer(ts);
18+
}

0 commit comments

Comments
 (0)