⚡ perf(vscode): 代码阅读预览发给页面的块带内容指纹:扩展端按卡片内容、在源码里占的地方连同子块的指纹算摘要,页面按指纹认出没变的块沿用原来的对象,不再逐块深比内容,5126 行的文档到了比对 10.6ms → 0.4ms
This commit is contained in:
1 parent
ad5f559e92
commit
266dc42bc5
13 files changed
+194
-26
No files matched your search
@@ -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'])
|
||||
})
|
||||
})
|
||||
@@ -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 }
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user