⚡ perf(vscode): 代码阅读预览的文档消息里源码和着色只带一份:块不再各带一份连子块在内的整段源码和上色,编辑框按块的编辑范围从整份源码和着色里取,5126 行的消息 4.3MB → 1.9MB
This commit is contained in:
1 parent
0371712176
commit
2e3602242e
43 files changed
+501
-239
No files matched your search
@@ -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])
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
Reference in new issue
Block a user