feat!: 合并 Git 面板与文件追踪为统一「文件变动」tab(双视角 + 统一 diff 栈)

在 PR #471(file-trace)基础上的演进:

- 合并 tab:git tab(id 保留,兼容持久化布局)升级为「文件变动」双视角
  tab——Git 视角(原 GitView 全部能力:staged/unstaged/提交/历史/worktree·
  子仓库选择/右键菜单/确认弹窗)+ 本轮文件视角(原 file-trace:会话事件
  日志实时折叠的模型读写编辑记录,按文件分组、新增类型筛选 chips、相对
  时间);file-trace tab 移除(未随任何版本发布)。
- 统一 diff 组件:新 src/client/diff/ 模块(rows.ts 引擎 + DiffRows/
  DiffFiles 渲染器 + highlight.ts 语法着色)——git diff 与会话操作 diff
  共用一套渲染:mod 改写配对(del-run/add-run 对半标蓝)、行内字符级
  高亮(修复了旧实现旧侧失配不高亮的问题,改为行对象身份键)、上下文
  hunk 折叠、git hunk 间未输出区间按行号范围生成不可展开折叠、语法着色
  色相落位 diff 三色空档、+n −m 统计 chips、行底色 tint;DiffTab 改用
  该栈(git diff tab 从此也有高亮)。
- 交互:两视角共用底部可拖拽预览面板(点击文件/提交/操作即预览,不再
  直接开 diff tab;面板可刷新、可键盘/拖拽调高、git 目标可一键展开为
  独立 diff tab);视角选择与面板高度持久化在 tab.meta;git 行 XY 徽章
  按状态字母着色;选中行高亮。
- i18n:fileTrace* 十键重命名为 changes* 前缀,git 键值更新为「文件
  变动」,新增 7 键(视角/筛选/面板操作),全部 21 个词典同步。
- 结构:src/client/changes/(ChangesTab/GitLens/SessionLens/DiffPane/
  ops)+ src/client/diff/;GitView/DiffView/filetrace/ 删除;git 样式
  从 sidebar.module.css 迁至 changes.module.css。
- 测试:builtins 8→7(git tab 承接 badge pin)、diff-rows(unified
  转换/mod 配对/间隙折叠/统计)、changes-tab(壳交互 jsdom)、
  diff-files-collapse(原 DiffView 折叠行为钉住)、changes-ops(原
  filetrace 数据层)、git-view-worktree 迁移至 GitLens;全量 1122 通过。
- 文档:README ×2 功能矩阵合并、external-plugin-guide §4.4 与内置清单
  7 tab、mount e2e 菜单标题同步。
