✨ feat(vscode): 代码阅读预览按侧栏筛选算代码差异(未提交的更改对比上一次提交、审查对比 PR 目标分支;兄弟块按名字 / 相似度对齐,卡片内按行 / 值 / 词比对),算好后补发给预览

This commit is contained in:
cruldra committed 2026-09-30 08:35:17 +08:00
1 parent 3db44f84ae
commit 2a5806c6b5
23 files changed
+1527 -31

No files matched your search

@@ -1,15 +1,18 @@
import type { ExtensionContext, TreeView, WorkspaceFolder } from 'vscode'
import type { ExtensionContext, TreeView, Uri, WorkspaceFolder } from 'vscode'
import type { GitApi, GitRepository } from '../../git/gitApi'
import type { DiffBase } from '../diff/diffBase'
import type { ChangedFile } from '../explorer/changedTree'
import type { ChangedSection, FileNode, FileTreeProvider, TreeNode } from '../explorer/fileTree'
import type { TreeFilter, TreeFilters } from '../explorer/treeFilters'
import type { UncommittedRoot } from './uncommittedFiles'
import * as path from 'node:path'
import { commands, Disposable, window, workspace } from 'vscode'
import { fileAtRef } from '../../git/fileAt'
import { loadGitApi } from '../../git/gitExtension'
import { logger } from '../../logging/logger'
import { buildChangedTree } from '../explorer/changedTree'
import { uncommittedDescription, uncommittedMessage, uncommittedRoots, UNSTAGED_MARKER, withoutTestFiles, workspaceOrder } from './uncommittedFiles'
import { toPathKey } from '../pathMap'
import { HEAD_BASE_LABEL, headMissingText, isUntracked, uncommittedDescription, uncommittedMessage, uncommittedRoots, UNSTAGED_MARKER, withoutTestFiles, workspaceOrder } from './uncommittedFiles'
/** workspaceState:开着「未提交的更改」,重开窗口接着显示。 */
const UNCOMMITTED_KEY = 'superpowers.codeReader.uncommitted'
@@ -33,7 +36,7 @@ function errorText(err: unknown): string {
/**
* 侧栏只看未提交的更改:照源代码管理视图分「暂存的更改」「更改」两组(空的不摆),每组下面每个有改动的仓库一个根。
* 文件就是工作区里的文件,颜色和 M / A / D 徽标由 VS Code 自带的 git 装饰加上;「更改」组里的文件行上有「暂存」按钮。
* 可以隐藏测试文件。开着时盯着 git 状态自动重摆;「显示全部」回到工作区文件树。
* 可以隐藏测试文件。开着时盯着 git 状态自动重摆,打开的预览对比上一次提交(diffBase);「显示全部」回到工作区文件树。
* 开着没开、隐不隐藏测试文件都按工作区记住;和审查工单互斥,见 TreeFilters。
*/
export class UncommittedMode implements TreeFilter, Disposable {
@@ -123,6 +126,24 @@ export class UncommittedMode implements TreeFilter, Disposable {
}
}
/** 开着时,工作区仓库里的文件对比上一次提交(HEAD);不在工作区的仓库里(审查的工作副本、别处的文件)的不比。 */
diffBase(uri: Uri): DiffBase | undefined {
const repository = this.api?.getRepository(uri)
if (!repository || workspaceOrder(repository.rootUri.path, workspace.workspaceFolders ?? []) < 0)
return undefined
const relative = toPathKey(repository.rootUri.path, uri.path)
if (relative === undefined)
return undefined
const head = repository.state.HEAD?.commit
const root = repository.rootUri.fsPath
return {
label: HEAD_BASE_LABEL,
key: `${head ?? ''}:${relative}`,
read: () => head ? fileAtRef(root, head, relative) : Promise.resolve(null),
missing: headMissingText({ hasCommit: head !== undefined, untracked: isUntracked(repository.state, uri.path) }),
}
}
dispose(): void {
this.stopWatching()
for (const disposable of this.disposables)
@@ -173,12 +194,18 @@ export class UncommittedMode implements TreeFilter, Disposable {
}
}
/** 盯着 git:各仓库的状态变了、开了 / 关了仓库、git 扩展找完仓库、工作区文件夹变了,都合成一次重摆。 */
/**
* 盯着 git:各仓库的状态变了、开了 / 关了仓库、git 扩展找完仓库、工作区文件夹变了,都合成一次重摆;
* 打开着的预览也跟着重新比(提交后 HEAD 动了,对比的上一次提交就换了)。
*/
private watch(api: GitApi): Disposable {
const repositories = new Map<GitRepository, Disposable>()
const schedule = (): void => {
clearTimeout(this.timer)
this.timer = setTimeout(() => this.rebuild(), REBUILD_DELAY_MS)
this.timer = setTimeout(() => {
this.rebuild()
this.filters.baseChanged(this)
}, REBUILD_DELAY_MS)
}
const add = (repository: GitRepository): void => {
if (!repositories.has(repository))