diff --git a/packages/vue-language-core/src/plugins/file-vue.ts b/packages/vue-language-core/src/plugins/file-vue.ts index dc198c116a..e965af84bf 100644 --- a/packages/vue-language-core/src/plugins/file-vue.ts +++ b/packages/vue-language-core/src/plugins/file-vue.ts @@ -11,7 +11,48 @@ const plugin: VueLanguagePlugin = () => { return parse(content, { sourceMap: false, ignoreEmpty: false }); } - } + }, + + updateSFC(sfc, change) { + + // avoid broken @vue/compiler-sfc cache + if (!(sfc as any).__volar_clone) { + sfc = JSON.parse(JSON.stringify(sfc)); + (sfc as any).__volar_clone = true; + } + + const blocks = [ + sfc.descriptor.template, + sfc.descriptor.script, + sfc.descriptor.scriptSetup, + ...sfc.descriptor.styles, + ...sfc.descriptor.customBlocks, + ].filter((block): block is NonNullable => !!block); + + const hitBlock = blocks.find(block => change.start >= block.loc.start.offset && change.end <= block.loc.end.offset); + + if (!hitBlock) { + return; + } + + hitBlock.content = + hitBlock.content.substring(0, change.start - hitBlock.loc.start.offset) + + change.newText + + hitBlock.content.substring(change.end - hitBlock.loc.start.offset); + + const lengthDiff = change.newText.length - (change.end - change.start); + + for (const block of blocks) { + if (block.loc.start.offset >= change.end) { + block.loc.start.offset += lengthDiff; + } + if (block.loc.end.offset >= change.end) { + block.loc.end.offset += lengthDiff; + } + } + + return sfc; + }, }; -} +}; export = plugin; diff --git a/packages/vue-language-core/src/sourceFile.ts b/packages/vue-language-core/src/sourceFile.ts index d0e777f12f..4fedb6164c 100644 --- a/packages/vue-language-core/src/sourceFile.ts +++ b/packages/vue-language-core/src/sourceFile.ts @@ -17,6 +17,7 @@ export type VueLanguagePlugin = (ctx: { }) => { order?: number; parseSFC?(fileName: string, content: string): SFCParseResult | undefined; + updateSFC?(oldResult: SFCParseResult, textChange: { start: number, end: number, newText: string; }): SFCParseResult | undefined; compileSFCTemplate?(lang: string, template: string, options?: CompilerDom.CompilerOptions): CompilerDom.CodegenResult | undefined; getEmbeddedFileNames?(fileName: string, sfc: Sfc): string[]; resolveEmbeddedFile?(fileName: string, sfc: Sfc, embeddedFile: EmbeddedFile): void; @@ -109,6 +110,13 @@ export function createSourceFile( }) as unknown as Sfc['scriptSetupAst'], }) as Sfc /* avoid Sfc unwrap in .d.ts by reactive */; + // cache + let parsedSfcCache: { + snapshot: ts.IScriptSnapshot, + sfc: SFCParseResult, + plugin: ReturnType, + } | undefined; + // use const scriptAst = computed(() => { if (sfc.script) { @@ -121,9 +129,28 @@ export function createSourceFile( } }); const parsedSfc = computed(() => { + + // incremental update + if (parsedSfcCache?.plugin.updateSFC) { + const change = snapshot.value.getChangeRange(parsedSfcCache.snapshot); + if (change) { + const newSfc = parsedSfcCache.plugin.updateSFC(parsedSfcCache.sfc, { + start: change.span.start, + end: change.span.start + change.span.length, + newText: snapshot.value.getText(change.span.start, change.span.start + change.newLength), + }); + if (newSfc) { + parsedSfcCache.snapshot = snapshot.value; + parsedSfcCache.sfc = newSfc; + return newSfc; + } + } + } + for (const plugin of plugins) { const sfc = plugin.parseSFC?.(fileName, fileContent.value); if (sfc) { + parsedSfcCache = { snapshot: snapshot.value, sfc, plugin }; return sfc; } } @@ -404,13 +431,8 @@ export function createSourceFile( return; } - const change = newScriptSnapshot.getChangeRange(snapshot.value); snapshot.value = newScriptSnapshot; - if (change) { - // TODO - } - // TODO: wait for https://github.com/vuejs/core/pull/5912 if (parsedSfc.value) { updateTemplate(parsedSfc.value.descriptor.template); @@ -419,6 +441,13 @@ export function createSourceFile( updateStyles(parsedSfc.value.descriptor.styles); updateCustomBlocks(parsedSfc.value.descriptor.customBlocks); } + else { + updateTemplate(null); + updateScript(null); + updateScriptSetup(null); + updateStyles([]); + updateCustomBlocks([]); + } function updateTemplate(block: SFCTemplateBlock | null) {