✨ feat(vscode): 代码阅读差异加「只看改动」(连着没变的块折成一条、按工作区记住)、上一处 / 下一处改动、右边缘总览条
This commit is contained in:
1 parent
707d85c324
commit
6120c85bc0
14 files changed
+533
-42
No files matched your search
@@ -68,3 +68,114 @@ export function removedTitle(block: BlockView): string {
|
||||
: 'markdown' in block.translation ? block.translation.markdown : inlineText(block.translation)
|
||||
return (text ?? '').split('\n', 1)[0].trim()
|
||||
}
|
||||
|
||||
/** 「只看改动」时折起来的:各段连着没变的块(按第一块的 id 记这段几块),和折进去的所有块(连同子孙)。 */
|
||||
export interface FoldedRuns {
|
||||
runs: ReadonlyMap<string, number>
|
||||
folded: ReadonlySet<string>
|
||||
}
|
||||
|
||||
/**
|
||||
* 「只看改动」:每一层连着没变的块折成一段(被筛掉的块不算、不打断),改过的块照常、它下面的子块同样按层折;
|
||||
* 删掉的块排在哪块后面,那一段就在哪儿断开。opened 里的段(按第一块的 id,点开过的)不折;keep 里的块(跳到定义要露出来的)
|
||||
* 和通向它的块不折。
|
||||
*/
|
||||
export function foldUnchanged(blocks: readonly BlockView[], tree: DiffTree, shown: (block: BlockView) => boolean, opened: ReadonlySet<string>, keep: ReadonlySet<string>): FoldedRuns {
|
||||
const runs = new Map<string, number>()
|
||||
const folded = new Set<string>()
|
||||
const fold = (block: BlockView): void => {
|
||||
folded.add(block.id)
|
||||
block.children.forEach(fold)
|
||||
}
|
||||
const kept = (block: BlockView): boolean => keep.has(block.id) || block.children.some(kept)
|
||||
const visit = (list: readonly BlockView[], owner: string): void => {
|
||||
const breaks = new Set((tree.removed.get(owner) ?? []).map(item => item.after))
|
||||
let run: BlockView[] = []
|
||||
const close = (): void => {
|
||||
if (run.length > 0 && !opened.has(run[0].id)) {
|
||||
runs.set(run[0].id, run.length)
|
||||
run.forEach(fold)
|
||||
}
|
||||
run = []
|
||||
}
|
||||
for (const block of list) {
|
||||
if (shown(block)) {
|
||||
if (tree.changes.has(block.id) || kept(block)) {
|
||||
close()
|
||||
visit(block.children, block.id)
|
||||
}
|
||||
else {
|
||||
run.push(block)
|
||||
}
|
||||
}
|
||||
if (breaks.has(block.id))
|
||||
close()
|
||||
}
|
||||
close()
|
||||
}
|
||||
visit(blocks, '')
|
||||
return { runs, folded }
|
||||
}
|
||||
|
||||
/**
|
||||
* 「上一处 / 下一处」和总览条上的一处改动:新增的最外面那块、自己改了的修改块、删掉的块,照页面上的先后。
|
||||
* id 是页面上的块 id(删掉的块是 PlacedRemoved.id);from / to 是它在新文件里的行(0 起),修改块只算它自己那几行(到第一个子块之前);
|
||||
* ancestors 是要展开才看得见它的各层块。
|
||||
*/
|
||||
export interface ChangeStop {
|
||||
id: string
|
||||
change: 'add' | 'mod' | 'del'
|
||||
from: number
|
||||
to: number
|
||||
ancestors: string[]
|
||||
}
|
||||
|
||||
/** 页面上的各处改动(见 ChangeStop);hidden 里的块(被筛掉的)连同子孙不算。 */
|
||||
export function changeStops(blocks: readonly BlockView[], tree: DiffTree, hidden: ReadonlySet<string>): ChangeStop[] {
|
||||
const stops: ChangeStop[] = []
|
||||
const visit = (list: readonly BlockView[], owner: string, ancestors: string[]): void => {
|
||||
const removed = tree.removed.get(owner) ?? []
|
||||
const removedAfter = (id: string | undefined): void => {
|
||||
for (const item of removed.filter(entry => entry.after === id))
|
||||
stops.push({ id: item.id, change: 'del', from: item.removed.line, to: item.removed.line, ancestors })
|
||||
}
|
||||
removedAfter(undefined)
|
||||
for (const block of list) {
|
||||
const change = hidden.has(block.id) ? undefined : tree.changes.get(block.id)
|
||||
if (change?.change === 'add' && change.outermost)
|
||||
stops.push({ id: block.id, change: 'add', from: block.startLine, to: block.endLine, ancestors })
|
||||
if (change?.change === 'mod' && change.own) {
|
||||
const own = block.children[0] ? Math.max(block.startLine, block.children[0].startLine - 1) : block.endLine
|
||||
stops.push({ id: block.id, change: 'mod', from: block.startLine, to: own, ancestors })
|
||||
}
|
||||
if (change?.change === 'mod')
|
||||
visit(block.children, block.id, [...ancestors, block.id])
|
||||
removedAfter(block.id)
|
||||
}
|
||||
}
|
||||
visit(blocks, '', [])
|
||||
return stops
|
||||
}
|
||||
|
||||
/**
|
||||
* 「上一处 / 下一处」跳到第几处:at.index 是接着走的那处(刚跳到的那处还在视口顶上时),否则按视口顶上那块的行 at.line 找:
|
||||
* 下一处是起始行在它后面的第一处,上一处是在它前面的最后一处;走到头绕回另一头。
|
||||
*/
|
||||
export function nextStop(stops: readonly ChangeStop[], at: { index?: number, line?: number }, step: 1 | -1): number {
|
||||
const count = stops.length
|
||||
if (at.index !== undefined)
|
||||
return (at.index + step + count) % count
|
||||
const line = at.line
|
||||
if (line === undefined)
|
||||
return step === 1 ? 0 : count - 1
|
||||
if (step === 1) {
|
||||
const index = stops.findIndex(stop => stop.from > line)
|
||||
return index >= 0 ? index : 0
|
||||
}
|
||||
let last = -1
|
||||
stops.forEach((stop, index) => {
|
||||
if (stop.from < line)
|
||||
last = index
|
||||
})
|
||||
return last >= 0 ? last : count - 1
|
||||
}
|
||||
Reference in new issue
Block a user