From 72345cde7d23fd331fd0e5befaa2918e33645949 Mon Sep 17 00:00:00 2001 From: Johnson Chu Date: Fri, 1 Mar 2024 15:57:17 +0800 Subject: [PATCH] fix(language-service): wait for tsserver to be ready when requesting auto insert `.value` (#3914) --- .../lib/plugins/vue-autoinsert-dotvalue.ts | 45 ++++++++++++++----- .../lib/requests/getPropertiesAtLocation.ts | 34 +++++++++++++- packages/typescript-plugin/lib/server.ts | 22 +++++---- 3 files changed, 79 insertions(+), 22 deletions(-) diff --git a/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts b/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts index a733659707..03d488c6e0 100644 --- a/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts +++ b/packages/language-service/lib/plugins/vue-autoinsert-dotvalue.ts @@ -10,6 +10,7 @@ export function create(ts: typeof import('typescript')): ServicePlugin { return { name: 'vue-autoinsert-dotvalue', create(context): ServicePluginInstance { + let currentReq = 0; return { async provideAutoInsertionEdit(document, position, lastChange) { @@ -19,34 +20,52 @@ export function create(ts: typeof import('typescript')): ServicePlugin { if (!isCharacterTyping(document, lastChange)) return; + const req = ++currentReq; + // Wait for tsserver to sync + await sleep(250); + if (req !== currentReq) + return; + const enabled = await context.env.getConfiguration?.('vue.autoInsert.dotValue') ?? true; if (!enabled) return; - const [_, file] = context.documents.getVirtualCodeByUri(document.uri); + const [code, file] = context.documents.getVirtualCodeByUri(document.uri); + if (!file) + return; - let fileName: string | undefined; let ast: ts.SourceFile | undefined; + let sourceCodeOffset = document.offsetAt(position); + + const fileName = context.env.typescript!.uriToFileName(file.id); if (file?.generated) { const script = file.generated.languagePlugin.typescript?.getScript(file.generated.code); - if (script) { - fileName = context.env.typescript!.uriToFileName(file.id); - ast = getAst(fileName, script.code.snapshot, script.scriptKind); + if (script?.code !== code) { + return; + } + ast = getAst(fileName, script.code.snapshot, script.scriptKind); + let mapped = false; + for (const [_1, [_2, map]] of context.language.files.getMaps(code)) { + const sourceOffset = map.getSourceOffset(document.offsetAt(position)); + if (sourceOffset !== undefined) { + sourceCodeOffset = sourceOffset[0]; + mapped = true; + break; + } + } + if (!mapped) { + return; } } - else if (file) { - fileName = context.env.typescript!.uriToFileName(file.id); + else { ast = getAst(fileName, file.snapshot); } - if (!ast || !fileName) - return; - if (isBlacklistNode(ts, ast, document.offsetAt(position), false)) return; - const props = await namedPipeClient.getPropertiesAtLocation(fileName, document.offsetAt(position)) ?? []; + const props = await namedPipeClient.getPropertiesAtLocation(fileName, sourceCodeOffset) ?? []; if (props.some(prop => prop === 'value')) { return '${1:.value}'; } @@ -56,6 +75,10 @@ export function create(ts: typeof import('typescript')): ServicePlugin { }; } +function sleep(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + function isTsDocument(document: TextDocument) { return document.languageId === 'javascript' || document.languageId === 'typescript' || diff --git a/packages/typescript-plugin/lib/requests/getPropertiesAtLocation.ts b/packages/typescript-plugin/lib/requests/getPropertiesAtLocation.ts index aa8eb171bd..fd39eded66 100644 --- a/packages/typescript-plugin/lib/requests/getPropertiesAtLocation.ts +++ b/packages/typescript-plugin/lib/requests/getPropertiesAtLocation.ts @@ -1,3 +1,4 @@ +import { isCompletionEnabled } from '@vue/language-core'; import { getProject } from '../utils'; import type * as ts from 'typescript'; @@ -10,6 +11,36 @@ export function getPropertiesAtLocation(fileName: string, position: number, isTs const { info, files, ts } = match; const languageService = info.languageService; + + // mapping + const file = files.get(fileName); + if (file?.generated) { + const virtualScript = file.generated.languagePlugin.typescript?.getScript(file.generated.code); + if (!virtualScript) { + return; + } + let mapped = false; + for (const [_1, [_2, map]] of files.getMaps(virtualScript.code)) { + for (const [position2, mapping] of map.getGeneratedOffsets(position)) { + if (isCompletionEnabled(mapping.data)) { + position = position2; + mapped = true; + break; + } + } + if (mapped) { + break; + } + } + if (!mapped) { + return; + } + if (isTsPlugin) { + position += file.snapshot.getLength(); + } + } + + const program: ts.Program = (languageService as any).getCurrentProgram(); if (!program) { return; @@ -20,8 +51,7 @@ export function getPropertiesAtLocation(fileName: string, position: number, isTs return; } - const volarFile = files.get(fileName); - const node = findPositionIdentifier(sourceFile, sourceFile, position + (isTsPlugin ? (volarFile?.snapshot.getLength() ?? 0) : 0)); + const node = findPositionIdentifier(sourceFile, sourceFile, position); if (!node) { return; } diff --git a/packages/typescript-plugin/lib/server.ts b/packages/typescript-plugin/lib/server.ts index 62f49afb54..3e99faaf47 100644 --- a/packages/typescript-plugin/lib/server.ts +++ b/packages/typescript-plugin/lib/server.ts @@ -1,5 +1,9 @@ import * as fs from 'fs'; import * as net from 'net'; +import { collectExtractProps } from './requests/collectExtractProps'; +import { getComponentEvents, getComponentNames, getComponentProps, getElementAttrs, getTemplateContextProps } from './requests/componentInfos'; +import { getPropertiesAtLocation } from './requests/getPropertiesAtLocation'; +import { getQuickInfoAtPosition } from './requests/getQuickInfoAtPosition'; import { pipeFile } from './utils'; export interface Request { @@ -21,39 +25,39 @@ export function startNamedPipeServer() { if (started) return; started = true; const server = net.createServer(connection => { - connection.on('data', async data => { + connection.on('data', data => { const request: Request = JSON.parse(data.toString()); if (request.type === 'collectExtractProps') { - const result = (await import('./requests/collectExtractProps.js')).collectExtractProps.apply(null, request.args); + const result = collectExtractProps.apply(null, request.args); connection.write(JSON.stringify(result ?? null)); } else if (request.type === 'getPropertiesAtLocation') { - const result = (await import('./requests/getPropertiesAtLocation.js')).getPropertiesAtLocation.apply(null, request.args); + const result = getPropertiesAtLocation.apply(null, request.args); connection.write(JSON.stringify(result ?? null)); } else if (request.type === 'getQuickInfoAtPosition') { - const result = (await import('./requests/getQuickInfoAtPosition.js')).getQuickInfoAtPosition.apply(null, request.args); + const result = getQuickInfoAtPosition.apply(null, request.args); connection.write(JSON.stringify(result ?? null)); } // Component Infos else if (request.type === 'getComponentProps') { - const result = (await import('./requests/componentInfos.js')).getComponentProps.apply(null, request.args); + const result = getComponentProps.apply(null, request.args); connection.write(JSON.stringify(result ?? null)); } else if (request.type === 'getComponentEvents') { - const result = (await import('./requests/componentInfos.js')).getComponentEvents.apply(null, request.args); + const result = getComponentEvents.apply(null, request.args); connection.write(JSON.stringify(result ?? null)); } else if (request.type === 'getTemplateContextProps') { - const result = (await import('./requests/componentInfos.js')).getTemplateContextProps.apply(null, request.args); + const result = getTemplateContextProps.apply(null, request.args); connection.write(JSON.stringify(result ?? null)); } else if (request.type === 'getComponentNames') { - const result = (await import('./requests/componentInfos.js')).getComponentNames.apply(null, request.args); + const result = getComponentNames.apply(null, request.args); connection.write(JSON.stringify(result ?? null)); } else if (request.type === 'getElementAttrs') { - const result = (await import('./requests/componentInfos.js')).getElementAttrs.apply(null, request.args); + const result = getElementAttrs.apply(null, request.args); connection.write(JSON.stringify(result ?? null)); } else {