Skip to content

Commit 7a2ea48

Browse files
refactor(language-server): move in server code from insiders edition (#5423)
1 parent 4599d20 commit 7a2ea48

File tree

6 files changed

+731
-25
lines changed

6 files changed

+731
-25
lines changed

extensions/vscode/lib/splitEditors.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ExecuteCommandRequest, type BaseLanguageClient, type ExecuteCommandParams } from '@volar/vscode';
1+
import { type BaseLanguageClient } from '@volar/vscode';
22
import type { SFCBlock, SFCParseResult } from '@vue/compiler-sfc';
33
import { executeCommand, useActiveTextEditor, useCommand } from 'reactive-vscode';
44
import * as vscode from 'vscode';
@@ -20,10 +20,9 @@ export function activate(client: BaseLanguageClient) {
2020
if (!astMap.has(doc) || astMap.get(doc)![0] !== doc.version) {
2121
astMap.set(doc, [
2222
doc.version,
23-
client.sendRequest(ExecuteCommandRequest.type, {
24-
command: 'vue.parseSfc',
25-
arguments: [doc.getText()],
26-
} satisfies ExecuteCommandParams),
23+
client.sendRequest('vue/parseSfc', {
24+
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(doc),
25+
}),
2726
]);
2827
}
2928
const ast = await astMap.get(doc)![1];

packages/language-core/lib/parsers/scriptSetupRanges.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ export function parseBindingRanges(ts: typeof import('typescript'), ast: ts.Sour
451451
}
452452
}
453453

