diff --git a/extensions/vscode-vue-language-features/README.md b/extensions/vscode-vue-language-features/README.md index e481215b1..d5656e408 100644 --- a/extensions/vscode-vue-language-features/README.md +++ b/extensions/vscode-vue-language-features/README.md @@ -104,10 +104,6 @@ export {} -## Limitations - -- Due to performance, *.ts content update don't update template diagnosis for now. ([#565](https://github.com/johnsoncodehk/volar/issues/565)) (Block by [microsoft/TypeScript#41051](https://github.com/microsoft/TypeScript/issues/41051)) - ## Notes ### Vetur diff --git a/extensions/vscode-vue-language-features/src/common.ts b/extensions/vscode-vue-language-features/src/common.ts index 12b2578fc..30d7f9db8 100644 --- a/extensions/vscode-vue-language-features/src/common.ts +++ b/extensions/vscode-vue-language-features/src/common.ts @@ -137,7 +137,7 @@ async function doActivate(context: vscode.ExtensionContext, createLc: CreateLang vscode.workspace.onDidChangeConfiguration(async () => { const nowUseSecondServer = useSecondServer(); if (_useSecondServer !== nowUseSecondServer) { - const reload = await vscode.window.showInformationMessage('Please reload VSCode to switch low power mode.', 'Reload Window'); + const reload = await vscode.window.showInformationMessage('Please reload VSCode to restart language servers.', 'Reload Window'); if (reload === undefined) return; // cancel vscode.commands.executeCommand('workbench.action.reloadWindow'); } @@ -240,7 +240,6 @@ function getInitializationOptions( documentColor: true, documentFormatting: true, } : undefined, - initializationMessage: initMessage, }; return initializationOptions; } diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts index 6e4ef56ae..5cb477033 100644 --- a/packages/shared/src/types.ts +++ b/packages/shared/src/types.ts @@ -81,5 +81,4 @@ export interface ServerInitializationOptions { documentColor?: boolean documentFormatting?: boolean, } - initializationMessage?: string } diff --git a/packages/typescript-vue-plugin/src/index.ts b/packages/typescript-vue-plugin/src/index.ts index 6ede17f04..57a2404e0 100644 --- a/packages/typescript-vue-plugin/src/index.ts +++ b/packages/typescript-vue-plugin/src/index.ts @@ -34,22 +34,22 @@ const init: ts.server.PluginModuleFactory = (modules) => { }); const _tsPluginApis = apis.register(tsRuntime); const tsPluginProxy: Partial = { - getSemanticDiagnostics: apiHook(tsRuntime.getTsLs().getSemanticDiagnostics, false), - getEncodedSemanticClassifications: apiHook(tsRuntime.getTsLs().getEncodedSemanticClassifications, false), - getCompletionsAtPosition: apiHook(_tsPluginApis.getCompletionsAtPosition, false), - getCompletionEntryDetails: apiHook(tsRuntime.getTsLs().getCompletionEntryDetails, false), // not sure - getCompletionEntrySymbol: apiHook(tsRuntime.getTsLs().getCompletionEntrySymbol, false), // not sure - getQuickInfoAtPosition: apiHook(tsRuntime.getTsLs().getQuickInfoAtPosition, false), - getSignatureHelpItems: apiHook(tsRuntime.getTsLs().getSignatureHelpItems, false), - getRenameInfo: apiHook(tsRuntime.getTsLs().getRenameInfo, false), + getSemanticDiagnostics: apiHook(tsRuntime.getTsLs().getSemanticDiagnostics), + getEncodedSemanticClassifications: apiHook(tsRuntime.getTsLs().getEncodedSemanticClassifications), + getCompletionsAtPosition: apiHook(_tsPluginApis.getCompletionsAtPosition), + getCompletionEntryDetails: apiHook(tsRuntime.getTsLs().getCompletionEntryDetails), + getCompletionEntrySymbol: apiHook(tsRuntime.getTsLs().getCompletionEntrySymbol), + getQuickInfoAtPosition: apiHook(tsRuntime.getTsLs().getQuickInfoAtPosition), + getSignatureHelpItems: apiHook(tsRuntime.getTsLs().getSignatureHelpItems), + getRenameInfo: apiHook(tsRuntime.getTsLs().getRenameInfo), - findRenameLocations: apiHook(_tsPluginApis.findRenameLocations, true), - getDefinitionAtPosition: apiHook(_tsPluginApis.getDefinitionAtPosition, false), - getDefinitionAndBoundSpan: apiHook(_tsPluginApis.getDefinitionAndBoundSpan, false), - getTypeDefinitionAtPosition: apiHook(_tsPluginApis.getTypeDefinitionAtPosition, false), - getImplementationAtPosition: apiHook(_tsPluginApis.getImplementationAtPosition, false), - getReferencesAtPosition: apiHook(_tsPluginApis.getReferencesAtPosition, true), - findReferences: apiHook(_tsPluginApis.findReferences, true), + findRenameLocations: apiHook(_tsPluginApis.findRenameLocations), + getDefinitionAtPosition: apiHook(_tsPluginApis.getDefinitionAtPosition), + getDefinitionAndBoundSpan: apiHook(_tsPluginApis.getDefinitionAndBoundSpan), + getTypeDefinitionAtPosition: apiHook(_tsPluginApis.getTypeDefinitionAtPosition), + getImplementationAtPosition: apiHook(_tsPluginApis.getImplementationAtPosition), + getReferencesAtPosition: apiHook(_tsPluginApis.getReferencesAtPosition), + findReferences: apiHook(_tsPluginApis.findReferences), // TODO: now is handle by vue server // prepareCallHierarchy: apiHook(tsLanguageService.rawLs.prepareCallHierarchy, false), @@ -75,12 +75,10 @@ const init: ts.server.PluginModuleFactory = (modules) => { function apiHook any>( api: T, - shouldUpdateTemplateScript: boolean | ((...args: Parameters) => boolean) = true, ) { const handler = { apply(target: (...args: any) => any, thisArg: any, argumentsList: Parameters) { - const _shouldUpdateTemplateScript = typeof shouldUpdateTemplateScript === 'boolean' ? shouldUpdateTemplateScript : shouldUpdateTemplateScript.apply(null, argumentsList); - tsRuntime.update(_shouldUpdateTemplateScript); + tsRuntime.update(); return target.apply(thisArg, argumentsList); } }; diff --git a/packages/vue-code-gen/src/generators/template.ts b/packages/vue-code-gen/src/generators/template.ts index 3ceac7ae6..e079fd476 100644 --- a/packages/vue-code-gen/src/generators/template.ts +++ b/packages/vue-code-gen/src/generators/template.ts @@ -1,10 +1,10 @@ import * as SourceMaps from '@volar/source-map'; import { CodeGen } from '@volar/code-gen'; -import { camelize, hyphenate, isHTMLTag, isSVGTag, isGloballyWhitelisted } from '@vue/shared'; +import { camelize, hyphenate, isHTMLTag, isSVGTag } from '@vue/shared'; import * as CompilerDOM from '@vue/compiler-dom'; import * as CompilerCore from '@vue/compiler-core'; import { EmbeddedFileMappingData } from '../types'; -import type * as ts from 'typescript/lib/tsserverlibrary'; +import { colletVars, walkInterpolationFragment } from '../transform'; const capabilitiesSet = { all: { basic: true, diagnostic: true, references: true, definitions: true, rename: true, completion: true, semanticTokens: true }, @@ -269,110 +269,6 @@ export function generate( attrNames, }; - function walkInterpolationFragment(code: string, cb: (fragment: string, offset: number | undefined) => void) { - - let ctxVarOffsets: number[] = []; - let localVarOffsets: number[] = []; - - const ast = ts.createSourceFile('/foo.ts', code, ts.ScriptTarget.ESNext); - const varCb = (localVar: ts.Identifier) => { - if (!!localVars[localVar.text] || isGloballyWhitelisted(localVar.text)) { - localVarOffsets.push(localVar.getStart(ast)); - } - else { - ctxVarOffsets.push(localVar.getStart(ast)); - } - }; - ast.forEachChild(node => walkIdentifiers(node, varCb)); - - ctxVarOffsets = ctxVarOffsets.sort((a, b) => a - b); - localVarOffsets = localVarOffsets.sort((a, b) => a - b); - - if (ctxVarOffsets.length) { - - cb(code.substring(0, ctxVarOffsets[0]), 0); - - for (let i = 0; i < ctxVarOffsets.length - 1; i++) { - cb('__VLS_ctx.', undefined); - cb(code.substring(ctxVarOffsets[i], ctxVarOffsets[i + 1]), ctxVarOffsets[i]); - } - - cb('__VLS_ctx.', undefined); - cb(code.substring(ctxVarOffsets[ctxVarOffsets.length - 1]), ctxVarOffsets[ctxVarOffsets.length - 1]); - } - else { - cb(code, 0); - } - } - function walkIdentifiers(node: ts.Node, cb: (varNode: ts.Identifier) => void) { - - const blockVars: string[] = []; - - if (ts.isIdentifier(node)) { - cb(node); - } - else if (ts.isPropertyAccessExpression(node)) { - walkIdentifiers(node.expression, cb); - } - else if (ts.isVariableDeclaration(node)) { - - colletVars(node.name, blockVars); - - for (const varName of blockVars) - localVars[varName] = (localVars[varName] ?? 0) + 1; - - if (node.initializer) - walkIdentifiers(node.initializer, cb); - } - else if (ts.isArrowFunction(node)) { - - const functionArgs: string[] = []; - - for (const param of node.parameters) - colletVars(param.name, functionArgs); - - for (const varName of functionArgs) - localVars[varName] = (localVars[varName] ?? 0) + 1; - - walkIdentifiers(node.body, cb); - - for (const varName of functionArgs) - localVars[varName]--; - } - else if (ts.isObjectLiteralExpression(node)) { - for (const prop of node.properties) { - if (ts.isPropertyAssignment(prop)) { - walkIdentifiers(prop.initializer, cb); - } - } - } - else { - node.forEachChild(node => walkIdentifiers(node, cb)); - } - - for (const varName of blockVars) - localVars[varName]--; - } - function colletVars(node: ts.Node, result: string[]) { - if (ts.isIdentifier(node)) { - result.push(node.text); - } - else if (ts.isObjectBindingPattern(node)) { - for (const el of node.elements) { - colletVars(el.name, result); - } - } - else if (ts.isArrayBindingPattern(node)) { - for (const el of node.elements) { - if (ts.isBindingElement(el)) { - colletVars(el.name, result); - } - } - } - else { - node.forEachChild(node => colletVars(node, result)); - } - } function collectTags(node: CompilerDOM.TemplateChildNode) { if (node.type === CompilerDOM.NodeTypes.ELEMENT) { const patchForNode = getPatchForSlotNode(node); @@ -551,7 +447,7 @@ export function generate( if (leftExpressionRange && leftExpressionText) { const collentAst = ts.createSourceFile('/foo.ts', `const [${leftExpressionText}]`, ts.ScriptTarget.ESNext); - colletVars(collentAst, forBlockVars); + colletVars(ts, collentAst, forBlockVars); for (const varName of forBlockVars) localVars[varName] = (localVars[varName] ?? 0) + 1; @@ -1322,7 +1218,7 @@ export function generate( tsCodeGen.addText(`const `); const collentAst = ts.createSourceFile('/foo.ts', `const ${prop.exp.content}`, ts.ScriptTarget.ESNext); - colletVars(collentAst, slotBlockVars); + colletVars(ts, collentAst, slotBlockVars); writeCode( prop.exp.content, @@ -1760,10 +1656,18 @@ export function generate( } } function writeObjectProperty(mapCode: string, sourceRange: SourceMaps.Range, mapMode: SourceMaps.Mode, data: EmbeddedFileMappingData) { - if (validTsVar.test(mapCode) || (mapCode.startsWith('[') && mapCode.endsWith(']'))) { + if (validTsVar.test(mapCode)) { writeCode(mapCode, sourceRange, mapMode, data); return 1; } + else if (mapCode.startsWith('[') && mapCode.endsWith(']')) { + writeInterpolation( + mapCode, + sourceRange.start, + data, + ); + return 1; + } else { writeCodeWithQuotes(mapCode, sourceRange, data); return 2; @@ -1863,7 +1767,7 @@ export function generate( prefix = '', suffix = '', ) { - walkInterpolationFragment(prefix + mapCode + suffix, (frag, fragOffset) => { + walkInterpolationFragment(ts, prefix + mapCode + suffix, (frag, fragOffset) => { if (fragOffset === undefined) { tsCodeGen.addText(frag); } @@ -1891,7 +1795,7 @@ export function generate( ); tsCodeGen.addText(addSubfix); } - }); + }, localVars); } function writeFormatCode(mapCode: string, sourceOffset: number, formatWrapper: [string, string]) { tsFormatCodeGen.addText(formatWrapper[0]); diff --git a/packages/vue-code-gen/src/transform.ts b/packages/vue-code-gen/src/transform.ts new file mode 100644 index 000000000..709cea077 --- /dev/null +++ b/packages/vue-code-gen/src/transform.ts @@ -0,0 +1,130 @@ +import { isGloballyWhitelisted } from '@vue/shared'; +import type * as ts from 'typescript/lib/tsserverlibrary'; + +export function walkInterpolationFragment( + ts: typeof import('typescript/lib/tsserverlibrary'), + code: string, + cb: (fragment: string, offset: number | undefined) => void, + localVars: Record, +) { + + let ctxVarOffsets: number[] = []; + let localVarOffsets: number[] = []; + + const ast = ts.createSourceFile('/foo.ts', code, ts.ScriptTarget.ESNext); + const varCb = (localVar: ts.Identifier) => { + if ( + !!localVars[localVar.text] || + isGloballyWhitelisted(localVar.text) || + localVar.text === '$style' + ) { + localVarOffsets.push(localVar.getStart(ast)); + } + else { + ctxVarOffsets.push(localVar.getStart(ast)); + } + }; + ast.forEachChild(node => walkIdentifiers(ts, node, varCb, localVars)); + + ctxVarOffsets = ctxVarOffsets.sort((a, b) => a - b); + localVarOffsets = localVarOffsets.sort((a, b) => a - b); + + if (ctxVarOffsets.length) { + + cb(code.substring(0, ctxVarOffsets[0]), 0); + + for (let i = 0; i < ctxVarOffsets.length - 1; i++) { + cb('__VLS_ctx.', undefined); + cb(code.substring(ctxVarOffsets[i], ctxVarOffsets[i + 1]), ctxVarOffsets[i]); + } + + cb('__VLS_ctx.', undefined); + cb(code.substring(ctxVarOffsets[ctxVarOffsets.length - 1]), ctxVarOffsets[ctxVarOffsets.length - 1]); + } + else { + cb(code, 0); + } +} + +function walkIdentifiers( + ts: typeof import('typescript/lib/tsserverlibrary'), + node: ts.Node, + cb: (varNode: ts.Identifier) => void, + localVars: Record, +) { + + const blockVars: string[] = []; + + if (ts.isIdentifier(node)) { + cb(node); + } + else if (ts.isPropertyAccessExpression(node)) { + walkIdentifiers(ts, node.expression, cb, localVars); + } + else if (ts.isVariableDeclaration(node)) { + + colletVars(ts, node.name, blockVars); + + for (const varName of blockVars) + localVars[varName] = (localVars[varName] ?? 0) + 1; + + if (node.initializer) + walkIdentifiers(ts, node.initializer, cb, localVars); + } + else if (ts.isArrowFunction(node)) { + + const functionArgs: string[] = []; + + for (const param of node.parameters) + colletVars(ts, param.name, functionArgs); + + for (const varName of functionArgs) + localVars[varName] = (localVars[varName] ?? 0) + 1; + + walkIdentifiers(ts, node.body, cb, localVars); + + for (const varName of functionArgs) + localVars[varName]--; + } + else if (ts.isObjectLiteralExpression(node)) { + for (const prop of node.properties) { + if (ts.isPropertyAssignment(prop)) { + walkIdentifiers(ts, prop.initializer, cb, localVars); + } + } + } + else if (ts.isTypeReferenceNode(node)) { + // ignore + } + else { + node.forEachChild(node => walkIdentifiers(ts, node, cb, localVars)); + } + + for (const varName of blockVars) + localVars[varName]--; +} + +export function colletVars( + ts: typeof import('typescript/lib/tsserverlibrary'), + node: ts.Node, + result: string[], +) { + if (ts.isIdentifier(node)) { + result.push(node.text); + } + else if (ts.isObjectBindingPattern(node)) { + for (const el of node.elements) { + colletVars(ts, el.name, result); + } + } + else if (ts.isArrayBindingPattern(node)) { + for (const el of node.elements) { + if (ts.isBindingElement(el)) { + colletVars(ts, el.name, result); + } + } + } + else { + node.forEachChild(node => colletVars(ts, node, result)); + } +} diff --git a/packages/vue-language-server/src/project.ts b/packages/vue-language-server/src/project.ts index ebd489008..1ced3ffa2 100644 --- a/packages/vue-language-server/src/project.ts +++ b/packages/vue-language-server/src/project.ts @@ -84,7 +84,6 @@ export async function createProject( async function getLanguageService() { if (!vueLs) { vueLs = (async () => { - const workDoneProgress = await connection.window.createWorkDoneProgress(); const customPlugins = loadCustomPlugins(languageServiceHost.getCurrentDirectory()); const vueLs = vue.createLanguageService( { typescript: ts }, @@ -128,37 +127,11 @@ export async function createProject( }; } : undefined, ); - vueLs.__internal__.tsRuntime.onInitProgress(p => { - if (p === 0) { - workDoneProgress.begin(getMessageText()); - } - if (p < 1) { - workDoneProgress.report(p * 100); - } - else { - workDoneProgress.done(); - } - }); return vueLs; })(); } return vueLs; } - function getMessageText() { - let messageText = options.initializationMessage; - if (!messageText) { - let numOfFeatures = 0; - if (options.languageFeatures) { - for (let feature in options.languageFeatures) { - if (!!options.languageFeatures[feature as keyof typeof options.languageFeatures]) { - numOfFeatures++; - } - } - } - messageText = `Initializing Vue language features (${numOfFeatures} features)`; - } - return messageText; - } async function onWorkspaceFilesChanged(changes: vscode.FileEvent[]) { await Promise.all([...fileRenamings]); diff --git a/packages/vue-language-service/src/documentService.ts b/packages/vue-language-service/src/documentService.ts index 1e0e8a1c5..5c894f2ca 100644 --- a/packages/vue-language-service/src/documentService.ts +++ b/packages/vue-language-service/src/documentService.ts @@ -148,6 +148,8 @@ export function getDocumentService( context.typescript, 'Record', stylesheetExtra.getCssClasses, + undefined, + undefined, ); vueDoc = parseVueDocument(vueFile); diff --git a/packages/vue-language-service/src/ideFeatures/tagNameCase.ts b/packages/vue-language-service/src/ideFeatures/tagNameCase.ts index 2d5c8db60..87d73e549 100644 --- a/packages/vue-language-service/src/ideFeatures/tagNameCase.ts +++ b/packages/vue-language-service/src/ideFeatures/tagNameCase.ts @@ -54,7 +54,7 @@ export function register(context: LanguageServiceRuntimeContext) { } function getTagNameCase(vueDocument: VueDocument): 'both' | 'kebabCase' | 'pascalCase' | 'unsure' { - const components = vueDocument.file.getTemplateScriptData().components; + const components = vueDocument.file.getTemplateData().components; const tagNames = new Set(Object.keys(vueDocument.file.getTemplateTagNames() ?? {})); let anyComponentUsed = false; diff --git a/packages/vue-language-service/src/languageFeatures/validation.ts b/packages/vue-language-service/src/languageFeatures/validation.ts index a701d6199..a00d8e37f 100644 --- a/packages/vue-language-service/src/languageFeatures/validation.ts +++ b/packages/vue-language-service/src/languageFeatures/validation.ts @@ -5,7 +5,7 @@ import * as dedupe from '../utils/dedupe'; import { languageFeatureWorker } from '../utils/featureWorkers'; import { EmbeddedDocumentSourceMap } from '../vueDocuments'; -export function register(context: LanguageServiceRuntimeContext, updateTemplateScripts: () => void) { +export function register(context: LanguageServiceRuntimeContext) { const responseCache = new Map< string, @@ -30,9 +30,6 @@ export function register(context: LanguageServiceRuntimeContext, updateTemplateS } > >(); - const templateTsCache_semantic: typeof nonTsCache = new Map(); - const templateTsCache_syntactic: typeof nonTsCache = new Map(); - const templateTsCache_suggestion: typeof nonTsCache = new Map(); const scriptTsCache_semantic: typeof nonTsCache = new Map(); const scriptTsCache_syntactic: typeof nonTsCache = new Map(); const scriptTsCache_suggestion: typeof nonTsCache = new Map(); @@ -58,36 +55,10 @@ export function register(context: LanguageServiceRuntimeContext, updateTemplateS syntactic: true, }, nonTsCache, errors => cache.nonTs = errors ?? []); doResponse(); - - const vueDocument = context.vueDocuments.get(uri); - - if (vueDocument) { - - const lastUpdated = vueDocument.file.getLastUpdated(); - - const isScriptChanged = lastUpdated.script || lastUpdated.scriptSetup; - if (isScriptChanged) { - await worker(true, { syntactic: true }, templateTsCache_syntactic, errors => cache.templateTs_syntactic = errors ?? []); - await worker(true, { suggestion: true }, templateTsCache_suggestion, errors => cache.templateTs_suggestion = errors ?? []); - doResponse(); - if (!await isCancel?.()) - updateTemplateScripts(); - await worker(true, { semantic: true }, templateTsCache_semantic, errors => cache.templateTs_semantic = errors ?? []); - } - else { - await worker(true, { syntactic: true }, scriptTsCache_syntactic, errors => cache.scriptTs_syntactic = errors ?? []); - await worker(true, { suggestion: true }, scriptTsCache_suggestion, errors => cache.scriptTs_suggestion = errors ?? []); - doResponse(); - await worker(true, { semantic: true }, scriptTsCache_semantic, errors => cache.scriptTs_semantic = errors ?? []); - } - - } - else { - await worker(true, { syntactic: true }, scriptTsCache_syntactic, errors => cache.scriptTs_syntactic = errors ?? []); - await worker(true, { suggestion: true }, scriptTsCache_suggestion, errors => cache.scriptTs_suggestion = errors ?? []); - doResponse(); - await worker(true, { semantic: true }, scriptTsCache_semantic, errors => cache.scriptTs_semantic = errors ?? []); - } + await worker(true, { syntactic: true }, scriptTsCache_syntactic, errors => cache.scriptTs_syntactic = errors ?? []); + await worker(true, { suggestion: true }, scriptTsCache_suggestion, errors => cache.scriptTs_suggestion = errors ?? []); + doResponse(); + await worker(true, { semantic: true }, scriptTsCache_semantic, errors => cache.scriptTs_semantic = errors ?? []); return getErrors(); @@ -147,7 +118,7 @@ export function register(context: LanguageServiceRuntimeContext, updateTemplateS const pluginCache = cacheMap.get(plugin.id) ?? cacheMap.set(plugin.id, new Map()).get(plugin.id)!; const cache = pluginCache.get(document.uri); - const tsProjectVersion = isTs ? undefined : context.getTsLs()?.__internal__.host.getProjectVersion?.(); + const tsProjectVersion = isTs ? undefined : context.getTsLs().__internal__.host.getProjectVersion?.(); if (!isTs) { if (cache && cache.documentVersion === document.version) { diff --git a/packages/vue-language-service/src/languageService.ts b/packages/vue-language-service/src/languageService.ts index 2697f905b..1318d0ee6 100644 --- a/packages/vue-language-service/src/languageService.ts +++ b/packages/vue-language-service/src/languageService.ts @@ -258,40 +258,40 @@ export function createLanguageService( getPluginById: id => allPlugins.get(id), }; const _callHierarchy = callHierarchy.register(context); - const findReferences_internal = defineInternalApi(references.register(context), true); - const doCodeActions_internal = defineInternalApi(codeActions.register(context), false); - const doCodeActionResolve_internal = defineInternalApi(codeActionResolve.register(context), false); - const doValidation_internal = defineInternalApi(diagnostics.register(context, () => tsRuntime.update(true)), false); - const doRename_internal = defineInternalApi(rename.register(context), true); - const findTypeDefinition_internal = defineInternalApi(definition.register(context, 'findTypeDefinition', data => !!data.capabilities.definitions, data => !!data.capabilities.definitions), isTemplateScriptPosition); + const findReferences_internal = defineInternalApi(references.register(context)); + const doCodeActions_internal = defineInternalApi(codeActions.register(context)); + const doCodeActionResolve_internal = defineInternalApi(codeActionResolve.register(context)); + const doValidation_internal = defineInternalApi(diagnostics.register(context)); + const doRename_internal = defineInternalApi(rename.register(context)); + const findTypeDefinition_internal = defineInternalApi(definition.register(context, 'findTypeDefinition', data => !!data.capabilities.definitions, data => !!data.capabilities.definitions)); return { - doValidation: defineApi(diagnostics.register(context, () => tsRuntime.update(true)), false, false), - findReferences: defineApi(references.register(context), true), - findDefinition: defineApi(definition.register(context, 'findDefinition', data => !!data.capabilities.definitions, data => !!data.capabilities.definitions), isTemplateScriptPosition), - findTypeDefinition: defineApi(definition.register(context, 'findTypeDefinition', data => !!data.capabilities.definitions, data => !!data.capabilities.definitions), isTemplateScriptPosition), - findImplementations: defineApi(definition.register(context, 'findImplementations', data => !!data.capabilities.references, data => false), false), - prepareRename: defineApi(renamePrepare.register(context), isTemplateScriptPosition), - doRename: defineApi(rename.register(context), true), - getEditsForFileRename: defineApi(fileRename.register(context), false), - getSemanticTokens: defineApi(semanticTokens.register(context), false), - doHover: defineApi(hover.register(context), isTemplateScriptPosition), - doComplete: defineApi(completions.register(context), isTemplateScriptPosition), - doCodeActions: defineApi(codeActions.register(context), false), - doCodeActionResolve: defineApi(codeActionResolve.register(context), false), - doCompletionResolve: defineApi(completionResolve.register(context), false), - getSignatureHelp: defineApi(signatureHelp.register(context), false), - doCodeLens: defineApi(codeLens.register(context), false), - doCodeLensResolve: defineApi(codeLensResolve.register(context), false), - findDocumentHighlights: defineApi(documentHighlight.register(context), false), - findDocumentLinks: defineApi(documentLink.register(context), false), - findWorkspaceSymbols: defineApi(workspaceSymbol.register(context), false), - doAutoInsert: defineApi(autoInsert.register(context), false), - doExecuteCommand: defineApi(executeCommand.register(context), false), + doValidation: defineApi(diagnostics.register(context), false), + findReferences: defineApi(references.register(context)), + findDefinition: defineApi(definition.register(context, 'findDefinition', data => !!data.capabilities.definitions, data => !!data.capabilities.definitions)), + findTypeDefinition: defineApi(definition.register(context, 'findTypeDefinition', data => !!data.capabilities.definitions, data => !!data.capabilities.definitions)), + findImplementations: defineApi(definition.register(context, 'findImplementations', data => !!data.capabilities.references, data => false)), + prepareRename: defineApi(renamePrepare.register(context)), + doRename: defineApi(rename.register(context)), + getEditsForFileRename: defineApi(fileRename.register(context)), + getSemanticTokens: defineApi(semanticTokens.register(context)), + doHover: defineApi(hover.register(context)), + doComplete: defineApi(completions.register(context)), + doCodeActions: defineApi(codeActions.register(context)), + doCodeActionResolve: defineApi(codeActionResolve.register(context)), + doCompletionResolve: defineApi(completionResolve.register(context)), + getSignatureHelp: defineApi(signatureHelp.register(context)), + doCodeLens: defineApi(codeLens.register(context)), + doCodeLensResolve: defineApi(codeLensResolve.register(context)), + findDocumentHighlights: defineApi(documentHighlight.register(context)), + findDocumentLinks: defineApi(documentLink.register(context)), + findWorkspaceSymbols: defineApi(workspaceSymbol.register(context)), + doAutoInsert: defineApi(autoInsert.register(context)), + doExecuteCommand: defineApi(executeCommand.register(context)), callHierarchy: { - doPrepare: defineApi(_callHierarchy.doPrepare, isTemplateScriptPosition), - getIncomingCalls: defineApi(_callHierarchy.getIncomingCalls, true), - getOutgoingCalls: defineApi(_callHierarchy.getOutgoingCalls, true), + doPrepare: defineApi(_callHierarchy.doPrepare), + getIncomingCalls: defineApi(_callHierarchy.getIncomingCalls), + getOutgoingCalls: defineApi(_callHierarchy.getOutgoingCalls), }, dispose: () => { tsRuntime.dispose(); @@ -301,9 +301,9 @@ export function createLanguageService( tsRuntime, rootPath: vueLsHost.getCurrentDirectory(), context, - getContext: defineApi(() => context, true), + getContext: defineApi(() => context), // getD3: defineApi(d3.register(context), true), // unused for now - detectTagNameCase: defineApi(tagNameCase.register(context), true), + detectTagNameCase: defineApi(tagNameCase.register(context)), }, }; @@ -402,7 +402,6 @@ export function createLanguageService( getScriptContentVersion: tsRuntime.getScriptContentVersion, vueLsHost, vueDocuments, - updateTemplateScripts: () => tsRuntime.update(true), tsSettings, tsRuntime, }), @@ -442,37 +441,8 @@ export function createLanguageService( } : _languageSupportPlugin; return languageSupportPlugin; } - function isTemplateScriptPosition(uri: string, pos: vscode.Position) { - - const vueDocument = vueDocuments.get(uri); - if (!vueDocument) { - return false; - } - - for (const sourceMap of vueDocument.getSourceMaps()) { - if (sourceMap.embeddedFile.fileName.indexOf('.__VLS_template.') >= 0) { - for (const _ of sourceMap.getMappedRanges(pos, pos, data => - data.vueTag === 'template' - || data.vueTag === 'style' // handle CSS variable injection to fix https://github.com/johnsoncodehk/volar/issues/777 - )) { - return true; - } - } - } - - const embeddedTemplate = vueDocument.file.getEmbeddedTemplate(); - if (embeddedTemplate) { - const sourceMap = vueDocument.sourceMapsMap.get(embeddedTemplate); - for (const _ of sourceMap.getMappedRanges(pos)) { - return true; - } - } - - return false; - } function defineApi any>( api: T, - shouldUpdateTemplateScript: boolean | ((...args: Parameters) => boolean), blockNewRequest = true, ): (...args: Parameters) => Promise> { const handler = { @@ -480,8 +450,7 @@ export function createLanguageService( for (const runningRequest of blockingRequests) { await runningRequest; } - const _shouldUpdateTemplateScript = typeof shouldUpdateTemplateScript === 'boolean' ? shouldUpdateTemplateScript : shouldUpdateTemplateScript.apply(null, argumentsList); - tsRuntime.update(_shouldUpdateTemplateScript); + tsRuntime.update(); const runner = target.apply(thisArg, argumentsList); if (blockNewRequest && runner instanceof Promise) { blockingRequests.add(runner); @@ -494,12 +463,10 @@ export function createLanguageService( } function defineInternalApi any>( api: T, - shouldUpdateTemplateScript: boolean | ((...args: Parameters) => boolean), ): (...args: Parameters) => Promise> { const handler = { async apply(target: (...args: any) => any, thisArg: any, argumentsList: Parameters) { - const _shouldUpdateTemplateScript = typeof shouldUpdateTemplateScript === 'boolean' ? shouldUpdateTemplateScript : shouldUpdateTemplateScript.apply(null, argumentsList); - tsRuntime.update(_shouldUpdateTemplateScript); + tsRuntime.update(); return target.apply(thisArg, argumentsList); } }; diff --git a/packages/vue-language-service/src/types.ts b/packages/vue-language-service/src/types.ts index 5af143834..0f94bbe31 100644 --- a/packages/vue-language-service/src/types.ts +++ b/packages/vue-language-service/src/types.ts @@ -1,7 +1,7 @@ +import type * as ts2 from '@volar/typescript-language-service'; +import { EmbeddedLanguageServicePlugin } from '@volar/vue-language-service-types'; import type { TextDocument } from 'vscode-languageserver-textdocument'; import { LanguageServicePlugin } from './languageService'; -import { EmbeddedLanguageServicePlugin } from '@volar/vue-language-service-types'; -import type * as ts2 from '@volar/typescript-language-service'; import { VueDocument, VueDocuments } from './vueDocuments'; export { LanguageServiceHost } from '@volar/vue-typescript'; diff --git a/packages/vue-language-service/src/vuePlugins/tagNameCasingConversions.ts b/packages/vue-language-service/src/vuePlugins/tagNameCasingConversions.ts index 489949f77..9e0ac74cb 100644 --- a/packages/vue-language-service/src/vuePlugins/tagNameCasingConversions.ts +++ b/packages/vue-language-service/src/vuePlugins/tagNameCasingConversions.ts @@ -34,7 +34,7 @@ export default function (options: { const template = desc.template; const document = vueDocument.getDocument(); const edits: vscode.TextEdit[] = []; - const components = new Set(vueDocument.file.getTemplateScriptData().components); + const components = new Set(vueDocument.file.getTemplateData().components); const resolvedTags = vueDocument.file.refs.sfcTemplateScript.templateCodeGens.value?.tagNames ?? {}; let i = 0; diff --git a/packages/vue-language-service/src/vuePlugins/vueTemplateLanguage.ts b/packages/vue-language-service/src/vuePlugins/vueTemplateLanguage.ts index 5f345cc4f..d0a95e09d 100644 --- a/packages/vue-language-service/src/vuePlugins/vueTemplateLanguage.ts +++ b/packages/vue-language-service/src/vuePlugins/vueTemplateLanguage.ts @@ -1,8 +1,8 @@ import * as shared from '@volar/shared'; import { parseScriptRanges } from '@volar/vue-code-gen/out/parsers/scriptRanges'; -import { SearchTexts, TypeScriptRuntime } from '@volar/vue-typescript'; +import { SearchTexts, TypeScriptRuntime, VueFile } from '@volar/vue-typescript'; import { VueDocument, VueDocuments } from '../vueDocuments'; -import { computed, pauseTracking, ref, resetTracking } from '@vue/reactivity'; +import { pauseTracking, resetTracking } from '@vue/reactivity'; import { camelize, capitalize, hyphenate, isHTMLTag } from '@vue/shared'; import * as path from 'upath'; import * as html from 'vscode-html-languageservice'; @@ -11,7 +11,6 @@ import { TextDocument } from 'vscode-languageserver-textdocument'; import type * as ts from 'typescript/lib/tsserverlibrary'; import type * as ts2 from '@volar/typescript-language-service'; import type { LanguageServiceHost } from '../types'; -import { untrack } from '../utils/untrack'; import { EmbeddedLanguageServicePlugin, useConfigurationHost } from '@volar/vue-language-service-types'; import useHtmlPlugin from '../commonPlugins/html'; @@ -70,12 +69,14 @@ export default function >(options: { getScriptContentVersion: () => number, vueLsHost: LanguageServiceHost, vueDocuments: VueDocuments, - updateTemplateScripts: () => void, tsSettings: ts2.Settings, tsRuntime: TypeScriptRuntime, }): EmbeddedLanguageServicePlugin & T { - const componentCompletionDataGetters = new WeakMap>(); + const componentCompletionDataCache = new WeakMap< + ReturnType, + Map + >(); const autoImportPositions = new WeakSet(); const tokenTypes = new Map(options.getSemanticTokenLegend().tokenTypes.map((t, i) => [t, i])); @@ -159,9 +160,7 @@ export default function >(options: { if (vueDocument && scanner) { - options.updateTemplateScripts(); - - const templateScriptData = vueDocument.file.getTemplateScriptData(); + const templateScriptData = vueDocument.file.getTemplateData(); const components = new Set([ ...templateScriptData.components, ...templateScriptData.components.map(hyphenate).filter(name => !isHTMLTag(name)), @@ -448,16 +447,6 @@ export default function >(options: { const tags: html.ITagData[] = []; const tsItems = new Map(); const globalAttributes: html.IAttributeData[] = []; - const { contextItems } = vueDocument.file.getTemplateScriptData(); - - for (const item of contextItems) { - const dir = hyphenate(item.name); - if (dir.startsWith('v-')) { - const key = createInternalItemId('vueDirective', [dir]); - globalAttributes.push({ name: dir, description: key }); - tsItems.set(key, item); - } - } for (const [_componentName, { item, bind, on }] of componentCompletion) { @@ -719,15 +708,6 @@ export default function >(options: { options.templateLanguagePlugin.htmlLs.setDataProviders(true, options.templateLanguagePlugin.getHtmlDataProviders()); } - function getComponentCompletionData(sourceFile: VueDocument) { - let getter = componentCompletionDataGetters.get(sourceFile); - if (!getter) { - getter = untrack(useComponentCompletionData(sourceFile)); - componentCompletionDataGetters.set(sourceFile, getter); - } - return getter(); - } - function getLastImportNode(ast: ts.SourceFile) { let importNode: ts.ImportDeclaration | undefined; ast.forEachChild(node => { @@ -741,24 +721,19 @@ export default function >(options: { } : undefined; } - function useComponentCompletionData(sourceFile: VueDocument) { + function getComponentCompletionData(sourceFile: VueDocument) { - const { - sfcTemplateScript, - templateScriptData, - sfcEntryForTemplateLs, - } = sourceFile.file.refs; + const templateData = sourceFile.file.getTemplateData(); - const projectVersion = ref(); - const usedTags = ref(new Set()); - const result = computed(() => { + let cache = componentCompletionDataCache.get(templateData); + if (!cache) { - const result = new Map(); + const { + sfcTemplateScript, + sfcEntryForTemplateLs, + } = sourceFile.file.refs; - { // watching - projectVersion.value; - usedTags.value; - } + cache = new Map(); pauseTracking(); const file = sfcTemplateScript.file.value; @@ -768,7 +743,7 @@ export default function >(options: { if (file) { - const tags_1 = templateScriptData.componentItems.map(item => { + const tags_1 = templateData.componentItems.map(item => { return { item, name: item.name }; }); const tags_2 = templateTagNames @@ -777,7 +752,7 @@ export default function >(options: { for (const tag of [...tags_1, ...tags_2]) { - if (result.has(tag.name)) + if (cache.has(tag.name)) continue; let bind: ts.CompletionEntry[] = []; @@ -787,7 +762,7 @@ export default function >(options: { let offset = file.content.indexOf(searchText); if (offset >= 0) { offset += searchText.length; - bind = options.tsRuntime.getTsLs()?.getCompletionsAtPosition(file.fileName, offset, undefined)?.entries ?? []; + bind = options.tsRuntime.getTsLs().getCompletionsAtPosition(file.fileName, offset, undefined)?.entries ?? []; } } { @@ -795,24 +770,19 @@ export default function >(options: { let offset = file.content.indexOf(searchText); if (offset >= 0) { offset += searchText.length; - on = options.tsRuntime.getTsLs()?.getCompletionsAtPosition(file.fileName, offset, undefined)?.entries ?? []; + on = options.tsRuntime.getTsLs().getCompletionsAtPosition(file.fileName, offset, undefined)?.entries ?? []; } } - result.set(tag.name, { item: tag.item, bind, on }); + cache.set(tag.name, { item: tag.item, bind, on }); } - const globalBind = options.tsRuntime.getTsLs()?.getCompletionsAtPosition(entryFile.fileName, entryFile.content.indexOf(SearchTexts.GlobalAttrs), undefined)?.entries ?? []; - result.set('*', { item: undefined, bind: globalBind, on: [] }); - } - return result; - }); - return () => { - projectVersion.value = options.getScriptContentVersion(); - const nowUsedTags = new Set(Object.keys(sfcTemplateScript.templateCodeGens.value?.tagNames ?? {})); - if (!eqSet(usedTags.value, nowUsedTags)) { - usedTags.value = nowUsedTags; + const globalBind = options.tsRuntime.getTsLs().getCompletionsAtPosition(entryFile.fileName, entryFile.content.indexOf(SearchTexts.GlobalAttrs), undefined)?.entries ?? []; + cache.set('*', { item: undefined, bind: globalBind, on: [] }); } - return result.value; - }; + + componentCompletionDataCache.set(templateData, cache); + } + + return cache; } } diff --git a/packages/vue-tsc/src/apis.ts b/packages/vue-tsc/src/apis.ts index 53c0d6517..aa57e34c8 100644 --- a/packages/vue-tsc/src/apis.ts +++ b/packages/vue-tsc/src/apis.ts @@ -90,7 +90,7 @@ export function register( }; } function getProgram() { - return context.getTsLs()?.getProgram(); + return context.getTsLs().getProgram(); } // transform diff --git a/packages/vue-typescript/src/types.ts b/packages/vue-typescript/src/types.ts index aca60a80e..3977a9bc7 100644 --- a/packages/vue-typescript/src/types.ts +++ b/packages/vue-typescript/src/types.ts @@ -7,8 +7,6 @@ export type LanguageServiceHost = ts.LanguageServiceHost & { export interface ITemplateScriptData { projectVersion: string | undefined; - context: string[]; - contextItems: ts.CompletionEntry[]; components: string[]; componentItems: ts.CompletionEntry[]; } diff --git a/packages/vue-typescript/src/typescriptRuntime.ts b/packages/vue-typescript/src/typescriptRuntime.ts index 56bf81d6e..ff0765007 100644 --- a/packages/vue-typescript/src/typescriptRuntime.ts +++ b/packages/vue-typescript/src/typescriptRuntime.ts @@ -36,10 +36,7 @@ export function createTypeScriptRuntime(options: { let vueProjectVersion: string | undefined; let scriptContentVersion = 0; // only update by `