⚡ perf(vscode): 代码阅读预览发给页面的块带内容指纹:扩展端按卡片内容、在源码里占的地方连同子块的指纹算摘要,页面按指纹认出没变的块沿用原来的对象,不再逐块深比内容,5126 行的文档到了比对 10.6ms → 0.4ms

This commit is contained in:
cruldra committed 2026-10-13 16:39:59 +11:00
1 parent ad5f559e92
commit 266dc42bc5
13 files changed
+194 -26

No files matched your search

+5 -1
View File
@@ -80,9 +80,13 @@ interface PageCardBase {
export type PageCard = PageCardBase & ({ translation: PreviewText, items?: PropertyItem[], table?: PreviewTable, doc?: Markdown, rows?: PropertyRow[] } | { properties: PropertyRow[] })
/** 发给页面的一块(见 pageBlocks):卡片的内容,在源码里占的地方(相对的,见 BlockSpan),子块。内容没变、只是挪了位置的块,发出去一模一样。 */
/**
* 发给页面的一块(见 pageBlocks):卡片的内容,在源码里占的地方(相对的,见 BlockSpan),子块。内容没变、只是挪了位置的块,发出去一模一样。
* hash 是这些(连子块)的内容指纹:页面按它认出没变的块,不用逐块比内容。
*/
export type PageBlock = PageCard & {
span: BlockSpan
hash: string
children: PageBlock[]
}
@@ -0,0 +1,69 @@
import type { BlockView } from '../blockView'
import type { PageBlock } from './messages'
import { describe, expect, it } from 'vitest'
import { toBlockViews } from '../blockView'
import { createPythonAdapter } from '../languages/python'
import { WASM_DIR, withBlocks } from '../testSupport'
import { pythonZhTranslator } from '../translators/pythonZh'
import { pageBlocks } from './pageBlocks'
const py = createPythonAdapter(WASM_DIR)
async function page(source: string): Promise<PageBlock[]> {
const views: BlockView[] = await withBlocks(py, 'python', source, blocks => toBlockViews(blocks, { source, language: py, markers: [] }, pythonZhTranslator))
return pageBlocks(views, source)
}
/** 各块的内容指纹,按块 id */
function hashes(blocks: PageBlock[]): Map<string, string> {
const out = new Map<string, string>()
const walk = (list: PageBlock[]): void => list.forEach((block) => {
out.set(block.id, block.hash)
walk(block.children)
})
walk(blocks)
return out
}
/** 指纹和 before 里同一块不一样的块 id */
function changed(before: Map<string, string>, after: Map<string, string>): string[] {
return [...after.keys()].filter(id => after.get(id) !== before.get(id))
}
const SOURCE = [
'import os',
'',
'def helper(value):',
' return value + 1',
'',
'class Store:',
' def load(self, key):',
' if key:',
' return os.getenv(key)',
' return None',
'',
'LIMIT = 3',
].join('\n')
describe('发给页面的块带内容指纹:连子块、在源码里占的地方一起算,页面按它认出没变的块', () => {
it('同一份源码算两次指纹一样,各块的指纹互不相同', async () => {
const first = hashes(await page(SOURCE))
expect(hashes(await page(SOURCE))).toEqual(first)
expect(new Set(first.values()).size).toBe(first.size)
})
it('靠前的一块里改了一个字:那块和包着它的块的指纹变了,后面的块不变', async () => {
const before = hashes(await page(SOURCE))
expect(changed(before, hashes(await page(SOURCE.replace('value + 1', 'value + 10'))))).toEqual(['1', '1.0'])
})
it('上面多了几行:只有离前一块远了的那块指纹变了;里面的子块离它的开头没变,不变', async () => {
const before = hashes(await page(SOURCE))
expect(changed(before, hashes(await page(SOURCE.replace('import os\n', 'import os\n\n\n'))))).toEqual(['1'])
})
it('深处的子块改了:一路往上包着它的块指纹都变了', async () => {
const before = hashes(await page(SOURCE))
expect(changed(before, hashes(await page(SOURCE.replace('os.getenv(key)', 'os.getenv(name)'))))).toEqual(['2', '2.0', '2.0.0', '2.0.0.0'])
})
})
+13 -2
View File
@@ -2,6 +2,7 @@ import type { BlockView } from '../blockView'
import type { Inline, PreviewTable, PreviewText, PropertyItem, PropertyRow, Segment, SourceRange } from '../contract'
import type { BlockMarks, ItemDiff, ItemsDiff, RowDiff, RowsDiff, TextDiff } from '../diff/viewDiff'
import type { BlockSpan, PageBlock, PageCard } from './messages'
import { createHash } from 'node:crypto'
/**
* 扩展端的块(位置都是文件里的)→ 发给页面的块(见 PageBlock):块在源码里占的地方按相对的量记(BlockSpan),
@@ -101,9 +102,17 @@ export function pageCard(block: BlockView, base: number): PageCard {
/** 文件里的一处:行号、下标 */
interface Point { line: number, offset: number }
/** 一块的内容指纹:卡片的内容、span、各子块的指纹一起摘要,取前 16 个字符(96 位) */
function fingerprint(card: PageCard, span: BlockSpan, children: readonly PageBlock[]): string {
const hash = createHash('sha1').update(JSON.stringify(card)).update(JSON.stringify(span))
for (const child of children)
hash.update(child.hash)
return hash.digest('base64url').slice(0, 16)
}
/**
* 一串兄弟块发给页面的样子:每块的开头是它第一行的行首、结尾是最后一行的行尾,离前一块的结尾(第一块从 from 算)隔多远、自己多长记成 span,
* 子块从这块的开头算起。
* 子块从这块的开头算起;带上内容指纹(见 PageBlock.hash)。
*/
function pageList(blocks: readonly BlockView[], lines: SourceLines, from: Point): PageBlock[] {
let cursor = from
@@ -112,7 +121,9 @@ function pageList(blocks: readonly BlockView[], lines: SourceLines, from: Point)
const end = { line: block.endLine, offset: lines.end(block.endLine) }
const span: BlockSpan = { lineGap: start.line - cursor.line, gap: start.offset - cursor.offset, lines: end.line - start.line, length: end.offset - start.offset }
cursor = end
return { ...pageCard(block, start.offset), span, children: pageList(block.children, lines, start) }
const card = pageCard(block, start.offset)
const children = pageList(block.children, lines, start)
return { ...card, span, hash: fingerprint(card, span, children), children }
})
}