🐛 fix(vscode): 调用树按钮一直显示,点开时没数到就当场再查

This commit is contained in:
cruldra committed 2026-10-11 22:49:42 +11:00
1 parent 6e8be1447a
commit 83d1f4a675
5 files changed
+155 -39

No files matched your search

@@ -100,6 +100,94 @@ describe('调用树棵数在文档发出去之后算', () => {
}])
session.dispose()
})
it('后台没数出调用者的块,点开时当场再问一次;这回问到了就发树,并补上棵数', async () => {
const mask = symbol('mask_phone')
const caller = symbol('get_profile')
const edge: CallEdge = { caller, callee: mask, at: { line: 2, character: 0, endLine: 2, endCharacter: 3 } }
let answering = false
let asked = 0
const source: CallTreeSource = {
version: 5,
blocks: [block('fn', 'function', 1)],
load: async () => {
asked++
return { symbol: mask, edges: answering ? [edge] : [] }
},
}
const posted: unknown[] = []
const session = new CallTreeSession(message => posted.push(message), () => source)
session.refresh()
await viFlush()
expect(posted).toEqual([{ type: 'callTreeCounts', version: 5, counts: {} }])
posted.length = 0
answering = true
session.open(5, 'fn')
await viFlush()
expect(asked).toBe(2)
expect(posted).toEqual([
{ type: 'callTreeCounts', version: 5, counts: { fn: 1 } },
{
type: 'callTree',
version: 5,
anchor: 'fn',
name: 'mask_phone',
where: 'mask_phone.py:1',
trees: [expect.objectContaining({ name: 'get_profile', hits: 1 })],
},
])
posted.length = 0
session.open(5, 'fn')
expect(asked).toBe(2)
expect(posted).toEqual([expect.objectContaining({ type: 'callTree', anchor: 'fn' })])
session.dispose()
})
it('点开时当场问还是没有调用者,就发一棵空的', async () => {
const source: CallTreeSource = {
version: 6,
blocks: [block('fn', 'function', 1)],
load: async () => ({ symbol: symbol('nobody'), edges: [] }),
}
const posted: unknown[] = []
const session = new CallTreeSession(message => posted.push(message), () => source)
session.refresh()
await viFlush()
posted.length = 0
session.open(6, 'fn')
await viFlush()
expect(posted).toEqual([{ type: 'callTree', version: 6, anchor: 'fn', name: '', where: '', trees: [] }])
session.dispose()
})
it('当场问到一半文档改了,结果不发', async () => {
let release: (() => void) | undefined
const gate = new Promise<void>((resolve) => {
release = resolve
})
let version = 7
const mask = symbol('mask_phone')
const edge: CallEdge = { caller: symbol('get_profile'), callee: mask, at: { line: 2, character: 0, endLine: 2, endCharacter: 3 } }
const source: CallTreeSource = {
get version() {
return version
},
blocks: [block('fn', 'function', 1)],
load: async () => {
await gate
return { symbol: mask, edges: [edge] }
},
}
const posted: unknown[] = []
const session = new CallTreeSession(message => posted.push(message), () => source)
session.open(7, 'fn')
version = 8
session.cancel()
release!()
await viFlush()
expect(posted).toEqual([])
session.dispose()
})
})
async function viFlush(): Promise<void> {
+53 -29
View File
@@ -1,6 +1,7 @@
/**
* 文档发出去之后,在后台数每个函数、类落在几棵调用树里。
* 页面点开某一块时,把这一版已经收好的边收成树发回去。版本对不上就丢掉。
* 文档发出去之后,在后台数每个函数、类落在几棵调用树里,有调用者的收起来,数完把棵数发给页面。
* 页面点开某一块时,收着就把树发回去;没收着就当场问这一块,问到了补发棵数再发树,没问到发一棵空的。
* 问的时候文档版本变了、或者停掉了,结果都丢掉。
*/
import type { ExtensionToWebview } from '../editor/messages'
@@ -32,8 +33,8 @@ interface Cached {
export class CallTreeSession {
private generation = 0
private readonly cache = new Map<string, Cached>()
private pending: { version: number, anchor: string } | undefined
private scannedGeneration = -1
/** 这一轮数到的棵数,只记大于 0 的。后台数完、点开时当场问到,发的都是这一份 */
private counts: Record<string, number> = {}
constructor(
private readonly post: (message: ExtensionToWebview) => void,
@@ -44,6 +45,7 @@ export class CallTreeSession {
cancel(): void {
this.generation++
this.cache.clear()
this.counts = {}
}
/** 按眼下这份文档重新数。先停掉上一轮。 */
@@ -52,7 +54,7 @@ export class CallTreeSession {
void this.scan(this.generation)
}
/** 页面要看某一块。不是这一版文档的不理;还没数完就等这一轮数完再发。 */
/** 页面要看某一块。不是这一版文档的不理;收着就直接发;没收着就当场问这一块。 */
open(version: number, anchor: string): void {
const src = this.source()
if (!src || src.version !== version)
@@ -62,9 +64,7 @@ export class CallTreeSession {
this.postTree(version, anchor, cached)
return
}
this.pending = { version, anchor }
if (this.scannedGeneration === this.generation)
this.postEmpty(version, anchor)
void this.askNow(this.generation, src, anchor)
}
dispose(): void {
@@ -76,31 +76,55 @@ export class CallTreeSession {
if (!src || generation !== this.generation)
return
const version = src.version
const counts: Record<string, number> = {}
for (const block of callBlocks(src.blocks)) {
if (generation !== this.generation || this.source()?.version !== version)
if (this.stale(generation, version))
return
const loaded = await src.load(block.defines!.startIndex, () => generation !== this.generation)
if (generation !== this.generation || this.source()?.version !== version)
return
if (!loaded)
continue
const classTarget = block.kind === 'class'
const trees = buildCallTrees(loaded.edges, loaded.symbol, { classTarget })
if (trees.length === 0)
continue
const cached: Cached = { symbol: loaded.symbol, edges: loaded.edges, classTarget }
this.cache.set(block.anchor, cached)
counts[block.anchor] = trees.length
if (this.pending?.anchor === block.anchor && this.pending.version === version)
this.postTree(version, block.anchor, cached)
if (!this.cache.has(block.anchor))
await this.collect(generation, src, block)
}
if (generation !== this.generation || this.source()?.version !== version)
if (this.stale(generation, version))
return
this.scannedGeneration = generation
this.post({ type: 'callTreeCounts', version, counts })
if (this.pending && this.pending.version === version && !this.cache.has(this.pending.anchor))
this.postEmpty(version, this.pending.anchor)
this.postCounts(version)
}
/** 当场问页面点开的那一块:问到了先补发棵数再发树,没问到(找不到这块、没人调用)发一棵空的。 */
private async askNow(generation: number, src: CallTreeSource, anchor: string): Promise<void> {
const version = src.version
const block = callBlocks(src.blocks).find(item => item.anchor === anchor)
const cached = block && await this.collect(generation, src, block)
if (this.stale(generation, version))
return
if (!cached) {
this.postEmpty(version, anchor)
return
}
this.postCounts(version)
this.postTree(version, anchor, cached)
}
/** 问一块有谁调用。有调用者就收起来、记上棵数再交回来;没人调用、语言不提供、问到一半作废了,都交回空。 */
private async collect(generation: number, src: CallTreeSource, block: CallTreeBlock): Promise<Cached | undefined> {
const version = src.version
const loaded = await src.load(block.defines!.startIndex, () => generation !== this.generation)
if (!loaded || this.stale(generation, version))
return undefined
const classTarget = block.kind === 'class'
const count = buildCallTrees(loaded.edges, loaded.symbol, { classTarget }).length
if (count === 0)
return undefined
const cached: Cached = { symbol: loaded.symbol, edges: loaded.edges, classTarget }
this.cache.set(block.anchor, cached)
this.counts[block.anchor] = count
return cached
}
/** 这一轮停掉了,或者文档版本变了:手上的结果不能再用。 */
private stale(generation: number, version: number): boolean {
return generation !== this.generation || this.source()?.version !== version
}
private postCounts(version: number): void {
this.post({ type: 'callTreeCounts', version, counts: { ...this.counts } })
}
private postTree(version: number, anchor: string, cached: Cached): void {