|
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'; |
2 | 3 | import { createLanguageServiceEnvironment } from '@volar/language-server/lib/project/simpleProject';
|
3 | 4 | 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'; |
6 | 7 | import * as ts from 'typescript';
|
7 | 8 | import { URI } from 'vscode-uri';
|
| 9 | +import { analyze } from './lib/reactionsAnalyze'; |
| 10 | +import { getLanguageService } from './lib/reactionsAnalyzeLS'; |
8 | 11 |
|
9 | 12 | const connection = createConnection();
|
10 | 13 | const server = createServer(connection);
|
@@ -186,3 +189,183 @@ connection.onInitialize(params => {
|
186 | 189 | connection.onInitialized(server.initialized);
|
187 | 190 |
|
188 | 191 | 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