This commit is contained in:
Menghuan1918
2026-08-31 01:31:22 +08:00
parent 9255040489
commit 24a3813c63
53 changed files with 2693 additions and 1792 deletions
+6 -414
View File
@@ -2177,141 +2177,9 @@ body[data-dsh-sidebar-dragging] .bottomPanel {
color: var(--dsw-alias-state-error-primary);
}
/* ── Git ───────────────────────────────────────────────────────────────── */
.git {
flex: 1;
min-height: 0;
min-width: 0;
display: flex;
flex-direction: column;
overflow-y: auto;
/* Tab pages never scroll horizontally: content wraps or truncates to the
panel width instead of panning sideways. */
overflow-x: hidden;
}
.gitHeader {
flex: none;
display: flex;
align-items: center;
gap: 8px;
height: 36px;
padding: 0 8px 0 12px;
}
.gitWorktreeRow {
flex: none;
display: flex;
align-items: center;
gap: 8px;
padding: 6px 8px 0 12px;
}
.gitWorktreeLabel {
flex: none;
color: var(--dsw-alias-label-tertiary);
font: var(--dsw-font-xxxs-11);
}
.gitBranchSelect {
flex: 1;
min-width: 0;
height: 26px;
padding: 0 6px;
border: 1px solid var(--dsw-alias-border-l2);
border-radius: 6px;
background: var(--dsw-alias-bg-base);
color: var(--dsw-alias-label-primary);
font: var(--dsw-font-xxs-12);
}
.gitSection {
border-top: 1px solid var(--dsw-alias-border-l1);
}
.gitSectionHeader {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 12px 4px;
font: var(--dsw-font-xxxs-strong-11);
color: var(--dsw-alias-label-tertiary);
text-transform: uppercase;
}
.gitLink {
border: none;
background: transparent;
font: var(--dsw-font-xxxs-11);
color: var(--dsw-alias-brand-primary);
cursor: pointer;
padding: 0;
}
.gitLink:hover:not(:disabled) {
text-decoration: underline;
}
.gitLink:disabled {
opacity: 0.4;
cursor: default;
}
.gitRow {
display: flex;
align-items: center;
gap: 6px;
min-height: 34px;
margin: 0 6px;
padding: 0 8px;
border-radius: 8px;
animation: dsh-row-in 150ms var(--ds-ease-in-out);
}
.gitRow:hover {
background: var(--dsw-alias-interactive-bg-hover);
}
.gitRowSelected {
background: var(--dsw-alias-interactive-bg-active);
}
.gitRowMain {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
gap: 8px;
border: none;
background: transparent;
padding: 3px 0;
cursor: pointer;
text-align: left;
}
.gitBadge {
flex: none;
width: 20px;
height: 16px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 4px;
font: var(--dsw-font-xxxs-strong-11);
background: var(--dsw-alias-interactive-bg-hover);
color: var(--dsw-alias-label-secondary);
}
.gitName {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font: var(--dsw-font-s-14);
color: var(--dsw-alias-label-primary);
}
/* ── Diff tab (generic states + shell; the changes tab owns the rest in
changes.module.css, and the diff rows themselves live in
diff/diff.module.css) ────────────────────────────────────────────────── */
.gitEmpty {
padding: 4px 12px 8px;
@@ -2333,13 +2201,9 @@ body[data-dsh-sidebar-dragging] .bottomPanel {
white-space: pre-wrap;
}
.gitDiff {
border-top: 1px solid var(--dsw-alias-border-l1);
padding: 8px;
}
/* The diff tab (a change opened from the git panel, like VSCode's diff
editor): header + scrollable diff body, no horizontal panning. */
/* The diff tab (a change expanded from the changes tab's preview, like
VSCode's diff editor): header + scrollable diff body, no horizontal
panning; rows render through the shared diff module. */
.gitDiffTab {
flex: 1;
min-height: 0;
@@ -2370,276 +2234,12 @@ body[data-dsh-sidebar-dragging] .bottomPanel {
color: var(--dsw-alias-label-primary);
}
/* One changed file's section: path header, hunk headers, aligned lines. */
.gitDiffFile {
width: 100%;
display: flex;
align-items: baseline;
gap: 6px;
padding: 8px 2px 2px;
border: 0;
background: transparent;
color: inherit;
text-align: left;
cursor: pointer;
}
.gitDiffFile:disabled {
cursor: default;
}
.gitDiffFile:hover:not(:disabled) {
background: var(--dsw-alias-interactive-bg-hover);
}
.gitDiffFileChevron {
flex: none;
color: var(--dsw-alias-label-tertiary);
transform: rotate(0deg);
}
.gitDiffFileChevronExpanded {
transform: rotate(90deg);
}
.gitDiffFilePath {
font: var(--dsw-font-xxs-strong-12);
color: var(--dsw-alias-label-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.gitDiffFileOld {
flex: none;
font: var(--dsw-font-xxxs-11);
color: var(--dsw-alias-label-tertiary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 40%;
}
.gitDiffFileTag {
flex: none;
padding: 0 6px;
border: 1px solid var(--dsw-alias-border-l2);
border-radius: 999px;
font: var(--dsw-font-xxxs-strong-11);
color: var(--dsw-alias-label-secondary);
}
.gitDiffHunk {
display: flex;
gap: 8px;
padding: 3px 2px;
font: var(--dsw-font-markdown-code-block-small);
color: var(--dsw-alias-label-tertiary);
}
.gitDiffHunkHeader {
flex: none;
color: var(--dsw-alias-label-secondary);
}
.gitDiffHunkSection {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* One aligned diff line: old gutter / new gutter / code. Lines WRAP instead
of scrolling horizontally (the tab-page rule: fill the width adaptively),
with the long-token fallback breaking anywhere a space never comes. */
.gitDiffLine {
display: flex;
align-items: stretch;
font: var(--dsw-font-markdown-code-block-small);
line-height: 20px;
white-space: pre-wrap;
overflow-wrap: anywhere;
min-width: 0;
}
.gitDiffNum {
flex: none;
width: 36px;
padding-right: 8px;
text-align: right;
color: var(--dsw-alias-label-tertiary);
user-select: none;
}
.gitDiffCode {
flex: 1;
min-width: 0;
overflow: visible;
}
.gitDiffCtx {
color: var(--dsw-alias-label-primary);
}
.gitDiffDel {
color: var(--dsw-alias-state-error-primary);
background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent);
}
.gitDiffAdd {
color: var(--dsw-alias-state-success-primary);
background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent);
}
.gitDiffMeta {
padding-left: 2px;
}
.gitDiffMetaText {
font: var(--dsw-font-xxxs-11);
font-style: italic;
color: var(--dsw-alias-label-tertiary);
}
.gitDiffExpand {
display: block;
width: 100%;
margin: 4px 0;
border: none;
background: transparent;
font: var(--dsw-font-xxs-12);
color: var(--dsw-alias-brand-primary);
cursor: pointer;
text-align: center;
}
.gitDiffExpand:hover {
background: var(--dsw-alias-interactive-bg-hover);
}
.gitConfirmDesc {
margin: 0;
font: var(--dsw-font-s-14);
color: var(--dsw-alias-label-primary);
white-space: pre-wrap;
}
.gitCommit {
display: flex;
gap: 6px;
padding: 8px 12px;
border-top: 1px solid var(--dsw-alias-border-l1);
align-items: center;
}
.gitCommitInput {
flex: 1;
min-width: 0;
}
.gitCommitButton {
flex: none;
height: 26px;
padding: 0 12px;
border: none;
border-radius: 6px;
background: var(--dsw-alias-button-primary-fill);
color: var(--dsw-alias-label-primary-inverted);
font: var(--dsw-font-xxs-strong-12);
cursor: pointer;
}
.gitCommitButton:hover:not(:disabled) {
background: var(--dsw-alias-button-primary-hover);
}
.gitCommitButton:disabled {
opacity: 0.45;
cursor: default;
}
/* A history row: line 1 = hash + subject, line 2 = ref badges + author/date
(badges live on the second line so they never crowd the subject). */
.gitLogRow {
display: flex;
flex-direction: column;
gap: 2px;
padding: 5px 12px;
cursor: pointer;
border-radius: 8px;
}
.gitLogRow:hover {
background: var(--dsw-alias-interactive-bg-hover);
}
.gitLogLine1 {
display: flex;
align-items: baseline;
gap: 8px;
min-width: 0;
}
.gitLogHash {
flex: none;
font: var(--dsw-font-markdown-code-block-small);
color: var(--dsw-alias-label-tertiary);
}
.gitLogLine2 {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
flex-wrap: wrap;
}
.gitLogRef {
flex: none;
padding: 0 5px;
border: 1px solid var(--dsw-alias-border-l2);
border-radius: 999px;
font: var(--dsw-font-xxxs-strong-11);
color: var(--dsw-alias-brand-primary);
white-space: nowrap;
}
.gitLogSubject {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font: var(--dsw-font-s-14);
color: var(--dsw-alias-label-primary);
}
.gitLogMeta {
font: var(--dsw-font-xxxs-11);
color: var(--dsw-alias-label-tertiary);
}
.gitLogMore {
display: block;
width: calc(100% - 24px);
margin: 4px 12px 8px;
padding: 6px 0;
border: 1px solid var(--dsw-alias-border-l2);
border-radius: 6px;
background: transparent;
font: var(--dsw-font-xxs-12);
color: var(--dsw-alias-label-secondary);
cursor: pointer;
}
.gitLogMore:hover:not(:disabled) {
background: var(--dsw-alias-interactive-bg-hover);
color: var(--dsw-alias-label-primary);
}
.gitLogMore:disabled {
opacity: 0.5;
cursor: default;
}
/* ── Intercepted produced-files row ────────────────────────────────────── */
@@ -2700,13 +2300,6 @@ body[data-dsh-sidebar-dragging] .bottomPanel {
.paneCard:focus-visible,
.explorerRow:focus-visible,
.explorerRef:focus-visible,
.gitRowMain:focus-visible,
.gitLink:focus-visible,
.gitCommitButton:focus-visible,
.gitLogRow:focus-visible,
.gitLogMore:focus-visible,
.gitDiffFile:focus-visible,
.gitDiffExpand:focus-visible,
.terminalRetry:focus-visible,
.editorModeButton:focus-visible,
.editorDownloadLink:focus-visible,
@@ -2728,7 +2321,6 @@ body[data-dsh-sidebar-dragging] .bottomPanel {
.tabBarPlus,
.paneCard,
.explorerRow,
.gitRow,
.divider,
.dividerRow::after,
.dividerCol::after {