From 5a0c5490eac4805edf2785a4644f5be484de1815 Mon Sep 17 00:00:00 2001 From: cruldra Date: Tue, 13 Oct 2026 14:15:33 +1100 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20perf(vscode):=20=E6=A0=87=E5=B7=AE?= =?UTF-8?q?=E5=BC=82=E6=97=B6=E6=97=A7=E7=89=88=E6=9C=AC=E7=9A=84=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E7=BB=93=E6=9E=9C=E6=8C=89=E6=97=A7=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E8=AE=B0=E7=9D=80=EF=BC=8C=E6=99=9A=E5=88=B0=E7=9A=84=E7=AD=94?= =?UTF-8?q?=E6=A1=88=E3=80=81=E7=BF=BB=E8=AF=91=E6=8F=92=E4=BB=B6=E6=B2=A1?= =?UTF-8?q?=E5=8F=98=E5=B0=B1=E4=B8=8D=E5=86=8D=E9=87=8D=E6=96=B0=E8=A7=A3?= =?UTF-8?q?=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vscode/src/codeReader/calleeKinds.test.ts | 19 ++++++++++++++ vscode/src/codeReader/calleeKinds.ts | 5 ++++ vscode/src/codeReader/deferredAnswers.ts | 10 +++++++- .../src/codeReader/diff/previewDiff.test.ts | 25 ++++++++++++++++--- vscode/src/codeReader/diff/previewDiff.ts | 23 ++++++++++++++--- .../codeReader/editor/previewEditor.test.ts | 2 +- vscode/src/codeReader/editor/previewEditor.ts | 6 +++++ vscode/src/codeReader/ruleLookups.ts | 5 ++++ 8 files changed, 87 insertions(+), 8 deletions(-) diff --git a/vscode/src/codeReader/calleeKinds.test.ts b/vscode/src/codeReader/calleeKinds.test.ts index 775ae77..0135b2e 100644 --- a/vscode/src/codeReader/calleeKinds.test.ts +++ b/vscode/src/codeReader/calleeKinds.test.ts @@ -43,6 +43,25 @@ describe('调用的名字定义成哪种块(DocumentCallees)', () => { expect(from.asked).toEqual([40, 120]) }) + it('revision:查到的答案变了才加一,新版本查到的一样不加(差异按它定旧版本要不要重新解析)', async () => { + const callees = new DocumentCallees(() => {}, []) + const session = callees.session() + session.calleeKind(call('Detail', 40)) + session.calleeKind(call('build', 5)) + expect(callees.revision).toBe(0) + const kinds: Record = { 40: 'class', 5: null } + callees.resolve(1, session.requests(), source(kinds).kindOf) + await vi.waitFor(() => expect(callees.revision).toBe(1)) + const same = source(kinds) + callees.resolve(2, session.requests(), same.kindOf) + await vi.waitFor(() => expect(same.asked).toEqual([40, 5])) + await Promise.resolve() + expect(callees.revision).toBe(1) + kinds[40] = 'function' + callees.resolve(3, session.requests(), source(kinds).kindOf) + await vi.waitFor(() => expect(callees.revision).toBe(2)) + }) + it('被调用的不是名字(没有 name)不问;查了没有(null)不算变了', async () => { let changed = 0 const callees = new DocumentCallees(() => changed++, []) diff --git a/vscode/src/codeReader/calleeKinds.ts b/vscode/src/codeReader/calleeKinds.ts index b7709aa..5c7f227 100644 --- a/vscode/src/codeReader/calleeKinds.ts +++ b/vscode/src/codeReader/calleeKinds.ts @@ -26,6 +26,11 @@ export class DocumentCallees { this.answers = new DeferredAnswers(({ callee }) => callee, (a, b) => (a ?? null) === (b ?? null), changed, delays, schedule) } + /** 查到的种类变过几次(见 DeferredAnswers.revision) */ + get revision(): number { + return this.answers.revision + } + /** 被调用的不是名字(没有 CallFacts.name)的不问,按调用算。 */ session(): CalleeSession { const session = this.answers.session() diff --git a/vscode/src/codeReader/deferredAnswers.ts b/vscode/src/codeReader/deferredAnswers.ts index 696f989..87b2f0f 100644 --- a/vscode/src/codeReader/deferredAnswers.ts +++ b/vscode/src/codeReader/deferredAnswers.ts @@ -24,6 +24,8 @@ export class DeferredAnswers { /** 眼下这个版本查过(或正在查)的问题 */ private readonly checked = new Set() private timer: ReturnType | undefined + /** 查到的答案变过几次:按眼下的答案排出来的东西(对比用的旧版本)比这个数就知道还能不能接着用 */ + private changes = 0 constructor( private readonly idOf: (question: Q) => string, @@ -34,6 +36,10 @@ export class DeferredAnswers { private readonly schedule: (run: () => void, ms: number) => ReturnType = setTimeout, ) {} + get revision(): number { + return this.changes + } + session(): AnswerSession { const asked = new Map() return { @@ -88,8 +94,10 @@ export class DeferredAnswers { return } const id = this.idOf(question) - if (!this.same(this.known.get(id), found)) + if (!this.same(this.known.get(id), found)) { changed = true + this.changes++ + } this.known.set(id, found) })) return { unanswered, changed } diff --git a/vscode/src/codeReader/diff/previewDiff.test.ts b/vscode/src/codeReader/diff/previewDiff.test.ts index 60b90f7..c699e3c 100644 --- a/vscode/src/codeReader/diff/previewDiff.test.ts +++ b/vscode/src/codeReader/diff/previewDiff.test.ts @@ -23,11 +23,12 @@ function base(text: string | null, key = 'k'): DiffBase & { read: ReturnType text), missing: '上一次提交里没有这个文件' } } -function setup(current: { base: DiffBase | undefined }) { +function setup(current: { base: DiffBase | undefined, inputs?: string }) { const posted: (DocumentDiff | null)[] = [] const reports: string[] = [] - const diff = new PreviewDiff(() => current.base, parse, value => posted.push(value), message => reports.push(message)) - return { diff, posted, reports } + const read = vi.fn(parse) + const diff = new PreviewDiff(() => current.base, read, () => current.inputs ?? '', value => posted.push(value), message => reports.push(message)) + return { diff, posted, reports, read } } describe('previewDiff', () => { @@ -75,6 +76,24 @@ describe('previewDiff', () => { expect(second.read).toHaveBeenCalledTimes(1) }) + it('旧版本解析出来的按旧内容和解析时看的东西(翻译插件、晚到的答案……)记着:都没变不再解析,哪个变了重新解析', async () => { + const current: { base: DiffBase | undefined, inputs: string } = { base: base('x = 1\n', 'a'), inputs: 'v1' } + const { diff, posted, read } = setup(current) + await diff.update(await sent('x = 2\n', 1)) + await diff.update(await sent('x = 3\n', 2)) + expect(read).toHaveBeenCalledTimes(1) + expect(posted.at(-1)).toMatchObject({ version: 2, stats: { mod: 1 } }) + current.inputs = 'v2' + await diff.update(await sent('x = 3\n', 3)) + expect(read).toHaveBeenCalledTimes(2) + current.base = base('y = 1\n', 'b') + await diff.update(await sent('x = 3\n', 4)) + expect(read).toHaveBeenCalledTimes(3) + current.base = base('y = 1\n', 'c') + await diff.update(await sent('x = 3\n', 5)) + expect(read).toHaveBeenCalledTimes(3) + }) + it('同一份文档、同一个基准不再比', async () => { const { diff, posted } = setup({ base: base('x = 1\n') }) const doc = await sent('x = 2\n') diff --git a/vscode/src/codeReader/diff/previewDiff.ts b/vscode/src/codeReader/diff/previewDiff.ts index 4f574bb..2b4ebc5 100644 --- a/vscode/src/codeReader/diff/previewDiff.ts +++ b/vscode/src/codeReader/diff/previewDiff.ts @@ -61,12 +61,14 @@ export function documentDiff(base: DiffBase, sent: SentDocument, old: { source: /** * 一个打开着的预览的差异:每发出一份文档之后、对比基准变了之后按眼下的基准重新比,比完再发,预览先照常画、不等它。 - * 取过的旧内容按基准的 key 记着(编辑时不用每次去问 git),旧版本每次重新解析:规则查到的定义、调用的种类这些晚到的答案两边要一样。 - * 这里不碰 VS Code。 + * 取过的旧内容按基准的 key 记着(编辑时不用每次去问 git);旧版本解析出来的按旧内容和解析时还看的东西(oldInputs)记着, + * 都没变不再解析,规则查到的定义、调用的种类这些晚到的答案变了才重新解析,两边同样的写法翻得一样。这里不碰 VS Code。 */ export class PreviewDiff { /** 取过的旧内容;key 相同不再取,取失败了下次重取 */ private old: { key: string, text: Promise } | undefined + /** 解析过的旧版本;旧内容、oldInputs 都没变不再解析,解析出错了下次重来 */ + private parsed: { text: string, inputs: string, document: Promise } | undefined /** 只发最后一次比的结果:慢的旧结果不能盖掉新的 */ private latest = 0 /** 上一次比的是哪份文档、哪个基准:都没变就不再比(git 状态变了、基准没挪) */ @@ -79,6 +81,8 @@ export class PreviewDiff { private readonly baseOf: () => DiffBase | undefined, /** 用这份文档的语言、翻译插件和库规则解析旧版本;解析失败是 undefined */ private readonly readOld: (source: string) => Promise, + /** 解析旧版本除了旧内容还看的东西(翻译插件、项目入口、晚到的答案)拼成的一串:变了旧版本要重新解析 */ + private readonly oldInputs: () => string, private readonly post: (diff: DocumentDiff | null) => void, private readonly report: (message: string) => void, ) {} @@ -97,7 +101,7 @@ export class PreviewDiff { } try { const text = await this.oldText(base) - const document = text === null ? undefined : await this.readOld(text) + const document = text === null ? undefined : await this.oldDocument(text) if (request !== this.latest) return if (text !== null && !document) { @@ -135,4 +139,17 @@ export class PreviewDiff { } return this.old.text } + + private oldDocument(text: string): Promise { + const inputs = this.oldInputs() + if (this.parsed?.text !== text || this.parsed.inputs !== inputs) { + const document = this.readOld(text) + this.parsed = { text, inputs, document } + document.catch(() => { + if (this.parsed?.document === document) + this.parsed = undefined + }) + } + return this.parsed.document + } } diff --git a/vscode/src/codeReader/editor/previewEditor.test.ts b/vscode/src/codeReader/editor/previewEditor.test.ts index 3af1a34..475679c 100644 --- a/vscode/src/codeReader/editor/previewEditor.test.ts +++ b/vscode/src/codeReader/editor/previewEditor.test.ts @@ -199,7 +199,7 @@ function opening(profiles = { ask: profileSet(''), rewrite: profileSet('') }, co } const provider = new PreviewEditorProvider(...[ {}, - {}, + { selectedTranslator: () => undefined }, {}, { subscribe, current: { excluded: {}, onlyChanges: false } }, colors, diff --git a/vscode/src/codeReader/editor/previewEditor.ts b/vscode/src/codeReader/editor/previewEditor.ts index 477ad63..2634b60 100644 --- a/vscode/src/codeReader/editor/previewEditor.ts +++ b/vscode/src/codeReader/editor/previewEditor.ts @@ -271,6 +271,7 @@ class Preview { this.diff = new PreviewDiff( () => this.from === undefined ? undefined : owner.diffBases.diffBase(document.uri, this.from), source => this.readOld(source), + () => this.oldInputs(), diff => this.post({ type: 'diff', diff }), text => logger.add({ level: 'warn', source: 'codeReader', message: `${document.uri.fsPath}:${text}` }), ) @@ -452,6 +453,11 @@ class Preview { return message.error === undefined ? { blocks: message.blocks, attributes: message.attributes } : undefined } + /** 排旧版本除了旧内容还看的(见 readOld):选的翻译插件、项目入口、规则查到的定义和调用的种类变过几次。变了旧版本要重新解析 */ + private oldInputs(): string { + return JSON.stringify([this.registry.selectedTranslator(this.document.languageId)?.id, this.owner.entries.namesOf(this.document.uri), this.lookups.revision, this.callees.revision]) + } + /** 这份文档里 offset 处名字的定义是哪种块:语言扩展查到的几处定义里第一处认得出的;语言扩展没答是 undefined。 */ private async calleeKind(offset: number): Promise { const { document } = this diff --git a/vscode/src/codeReader/ruleLookups.ts b/vscode/src/codeReader/ruleLookups.ts index 5149b6e..cb7e3d3 100644 --- a/vscode/src/codeReader/ruleLookups.ts +++ b/vscode/src/codeReader/ruleLookups.ts @@ -43,6 +43,11 @@ export class DocumentLookups { this.answers = new DeferredAnswers(({ index, key }) => idOf(index, key), same, changed, delays, schedule) } + /** 查到的定义变过几次(见 DeferredAnswers.revision) */ + get revision(): number { + return this.answers.revision + } + session(): LookupSession { const session = this.answers.session() return {