454-
function findBindingVars(
454+
export function findBindingVars(
455455
ts: typeof import('typescript'),
456456
left: ts.BindingName,
457457
ast: ts.SourceFile

packages/language-server/index.ts

Lines changed: 186 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
import type { LanguageServer } from '@volar/language-server';
1+
import type { LanguageServer, Position, TextDocumentIdentifier } from '@volar/language-server';
2+
import { type Range, TextDocument } from '@volar/language-server';
23
import { createLanguageServiceEnvironment } from '@volar/language-server/lib/project/simpleProject';
34
import { createConnection, createServer } from '@volar/language-server/node';
4-
import { createLanguage, createParsedCommandLine, createVueLanguagePlugin, getDefaultCompilerOptions } from '@vue/language-core';
5-
import { createLanguageService, createUriMap, createVueLanguageServicePlugins, type LanguageService } from '@vue/language-service';
5+
import { createLanguage, createParsedCommandLine, createVueLanguagePlugin, forEachEmbeddedCode, getDefaultCompilerOptions, isReferencesEnabled, parse } from '@vue/language-core';
6+
import { createLanguageService, createUriMap, createVueLanguageServicePlugins, type DocumentsAndMap, getSourceRange, type LanguageService } from '@vue/language-service';
67
import * as ts from 'typescript';
78
import { URI } from 'vscode-uri';
9+
import { analyze } from './lib/reactionsAnalyze';
10+
import { getLanguageService } from './lib/reactionsAnalyzeLS';
811

912
const connection = createConnection();
1013
const server = createServer(connection);
@@ -186,3 +189,183 @@ connection.onInitialize(params => {
186189
connection.onInitialized(server.initialized);
187190

188191
connection.onShutdown(server.shutdown);
192+
193+
connection.onRequest('vue/parseSfc', (params: {
194+
textDocument: TextDocumentIdentifier;
195+
}) => {
196+
const document = server.documents.get(URI.parse(params.textDocument.uri));
197+
if (document) {
198+
return parse(document.getText());
199+
}
200+
});
201+
202+
connection.onRequest('vue/interpolationRanges', async (params: {
203+
textDocument: TextDocumentIdentifier;
204+
}): Promise<[number, number][]> => {
205+
const uri = URI.parse(params.textDocument.uri);
206+
const languageService = await server.project.getLanguageService(uri);
207+
if (languageService) {
208+
const sourceFile = languageService.context.language.scripts.get(uri);
209+
if (sourceFile?.generated) {
210+
const ranges: [number, number][] = [];
211+
for (const code of forEachEmbeddedCode(sourceFile.generated.root)) {
212+
const codeText = code.snapshot.getText(0, code.snapshot.getLength());
213+
if (
214+
(
215+
code.id.startsWith('template_inline_ts_')
216+
&& codeText.startsWith('0 +')
217+
&& codeText.endsWith('+ 0;')
218+
)
219+
|| (code.id.startsWith('style_') && code.id.endsWith('_inline_ts'))
220+
) {
221+
for (const mapping of code.mappings) {
222+
for (let i = 0; i < mapping.sourceOffsets.length; i++) {
223+
ranges.push([
224+
mapping.sourceOffsets[i],
225+
mapping.sourceOffsets[i] + mapping.lengths[i],
226+
]);
227+
}
228+
}
229+
}
230+
}
231+
return ranges;
232+
}
233+
}
234+
return [];
235+
});
236+
237+
const cacheDocuments = new Map<string, [TextDocument, import('typescript').IScriptSnapshot]>();
238+
239+
connection.onRequest('vue/reactionsAnalyze', async (params: {
240+
textDocument: TextDocumentIdentifier;
241+
position: Position;
242+
syncDocument?: {
243+
content: string;
244+
languageId: string;
245+
};
246+
}): Promise<{
247+
subscribers: Range[];
248+
dependencies: Range[];
249+
} | undefined> => {
250+
if (params.syncDocument) {
251+
const document = TextDocument.create(params.textDocument.uri, params.syncDocument.languageId, 0, params.syncDocument.content);
252+
const snapshot = ts.ScriptSnapshot.fromString(params.syncDocument.content);
253+
cacheDocuments.set(params.textDocument.uri, [document, snapshot]);
254+
}
255+
const uri = URI.parse(params.textDocument.uri);
256+
const languageService = await server.project.getLanguageService(uri);
257+
const sourceScript = languageService.context.language.scripts.get(uri);
258+
let document: TextDocument | undefined;
259+
let snapshot: import('typescript').IScriptSnapshot | undefined;
260+
if (sourceScript) {
261+
document = languageService.context.documents.get(sourceScript.id, sourceScript.languageId, sourceScript.snapshot);
262+
snapshot = sourceScript.snapshot;
263+
}
264+
else if (cacheDocuments.has(params.textDocument.uri)) {
265+
const [doc, snap] = cacheDocuments.get(params.textDocument.uri)!;
266+
document = doc;
267+
snapshot = snap;
268+
}
269+
if (!document || !snapshot) {
270+
return;
271+
}
272+
let offset = document.offsetAt(params.position);
273+
if (sourceScript?.generated) {
274+
const serviceScript = sourceScript.generated.languagePlugin.typescript?.getServiceScript(sourceScript.generated.root);
275+
if (!serviceScript) {
276+
return;
277+
}
278+
const map = languageService.context.language.maps.get(serviceScript.code, sourceScript);
279+
let embeddedOffset: number | undefined;
280+
for (const [mapped, mapping] of map.toGeneratedLocation(offset)) {
281+
if (isReferencesEnabled(mapping.data)) {
282+
embeddedOffset = mapped;
283+
break;
284+
}
285+
}
286+
if (embeddedOffset === undefined) {
287+
return;
288+
}
289+
offset = embeddedOffset;
290+
291+
const embeddedUri = languageService.context.encodeEmbeddedDocumentUri(sourceScript.id, serviceScript.code.id);
292+
document = languageService.context.documents.get(embeddedUri, serviceScript.code.languageId, serviceScript.code.snapshot);
293+
snapshot = serviceScript.code.snapshot;
294+
}
295+
const { languageService: tsLs, fileName } = getLanguageService(ts, snapshot, document.languageId);
296+
const result = analyze(ts, tsLs, fileName, offset);
297+
if (!result) {
298+
return;
299+
}
300+
const subscribers: Range[] = [];
301+
const dependencies: Range[] = [];
302+
if (sourceScript?.generated) {
303+
const serviceScript = sourceScript.generated.languagePlugin.typescript?.getServiceScript(sourceScript.generated.root);
304+
if (!serviceScript) {
305+
return;
306+
}
307+
const docs: DocumentsAndMap = [
308+
languageService.context.documents.get(sourceScript.id, sourceScript.languageId, sourceScript.snapshot),
309+
document,
310+
languageService.context.language.maps.get(serviceScript.code, sourceScript),
311+
];
312+
for (const dependency of result.dependencies) {
313+
let start = document.positionAt(dependency.getStart(result.sourceFile));
314+
let end = document.positionAt(dependency.getEnd());
315+
if (ts.isBlock(dependency) && dependency.statements.length) {
316+
const { statements } = dependency;
317+
start = document.positionAt(statements[0].getStart(result.sourceFile));
318+
end = document.positionAt(statements[statements.length - 1].getEnd());
319+
}
320+
const sourceRange = getSourceRange(docs, { start, end });
321+
if (sourceRange) {
322+
dependencies.push(sourceRange);
323+
}
324+
}
325+
for (const subscriber of result.subscribers) {
326+
if (!subscriber.sideEffectInfo) {
327+
continue;
328+
}
329+
let start = document.positionAt(subscriber.sideEffectInfo.handler.getStart(result.sourceFile));
330+
let end = document.positionAt(subscriber.sideEffectInfo.handler.getEnd());
331+
if (ts.isBlock(subscriber.sideEffectInfo.handler) && subscriber.sideEffectInfo.handler.statements.length) {
332+
const { statements } = subscriber.sideEffectInfo.handler;
333+
start = document.positionAt(statements[0].getStart(result.sourceFile));
334+
end = document.positionAt(statements[statements.length - 1].getEnd());
335+
}
336+
const sourceRange = getSourceRange(docs, { start, end });
337+
if (sourceRange) {
338+
subscribers.push(sourceRange);
339+
}
340+
}
341+
}
342+
else {
343+
for (const dependency of result.dependencies) {
344+
let start = document.positionAt(dependency.getStart(result.sourceFile));
345+
let end = document.positionAt(dependency.getEnd());
346+
if (ts.isBlock(dependency) && dependency.statements.length) {
347+
const { statements } = dependency;
348+
start = document.positionAt(statements[0].getStart(result.sourceFile));
349+
end = document.positionAt(statements[statements.length - 1].getEnd());
350+
}
351+
dependencies.push({ start, end });
352+
}
353+
for (const subscriber of result.subscribers) {
354+
if (!subscriber.sideEffectInfo) {
355+
continue;
356+
}
357+
let start = document.positionAt(subscriber.sideEffectInfo.handler.getStart(result.sourceFile));
358+
let end = document.positionAt(subscriber.sideEffectInfo.handler.getEnd());
359+
if (ts.isBlock(subscriber.sideEffectInfo.handler) && subscriber.sideEffectInfo.handler.statements.length) {
360+
const { statements } = subscriber.sideEffectInfo.handler;
361+
start = document.positionAt(statements[0].getStart(result.sourceFile));
362+
end = document.positionAt(statements[statements.length - 1].getEnd());
363+
}
364+
subscribers.push({ start, end });
365+
}
366+
}
367+
return {
368+
subscribers,
369+
dependencies,
370+
};
371+
});

0 commit comments

Comments
 (0)