⚡ perf(vscode): 代码阅读预览的文档消息里源码和着色只带一份:块不再各带一份连子块在内的整段源码和上色,编辑框按块的编辑范围从整份源码和着色里取,5126 行的消息 4.3MB → 1.9MB

This commit is contained in:
cruldra committed 2026-10-13 15:50:33 +11:00
1 parent 0371712176
commit 2e3602242e
43 files changed
+501 -239

No files matched your search

+38 -26
View File
@@ -5,7 +5,7 @@ import { readBlockViews } from '../blockView'
import { createPythonAdapter } from '../languages/python'
import { runtime, WASM_DIR } from '../testSupport'
import { pythonZhTranslator } from '../translators/pythonZh'
import { alignSiblings, diffBlockTrees, similarity } from './blockDiff'
import { alignSiblings, diffBlockTrees, similarity, sourced } from './blockDiff'
const py = createPythonAdapter(WASM_DIR)
@@ -14,17 +14,28 @@ async function python(source: string): Promise<SourcedBlocks> {
return { blocks, source }
}
/** 手写的一块:只填比对用得到的几样。 */
function view(anchor: string, kind: string, code: string, extra: Partial<BlockView> = {}): BlockView {
/** 手写的几块拼成的源码:各块的代码、名字依次另起一行接上去。 */
interface Written { text: string }
/** text 另起一行接到源码后面:它占的行和起始下标。 */
function append(source: Written, text: string): { startLine: number, endLine: number, startIndex: number } {
const startLine = source.text === '' ? 0 : source.text.split('\n').length
source.text += source.text === '' ? '' : '\n'
const startIndex = source.text.length
source.text += text
return { startLine, endLine: startLine + text.split('\n').length - 1, startIndex }
}
/** 手写的一块:只填比对用得到的几样,代码接在源码后面,编辑范围是它占的行。 */
function view(anchor: string, kind: string, code: string, source: Written, extra: Partial<BlockView> = {}): BlockView {
const { startLine, endLine } = append(source, code)
return {
id: anchor,
anchor,
kind,
startLine: 0,
endLine: 0,
headerEndLine: 0,
code,
codeTokens: [],
startLine,
endLine,
headerEndLine: startLine,
attributes: { kind },
children: [],
translation: [{ text: code, role: 'plain' }],
@@ -32,11 +43,10 @@ function view(anchor: string, kind: string, code: string, extra: Partial<BlockVi
} as BlockView
}
/** 名字写在源码开头:defines 指向它。 */
function named(anchor: string, kind: string, name: string, code: string, source: { text: string }): BlockView {
const startIndex = source.text.length
source.text += name
return view(anchor, kind, code, { defines: { startIndex, endIndex: startIndex + name.length } })
/** 名字先接在源码后面(defines 指向它),再接代码。 */
function named(anchor: string, kind: string, name: string, code: string, source: Written): BlockView {
const { startIndex } = append(source, name)
return view(anchor, kind, code, source, { defines: { startIndex, endIndex: startIndex + name.length } })
}
const RELEASE_LUA = `_RELEASE_LUA = """
@@ -127,7 +137,7 @@ describe('alignSiblings', () => {
const source = { text: '' }
const before = [named('f', 'function', 'f', 'def f(): pass', source), named('g', 'function', 'g', 'def g(): pass', source)]
const after = [named('g2', 'function', 'g', 'def g(): return 1', source), named('h', 'function', 'h', 'def f(): pass', source)]
const { toOld, toNew } = alignSiblings(before, source.text, after, source.text)
const { toOld, toNew } = alignSiblings(before, sourced(source.text), after, sourced(source.text))
expect(toOld).toEqual([1, undefined])
expect(toNew).toEqual([undefined, 0])
})
@@ -136,38 +146,40 @@ describe('alignSiblings', () => {
const source = { text: '' }
const before = [named('a', 'function', 'f', 'one', source), named('b', 'function', 'f', 'two', source)]
const after = [named('c', 'function', 'f', 'one', source), named('d', 'function', 'f', 'two!', source)]
expect(alignSiblings(before, source.text, after, source.text).toOld).toEqual([0, 1])
expect(alignSiblings(before, sourced(source.text), after, sourced(source.text)).toOld).toEqual([0, 1])
})
it('种类不同的同名块不配', () => {
const source = { text: '' }
const before = [named('a', 'const', 'x', 'const x = 1', source)]
const after = [named('b', 'variable', 'x', 'let x = 1', source)]
expect(alignSiblings(before, source.text, after, source.text).toOld).toEqual([undefined])
expect(alignSiblings(before, sourced(source.text), after, sourced(source.text)).toOld).toEqual([undefined])
})
it('没名字的:源码没变的按最长公共子序列配,中间的按相似度配成修改,不像的算一删一增', () => {
const before = [view('1', 'statement', 'print(a)'), view('2', 'statement', 'call(alpha, beta, gamma)'), view('3', 'statement', 'print(b)')]
const source = { text: '' }
const before = [view('1', 'statement', 'print(a)', source), view('2', 'statement', 'call(alpha, beta, gamma)', source), view('3', 'statement', 'print(b)', source)]
const after = [
view('1', 'statement', 'print(a)'),
view('x', 'statement', 'totally_new()'),
view('2', 'statement', 'call(alpha, beta, delta)'),
view('3', 'statement', 'print(b)'),
view('1', 'statement', 'print(a)', source),
view('x', 'statement', 'totally_new()', source),
view('2', 'statement', 'call(alpha, beta, delta)', source),
view('3', 'statement', 'print(b)', source),
]
expect(alignSiblings(before, '', after, '').toOld).toEqual([0, undefined, 1, 2])
expect(alignSiblings(before, sourced(source.text), after, sourced(source.text)).toOld).toEqual([0, undefined, 1, 2])
})
it('没名字的块种类不同就不按相似度配', () => {
const before = [view('1', 'statement', 'call(alpha, beta)')]
const after = [view('2', 'return', 'call(alpha, beta, gamma)')]
expect(alignSiblings(before, '', after, '').toOld).toEqual([undefined])
const source = { text: '' }
const before = [view('1', 'statement', 'call(alpha, beta)', source)]
const after = [view('2', 'return', 'call(alpha, beta, gamma)', source)]
expect(alignSiblings(before, sourced(source.text), after, sourced(source.text)).toOld).toEqual([undefined])
})
it('有名字的块不按相似度配:改名算一删一增', () => {
const source = { text: '' }
const before = [named('a', 'function', 'old_name', 'def old_name(x): return x + 1', source)]
const after = [named('b', 'function', 'new_name', 'def new_name(x): return x + 1', source)]
expect(alignSiblings(before, source.text, after, source.text).toOld).toEqual([undefined])
expect(alignSiblings(before, sourced(source.text), after, sourced(source.text)).toOld).toEqual([undefined])
})
})
+20 -7
View File
@@ -1,6 +1,7 @@
import type { BlockView } from '../blockView'
import type { BlockMarks } from './viewDiff'
import { diffArrays, diffWordsWithSpace } from 'diff'
import { blockCodes } from '../blockView'
import { blockMarks, removedView } from './viewDiff'
/**
@@ -54,6 +55,16 @@ export interface SourcedBlocks {
source: string
}
/** 一份源码,和按行取一块编辑范围源码(去共同缩进)的办法:块的名字、源码都从它取。 */
export interface Sourced {
source: string
code: (view: BlockView) => string
}
export function sourced(source: string): Sourced {
return { source, code: blockCodes(source) }
}
/** 没名字的两块内容相似度(0~1)到这么多才配成修改,否则算一删一增。 */
const SIMILAR = 0.5
/** 没名字的块往后最多找这么多块配相似的,改动很大的文件不至于两两比个遍。 */
@@ -90,15 +101,15 @@ export interface Alignment {
* ③ 夹在相邻两个锚中间、还没配上的没名字的块,照先后找种类相同、内容最像且相似度到 SIMILAR 的配上(算修改)。
* 有名字却没配上的(改了名、删了、新加的)不按相似度配:改名算一删一增。
*/
export function alignSiblings(before: readonly BlockView[], beforeSource: string, after: readonly BlockView[], afterSource: string): Alignment {
export function alignSiblings(before: readonly BlockView[], beforeSource: Sourced, after: readonly BlockView[], afterSource: Sourced): Alignment {
const toOld: (number | undefined)[] = after.map(() => undefined)
const toNew: (number | undefined)[] = before.map(() => undefined)
const pair = (old: number, now: number): void => {
toOld[now] = old
toNew[old] = now
}
const oldNames = before.map(view => nameOf(view, beforeSource))
const newNames = after.map(view => nameOf(view, afterSource))
const oldNames = before.map(view => nameOf(view, beforeSource.source))
const newNames = after.map(view => nameOf(view, afterSource.source))
const waiting = new Map<string, number[]>()
before.forEach((view, old) => {
const name = oldNames[old]
@@ -113,7 +124,7 @@ export function alignSiblings(before: readonly BlockView[], beforeSource: string
})
const unnamedSame = (old: number, now: number): boolean => oldNames[old] === undefined && newNames[now] === undefined
&& before[old].kind === after[now].kind && before[old].code === after[now].code
&& before[old].kind === after[now].kind && beforeSource.code(before[old]) === afterSource.code(after[now])
const changes = diffArrays(before.map((_, i) => i), after.map((_, i) => i), { comparator: (old, now) => toNew[old] === now || unnamedSame(old, now) })
const anchors: [number, number][] = []
let old = 0
@@ -144,7 +155,7 @@ export function alignSiblings(before: readonly BlockView[], beforeSource: string
for (let n = from; n < Math.min(anchorNew, from + SIMILAR_WINDOW); n++) {
if (toOld[n] !== undefined || newNames[n] !== undefined || after[n].kind !== before[o].kind)
continue
const score = similarity(before[o].code, after[n].code)
const score = similarity(beforeSource.code(before[o]), afterSource.code(after[n]))
if (score >= SIMILAR && score > bestScore) {
best = n
bestScore = score
@@ -176,13 +187,15 @@ export function diffBlockTrees(before: SourcedBlocks, after: SourcedBlocks): Tre
const blocks: Record<string, ChangedBlock> = {}
const removed: RemovedBlock[] = []
const stats: DiffStats = { add: 0, mod: 0, del: 0 }
const oldSource = sourced(before.source)
const newSource = sourced(after.source)
/**
* 比一层兄弟块:parent 是这一层所属的新块(顶层没有),inside 表示外层已经带了标记(这一层的改动不再计数)。
* @returns 这一层有没有块新增、修改、删除
*/
const level = (old: readonly BlockView[], now: readonly BlockView[], parent: BlockView | undefined, inside: boolean): boolean => {
const { toOld, toNew } = alignSiblings(old, before.source, now, after.source)
const { toOld, toNew } = alignSiblings(old, oldSource, now, newSource)
let changed = false
now.forEach((view, index) => {
const paired = toOld[index]
@@ -194,7 +207,7 @@ export function diffBlockTrees(before: SourcedBlocks, after: SourcedBlocks): Tre
return
}
const previous = old[paired]
if (previous.code === view.code)
if (oldSource.code(previous) === newSource.code(view))
return
const marks = blockMarks(previous, view)
const childrenChanged = level(previous.children, view.children, view, true)
+2 -2
View File
@@ -16,11 +16,11 @@ function row(label: string, value: Inline[] | { markdown: string }, items?: Prop
}
function textView(translation: Inline[] | { markdown: string }, extra: Partial<BlockView> = {}): BlockView {
return { id: '0', anchor: 'a', kind: 'statement', startLine: 0, endLine: 0, headerEndLine: 0, code: '', codeTokens: [], attributes: {}, children: [], translation, ...extra } as BlockView
return { id: '0', anchor: 'a', kind: 'statement', startLine: 0, endLine: 0, headerEndLine: 0, attributes: {}, children: [], translation, ...extra } as BlockView
}
function propertyView(properties: PropertyRow[]): BlockView {
return { id: '0', anchor: 'a', kind: 'function', startLine: 0, endLine: 0, headerEndLine: 0, code: '', codeTokens: [], attributes: {}, concepts: [{ id: 'function', label: '函数', icon: 'square-function' }], children: [], properties }
return { id: '0', anchor: 'a', kind: 'function', startLine: 0, endLine: 0, headerEndLine: 0, attributes: {}, concepts: [{ id: 'function', label: '函数', icon: 'square-function' }], children: [], properties }
}
describe('inlineDiff', () => {