✨ feat(vscode): 代码阅读侧栏拆成多栏:常驻未提交的更改、收藏目录各占一栏

This commit is contained in:
cruldra committed 2026-10-12 14:29:48 +11:00
1 parent 45e526afa9
commit b40ddb5e3a
27 files changed
+2427 -674

No files matched your search

@@ -0,0 +1,55 @@
import type { Uri } from 'vscode'
import type { DiffBase, DiffBaseSource } from './diffBase'
import { describe, expect, it, vi } from 'vitest'
import { locatedDiffBases } from './diffBase'
vi.mock('vscode', () => ({
Disposable: {
from: (...items: { dispose: () => void }[]) => ({ dispose: () => items.forEach(item => item.dispose()) }),
},
}))
const uri = (path: string): Uri => ({ path }) as Uri
const base = (label: string): DiffBase => ({ label, key: label, read: async () => null, missing: '' })
/** 只管 root 下面的文件的一方;fire 模拟它的基准变了 */
function source(root: string, label: string): DiffBaseSource & { fire: () => void } {
const listeners = new Set<() => void>()
return {
diffBase: file => file.path.startsWith(`${root}/`) ? base(label) : undefined,
onDidChange: (listener) => {
listeners.add(listener)
return { dispose: () => listeners.delete(listener) }
},
fire: () => listeners.forEach(listener => listener()),
}
}
describe('预览按文件所在的位置取对比基准', () => {
const review = source('/worktrees/issue-446', 'PR 目标分支 master')
const head = source('/ws', '上一次提交')
const bases = locatedDiffBases([review, head])
it('审查工作副本里的对比 PR 目标分支,工作区仓库里的对比上一次提交,都不管的不比', () => {
expect(bases.diffBase(uri('/worktrees/issue-446/src/a.ts'))?.label).toBe('PR 目标分支 master')
expect(bases.diffBase(uri('/ws/src/a.ts'))?.label).toBe('上一次提交')
expect(bases.diffBase(uri('/elsewhere/a.ts'))).toBeUndefined()
})
it('两方都管的文件按顺序取前面那方', () => {
const both = locatedDiffBases([source('/ws', '前面'), source('/ws', '后面')])
expect(both.diffBase(uri('/ws/a.ts'))?.label).toBe('前面')
})
it('哪一方的基准变了都通知预览重新问;不听了就不再通知', () => {
const changed = vi.fn()
const listening = bases.onDidChange(changed)
review.fire()
head.fire()
expect(changed).toHaveBeenCalledTimes(2)
listening.dispose()
head.fire()
expect(changed).toHaveBeenCalledTimes(2)
})
})
+33 -2
View File
@@ -1,6 +1,9 @@
import type { Event, Uri } from 'vscode'
import { Disposable } from 'vscode'
/**
* 对比基准:侧栏只列改动的文件时(「未提交的更改」对比上一次提交,「选工单」审查对比 PR 的目标分支),
* 预览拿它取这个文件改之前的样子,和现在的比出差异。由开着的那种侧栏筛选给(TreeFilter.diffBase)。
* 对比基准:预览拿它取这个文件改之前的样子,和现在的比出差异。
* 按文件所在的位置给(见 locatedDiffBases):审查工作副本里的对比 PR 的目标分支,工作区仓库里有改动的对比上一次提交。
*/
export interface DiffBase {
/** 筛选栏上「对比:」后面的字:「上一次提交」「PR 目标分支 master」 */
@@ -12,3 +15,31 @@ export interface DiffBase {
/** 基准里没有这个文件时横幅上的一句说明 */
missing: string
}
/** 给对比基准的一方:它管的文件给基准,不管的没有;基准变了(HEAD 动了、换了审查的 PR)发 onDidChange,打开着的预览重新问。 */
export interface DiffBaseSource {
diffBase: (uri: Uri) => DiffBase | undefined
readonly onDidChange: Event<void>
}
/**
* 预览对比什么按文件所在的位置定,不看是从哪一栏、点链接还是切标签打开的:
* 按顺序问各方(审查工作副本在前、工作区仓库在后),第一个管这个文件的说了算,都不管的不比;哪一方的基准变了都通知。
*/
export function locatedDiffBases(sources: readonly DiffBaseSource[]): DiffBaseSource {
return {
diffBase: (uri) => {
for (const source of sources) {
const base = source.diffBase(uri)
if (base)
return base
}
return undefined
},
onDidChange: (listener, thisArgs, disposables) => {
const all = Disposable.from(...sources.map(source => source.onDidChange(listener, thisArgs)))
disposables?.push(all)
return all
},
}
}