✨ feat(vscode): 代码阅读侧栏加「未提交的更改」筛选

- 标题栏新按钮「未提交的更改」:树里只剩工作区各 git 仓库里没提交的文件(暂存、没暂存、未跟踪、合并冲突),每个有改动的仓库一个根,跟着 git 状态自动重摆;上色和徽标交给 VS Code 自带的 git 装饰
- 两种筛选互斥,由 TreeFilters 统一管,上下文键合并成 superpowers.codeReader.filter(review / uncommitted)
- 改动文件树改成多根,buildChangedTree 挪到 explorer/changedTree 并改吃 {path, change}
- 内置 git 扩展 API 的类型和加载抽到 src/git/gitApi、gitExtension,提交按钮的监听也改用它
- 文件注释按文件所在的工作区文件夹记,不看树从哪个根摆下来
This commit is contained in:
cruldra committed 2026-09-29 23:42:10 +08:00
1 parent 61c4728dce
commit 7f460d1257
18 files changed
+847 -240

No files matched your search

+18 -52
View File
@@ -1,47 +1,19 @@
import type { Uri } from 'vscode'
import type { GitRepository } from '../../git/gitApi'
import type { KanbanWebviewPanel } from '../KanbanPanel'
import { accessSync, constants } from 'node:fs'
import * as path from 'node:path'
import { extensions, workspace } from 'vscode'
import { workspace } from 'vscode'
import { resolveCommitProfileForRun } from '../../cc/commitProfile'
import { resolveProfilePath } from '../../cc/profiles'
import { spawnClaude } from '../../cc/spawnClaude'
import { findEnvFiles, lockEnvFiles, unlockEnvFiles } from '../../files/envLock'
import { loadGitApi } from '../../git/gitExtension'
import { missingGitIdentityKeys, readGitIdentity } from '../../git/identity'
import { logger } from '../../logging/logger'
import { getEffectiveCommitProfilePath } from '../../settings/store'
import { makeNonce } from '../../webview/html'
import { dismiss, toast } from './handoffFlow'
/**
* Minimal structural typing for the bits of the built-in `vscode.git`
* extension API we consume to observe working-tree state.
* Reference: microsoft/vscode → extensions/git/src/api/git.d.ts
*/
interface GitExtensionApiRepositoryStateChange {
(listener: () => unknown): { dispose: () => void }
}
interface GitExtensionApiRepositoryState {
readonly workingTreeChanges: ReadonlyArray<{ uri: Uri }>
readonly indexChanges: ReadonlyArray<{ uri: Uri }>
readonly untrackedChanges?: ReadonlyArray<{ uri: Uri }>
readonly onDidChange: GitExtensionApiRepositoryStateChange
}
interface GitExtensionApiRepository {
readonly rootUri: Uri
readonly state: GitExtensionApiRepositoryState
}
interface GitExtensionApi {
readonly repositories: ReadonlyArray<GitExtensionApiRepository>
}
interface GitExtensionExports {
getAPI: (version: 1) => GitExtensionApi
}
export async function handleCommitRun(panel: KanbanWebviewPanel): Promise<void> {
if (panel.commitRunning)
return
@@ -266,26 +238,20 @@ export function setupGitWatcher(panel: KanbanWebviewPanel): void {
if (!wsRoot)
return
const gitExt = extensions.getExtension<GitExtensionExports>('vscode.git')
if (!gitExt) {
logger.add({
level: 'warn',
source: 'panel',
message: 'vscode.git 扩展未找到,提交按钮将持续显示',
})
// Fallback: keep the button visible so users can at least trigger
// commits manually even though we can't observe state.
updateHasChanges(panel, true)
return
}
void loadGitApi().then((api) => {
if (!api) {
logger.add({
level: 'warn',
source: 'panel',
message: 'vscode.git 扩展没装或被禁用,提交按钮将持续显示',
})
// Fallback: keep the button visible so users can at least trigger
// commits manually even though we can't observe state.
updateHasChanges(panel, true)
return
}
const activation = gitExt.isActive
? Promise.resolve(gitExt.exports)
: Promise.resolve(gitExt.activate())
void activation.then((exports) => {
const api = exports.getAPI(1)
const findRepo = (): GitExtensionApiRepository | undefined =>
const findRepo = (): GitRepository | undefined =>
api.repositories.find(r => r.rootUri.fsPath === wsRoot)
const updateState = (): void => {
@@ -297,11 +263,11 @@ export function setupGitWatcher(panel: KanbanWebviewPanel): void {
const total
= repo.state.workingTreeChanges.length
+ repo.state.indexChanges.length
+ (repo.state.untrackedChanges?.length ?? 0)
+ repo.state.untrackedChanges.length
updateHasChanges(panel, total > 0)
}
const subscribe = (repo: GitExtensionApiRepository): void => {
const subscribe = (repo: GitRepository): void => {
panel.gitStateDisposable = repo.state.onDidChange(updateState)
}