fix:macOS 项目树支持多选与批量文件操作 - #991
Conversation
补充:macOS 项目树真实组合输入验证2026-10-01 在本地构建的 Lithe 中完成手动实测,贡献者确认以下操作均正常:
截图显示 该截图已随 PR 提交,功能矩阵的 macOS 验证状态已更新为 |
1lck
left a comment
There was a problem hiding this comment.
已按仓库 lithe-code-review / develop-lithe 规则审查。结论:存在一项阻塞问题,建议修复后合并;另有一处生成文件冲突需要处理。
审查 head:1f999d74318821465817eaee0d3a12ababc9e22f,共 19 个变更文件。冲突检查使用远端 preview:4773688eb91b04c56caaf8d909b0e220de5d98f6。
1. [P1] 批量删除期间,后续文件的新编辑可能被直接丢弃
位置:WorkspaceFeatureModel.swift:741–748
未保存检查只在 requestDeleteProjectItems 弹出确认前执行。新增批量循环逐项 await 删除,期间编辑器仍可使用,后续项目删除前没有再次检查脏状态。
触发路径:选中目录 A 和文件 B → 确认批量删除 → A 的废纸篓操作较慢时编辑 B → 循环继续删除 B。删除成功后通过 WorkspaceProjectionComposition 调用 DocumentFeatureModel.closeDocuments(containedIn:),后者直接关闭文档并取消自动保存;这段新增的内存修改无法从废纸篓恢复。
来源:本次批量流程引入了首项等待期间修改后续文件的路径。此结论来自静态调用链,尚未在 macOS 实机复现。
建议:每项删除前重新检查未保存状态,并确保原生删除等待期间产生的编辑不会被无提示关闭。增加可控暂停首项删除、将后续文档改脏再继续的回归测试,断言修改被保留且没有静默删除/关闭。
2. [P2] 键盘切换焦点后,项目树仍会吞掉文本快捷键
位置:ProjectTreeKeyboardCommands.swift:55–63
ownsKeyboard 只随鼠标事件改变,没有检查当前第一响应者。点击项目树后,用快捷键打开全局搜索;SearchEverywhereView 自动聚焦输入框,但同窗口内仍可见的项目树继续拦截 ⌘A / ⌘C / ⌘V。搜索框无法执行这些文本操作;剪贴板含文件 URL 时,⌘V 还可能向项目目录复制文件。
来源:本次引入。建议依据实际焦点或 responder chain 分发快捷键,并覆盖“项目树 → 键盘打开搜索 → 文本复制粘贴”的集成测试。P2 为建议修改项,不单独作为阻塞项。
冲突文件与处理建议
使用 git merge-tree --write-tree --name-only 检查以上精确 base/head,只有一处文本冲突:
docs/development/platform-parity-matrix.md顶部功能数量及实现/验证状态统计。
没有 Swift 源码冲突。双方分别增加了能力记录,shared/platform-feature-matrix.json 和 CSV 能自动合并。请保留双方 JSON 内容,再重新生成两个视图,不要直接选择一方的 Markdown 统计:
node scripts/generate-platform-feature-matrix.mjs
./scripts/verify-platform-feature-matrix.sh按上述两个提交合并,JSON 中应有 121 项能力。后续目标分支若有新提交,以重新生成的结果为准。
范围与验证
- #987 要求的多选、范围选择、右键保留集合及批量操作已有实现;原生剪贴板通过平台适配层接入,整体分层合理。
- 已有 CI 记录通过,PR 也提供了 macOS 手动验证记录,但新增测试未覆盖上述两个场景。
- 本次实际通过:运行时资源只读、变更测试稳定性、功能矩阵一致性及变更检查、
git diff --check。 - 审查环境为 Linux,未重新运行 macOS 构建、完整测试或 UI 验证。
- 本次仅审查,没有修改实现或解决冲突。
# Conflicts: # docs/development/platform-parity-matrix.md
- 批量删除:每项删除前重新检查未保存文档;废纸篓操作期间被改脏的文档保持打开, 仅关闭未修改的文档;中途失败或被未保存文档阻止时提示剩余项目未移动。 - 快捷键归属:点击项目树后若键盘焦点移到文本框(如全局搜索)或其他控件, ⌘A/⌘C/⌘V 交还给当前焦点,不再误复制或粘贴文件。 - Control 点击恢复为 macOS 右键菜单,⌘ 点击负责增减选择;移除为此新增的 litheContextMenu(capturesControlClick:) 参数。 - 单选高亮跟随活动编辑器文档;去掉 ProjectFileTreeContent 中冗余的可见节点比较。 - 中文“拷贝”与“复制(创建副本)”区分;补齐粘贴校验和批量删除提示的中英文案。 - 新增单元、焦点与应用级集成测试;功能矩阵改回待验证。
本地事件监听里的 `self?.handle(event) ?? event` 会把表示“已处理”的 nil 展平成原事件,导致 ⌘A/⌘C/⌘V 在项目树处理后仍传给编辑器:⌘A 同时全选编辑器文本, ⌘V 同时把剪贴板文本粘进编辑器。改为直接返回 handle 的结果并补回归测试。 粘贴完成的提示由“已复制文件”改为“已粘贴文件”,避免复制和粘贴各弹一条相同提示。
焦点在展开的子目录内时,⌘A 只选中该目录下的可见项目;焦点在项目顶层时选中项目下全部可见项目(含子目录及其展开内容,不含根行);焦点在根行或无焦点时选中整棵可见树。重复 ⌘A 保持同一范围,并补充同名前缀目录不被误选的测试。
点击文件夹后按 ⌘A,选中与该文件夹同级的项目,而不是它的内容;焦点在文件上时选中同目录文件;无焦点或焦点在项目行时选中项目顶层项目。同级关系按显示的树结构计算,压缩显示的 Java 包也使用其显示父节点。批量操作会自动包含已选文件夹的内容。
文件夹被选中时,其下展开的文件和子目录也显示选中高亮,与批量操作会包含文件夹内容保持一致。这些子项只用于显示,不会重复加入操作集合;在多选组内右键这些子项仍保持整组,单独选中一个文件夹时右键其中的文件则以该文件为目标。
|
@1lck 感谢审查。两项问题和冲突都已处理,当前 head 是 1. [P1] 批量删除期间,后续文件的新编辑可能被直接丢弃
2. [P2] 键盘切换焦点后,项目树仍会吞掉文本快捷键
冲突已合并最新的 其他改动
验证
|
1lck
left a comment
There was a problem hiding this comment.
Lithe Review|最新提交复核
结论:未发现需要阻塞合并的问题。此前指出的删除期间丢编辑 P1 和搜索框快捷键 P2,在 00e0eb3 中已有针对性修复,不再按旧代码结论列为阻塞。
范围:preview afb7a76 ← 00e0eb3,19 个变更文件,重点追踪两项旧 finding 及其新增回归测试。
旧问题复核
- WorkspaceFeatureModel.swift:739–766 现在逐项执行删除,并在每项前从当前 documentsProvider 重查未保存状态;真实 composition 读取的是最新 openDocuments。815–825 行只关闭仍干净的文档,Trash 等待期间新增的编辑会保留。新增可控暂停首项删除、后续文件变脏、自身变脏和失败用例与问题路径对应。
- ProjectTreeKeyboardCommands.swift:77–113 已检查实际 firstResponder,并把共享 NSTextView field editor 归一到所属 NSTextField。搜索输入框获得焦点后交还快捷键;已消费事件直接返回 nil,避免继续传给编辑器。
Scope Check
#987 的多选、范围选择、右键保留选中组,以及批量拷贝/粘贴、创建副本和移到废纸篓已有实现。选择状态、平台剪贴板 adapter 和工作流编排分工合理。Windows 未实现,普通批量剪切/跨目录移动也不在本 PR 范围。
非阻塞建议
可再补“点击项目树后,不先按普通键,直接双 Shift 打开搜索,再首次按 ⌘A”的自动回归,锁住真实焦点切换入口。作者已提供该路径的手动复测记录,本次没有新增高置信缺陷。
Verification
本次仅静态只读复核,未运行应用、测试或仓库脚本。作者报告的 1,344 个测试及实机验收未由本次独立重跑。查询时当前 HEAD 的部分 CI 仍在运行/排队,不能称为全部通过。此前生成文件冲突已处理,当前 GitHub 显示 mergeable=true;这不等于所有合并门禁已满足。
|
补充 00e0eb3 的最终 CI 状态:macOS、Windows、Database 和功能矩阵已通过;macOS 主 Swift lane 的 1,583 项测试通过,也覆盖这次批量删除竞态、焦点切换和文件工作流的新增回归。 但 macOS Plugin CI 仍失败:带 960 秒上限的测试步骤在编译 LitheTests 时到期,日志尚未进入实际测试执行,后面的官方插件打包也被跳过。编译期间另有 DecodingError / unexpected end of file,和超时是否相关还需要定位。 请查清并恢复这项 CI 后再收尾。目前没有证据把它归因为项目树逻辑回归,也不能直接认定是偶发环境问题;此前“未发现代码层面阻塞问题”的静态审查结论不等于 CI 全绿。 |
上次运行在 960 秒时限内仍在冷编译 LitheTests(Rust 4m39s,正常约 3m),尚未进入测试执行。同一提交的 macOS 主 Swift lane 已通过,本地按相同过滤条件运行 59 项插件测试全部通过。
|
重试结果已核对:a5a79395 是空提交,代码与本次审过的 00e0eb3 相同。当前 macOS、Windows、Database、Plugin 和功能矩阵五项工作流均已通过,之前的 Plugin CI 门禁失败已恢复。 Plugin 重试中 59 项测试和两个官方插件包验证通过:https://github.com/1lck/Lithe-IDEA/actions/runs/36902954392 上次超时的确切原因仍未证实;本条只更新已核实的重试结果。此前代码初审结论保持不变,原生交互验收仍以对应平台的实际记录为准。 |
SwiftUI Button 的动作在鼠标抬起后才执行,此时 NSApp.currentEvent 已不再是这次点击, 读到的修饰键为空,Shift 点击因此退化成普通单选、范围不生效。 - 新增 ProjectTreeModifiedClick 覆盖在每一行上:只在带 ⌘/Shift 的左键按下时命中, 直接用该 mouseDown 事件的修饰键更新选择,并且不再传给 Button,因此不会打开文件或折叠目录。 - 普通点击和 Control 点击(右键菜单)都穿透到原有的 Button 和右键捕获视图,行为不变。 - selectRow 改为接收修饰键参数,Button 动作只做普通单选。 - 新增窗口级回归测试,覆盖 Shift 正向/反向范围选择、⌘ 取消选择、修饰点击不触发打开,以及 Control+Shift 不被捕获。
|
补充修复
|
1lck
left a comment
There was a problem hiding this comment.
Lithe Review|修饰点击增量复核
范围:a5a79395 → f5ea06b,1 个提交、6 个文件。
结论:未发现需要阻塞合并的问题。
把修饰键读取移到原生 mouseDown,并在文件行、目录行接入独立捕获层,能够针对这次 SwiftUI Button 延迟执行导致丢失修饰键的问题;Control 点击仍交给原有右键入口。选择算法及此前删除保护没有改动。
当前五项 CI 均通过,macOS 主 Swift lane 的 1,584 项测试包含本次新增回归。
非阻塞建议(P2):新增窗口测试在 ProjectTreeSelectionTests.swift:156–169 直接调用 overlay.mouseDown,绕过 hitTest 和真实事件分发。即使生产 hitTest 始终返回 nil,此测试也可能通过,因此 opened.isEmpty 尚不能证明修饰点击实际命中 overlay 且不会触发底层 Button。
建议补经过实际 AppKit/SwiftUI 事件分发的集成或 UI 回归,覆盖普通点击建立锚点、Shift/⌘ 多选、不打开文件或折叠目录,以及 Control 右键穿透。
https://github.com/Absinthe-yl/Lithe-IDEA/blob/f5ea06bc00b1941c99f4c5d59afdf3c27132e5ce/macos/Tests/LitheTests/ProjectTreeSelectionTests.swift#L156-L183
这是本次新增测试的证据缺口,不是已经证实的运行时回归。本轮仅静态增量复审及现有 CI 日志核对,没有独立运行原生界面;作者手动验证与自动测试覆盖范围分开看待。
审查指出原测试直接调用 overlay.mouseDown,绕过 hitTest 和窗口分发,即使 生产 hitTest 恒返回 nil 也会通过。 - 测试改为把合成的鼠标按下/抬起事件交给 NSWindow.sendEvent,由窗口命中测试 决定交给覆盖层还是底层 Button;覆盖新增普通点击建立锚点、Shift 正反向范围、 ⌘ 增减选择、修饰点击不触发 Button(不打开文件、不折叠目录)以及 Control 点击穿透到底层行。 - ProjectTreeModifiedClickView 的当前事件来源改为可替换的 currentEvent,默认仍是 NSApp.currentEvent。测试进程里经 NSApp 事件队列取出事件会让 swift-testing 的 主队列提前退出,后续测试不再执行,所以测试只替换这一处查询。 - 反向验证:hitTest 恒返回 nil、或捕获 Control 点击时,新测试均失败。
|
@1lck 已处理修饰点击测试的 P2,提交 改动
为什么没有让测试经过 反向验证(在生产代码里临时制造错误,确认新测试会失败)
验证
|
xiaoyumuxi
left a comment
There was a problem hiding this comment.
Lithe Review|当前 HEAD 复核
未发现需要阻塞合并的 P0/P1 问题。此前批量删除丢编辑、搜索框快捷键归属,以及修饰点击测试证据缺口,在当前 HEAD 都已有针对性处理。
本轮留下 2 条非阻塞建议:一处批量文件操作的 operation ownership 并发窗口(P2),以及一处中文“拷贝 / 复制”文案不一致(P3)。
当前 Swift tests、Platform Feature Matrix、Database CI、Windows CI、macOS Plugin CI 已通过;提交本 review 时 macOS CI 的部分 package / bridge job 仍在运行,因此不把当前状态描述为全部 CI 已完成。
| let sources = topLevelProjectItems(urls) | ||
| for directory in Set(sources.map { $0.deletingLastPathComponent() }).sorted(by: { $0.path < $1.path }) { | ||
| guard workspaceGeneration == generation else { return } | ||
| await pasteProjectItems(sources.filter { $0.deletingLastPathComponent() == directory }, in: directory) |
There was a problem hiding this comment.
[P2] 这里的 batch ownership 没有覆盖整个批次。pasteProjectItems 在单组 copy 完成后会先把 isPerformingProjectItemOperation 设回 false,再 await refreshCurrent();于是第一组 refresh 期间,用户可以启动另一个 Paste/Duplicate/Delete。
若新操作此时重新占用 busy,原批次进入下一目录时这里再次调用 pasteProjectItems,会被它开头的 guard !isPerformingProjectItemOperation 静默跳过,导致一次批量 Duplicate 只完成前半段。批量 Trash 在单项完成后提前释放 busy、再 refresh/继续下一项,也有同类窗口。
建议让 busy/token 由外层 batch 持有到整个批次结束,内层拆成不自行 acquire/release 的单项 primitive,并补一个 gated-refresh 回归测试覆盖“第一项完成、refresh 未结束时触发第二个文件操作”的场景。
There was a problem hiding this comment.
已在 cd073b6c 修复。
duplicateProjectItems和confirmProjectItemDeletion改为由外层批次持有isPerformingProjectItemOperation,直到整批结束才释放,最后只刷新一次。- 单项逻辑拆成
pasteProjectItemsWithinOperation/trashProjectItemWithinOperation,这两个函数不自行加锁,也不刷新。单项粘贴和单项删除走同一套实现,行为不变。 - 新增回归测试
batchFileOperationsHoldTheOperationUntilTheWholeBatchFinishes:- 第一组复制在工作线程上暂停时,操作锁保持占用;这时插入的粘贴被拒绝,两组副本都能完成。
- 批量复制和批量删除都只在最后一项完成后各扫描一次(记录为
[[2, 0], [2, 2]])。
- 反向验证:恢复旧的逐组释放锁实现后,记录变成
[[1, 0], [2, 0], [2, 2]],测试失败。
| "Choose a default web browser in System Settings, then retry." = "请在系统设置中选择默认网页浏览器,然后重试。"; | ||
|
|
||
| "Paste" = "粘贴"; | ||
| "Copied files" = "已复制文件"; |
There was a problem hiding this comment.
[P3] 这里建议改成“已拷贝文件”。本 PR 已明确用“拷贝”表示写入剪贴板、用“复制”表示 Duplicate;菜单的 Copy Files 也已经翻成“拷贝”。执行“拷贝”后提示“已复制文件”会再次把两种操作混在一起。
There was a problem hiding this comment.
已在 cd073b6c 修改:"Copied files" 改为"已拷贝文件",和菜单里的"拷贝"保持一致。
处理 1lck#991 审查的 P2/P3: - 批量创建副本和批量移到废纸篓原来逐项调用单项入口,每项结束后都会先释放 isPerformingProjectItemOperation 再 await 刷新。刷新期间用户可以启动别的文件操作, 原批次的后续项随后被入口守卫静默跳过。现在由外层批次持有操作锁直到整批结束, 内层拆成不自行加锁、不刷新的单项实现,整批完成后只刷新一次。 - 单项粘贴和单项删除仍通过同一实现,行为不变。 - 中文“Copied files”改为“已拷贝文件”,与菜单“拷贝”一致。 - 新增回归:第一组复制暂停时操作锁保持占用、插入的粘贴被拒绝,批量复制和 批量删除各只在最后一项完成后刷新一次。恢复旧实现时该测试失败。
# Conflicts: # docs/development/platform-parity-matrix.md
* fix(agent): 统一权限菜单与英文思考强度显示 (#1008) * Merge pull request #1023 from Mucheen/fix/agent-selector-labels fix(macos): 收紧 Agent 配置菜单并修复双行说明裁剪 * fix:macOS 项目树支持多选与批量文件操作 (#991) * fix(macOS): 支持项目树多选与批量文件操作 (#987) * docs: 记录 #987 macOS 项目树手动验证结果 * docs: 添加 #987 项目树多选手动验证截图 * fix(macOS): 处理 #991 审查意见并补齐项目树批量操作边界 - 批量删除:每项删除前重新检查未保存文档;废纸篓操作期间被改脏的文档保持打开, 仅关闭未修改的文档;中途失败或被未保存文档阻止时提示剩余项目未移动。 - 快捷键归属:点击项目树后若键盘焦点移到文本框(如全局搜索)或其他控件, ⌘A/⌘C/⌘V 交还给当前焦点,不再误复制或粘贴文件。 - Control 点击恢复为 macOS 右键菜单,⌘ 点击负责增减选择;移除为此新增的 litheContextMenu(capturesControlClick:) 参数。 - 单选高亮跟随活动编辑器文档;去掉 ProjectFileTreeContent 中冗余的可见节点比较。 - 中文“拷贝”与“复制(创建副本)”区分;补齐粘贴校验和批量删除提示的中英文案。 - 新增单元、焦点与应用级集成测试;功能矩阵改回待验证。 * fix(macOS): 项目树快捷键被处理后不再同时传给编辑器 本地事件监听里的 `self?.handle(event) ?? event` 会把表示“已处理”的 nil 展平成原事件,导致 ⌘A/⌘C/⌘V 在项目树处理后仍传给编辑器:⌘A 同时全选编辑器文本, ⌘V 同时把剪贴板文本粘进编辑器。改为直接返回 handle 的结果并补回归测试。 粘贴完成的提示由“已复制文件”改为“已粘贴文件”,避免复制和粘贴各弹一条相同提示。 * fix(macOS): 项目树 ⌘A 按焦点所在目录全选 焦点在展开的子目录内时,⌘A 只选中该目录下的可见项目;焦点在项目顶层时选中项目下全部可见项目(含子目录及其展开内容,不含根行);焦点在根行或无焦点时选中整棵可见树。重复 ⌘A 保持同一范围,并补充同名前缀目录不被误选的测试。 * fix(macOS): 项目树 ⌘A 选中焦点项的同级项目 点击文件夹后按 ⌘A,选中与该文件夹同级的项目,而不是它的内容;焦点在文件上时选中同目录文件;无焦点或焦点在项目行时选中项目顶层项目。同级关系按显示的树结构计算,压缩显示的 Java 包也使用其显示父节点。批量操作会自动包含已选文件夹的内容。 * fix(macOS): 已选文件夹下的展开内容同时显示为选中 文件夹被选中时,其下展开的文件和子目录也显示选中高亮,与批量操作会包含文件夹内容保持一致。这些子项只用于显示,不会重复加入操作集合;在多选组内右键这些子项仍保持整组,单独选中一个文件夹时右键其中的文件则以该文件为目标。 * ci: 重新触发 macOS Plugin CI 上次运行在 960 秒时限内仍在冷编译 LitheTests(Rust 4m39s,正常约 3m),尚未进入测试执行。同一提交的 macOS 主 Swift lane 已通过,本地按相同过滤条件运行 59 项插件测试全部通过。 * fix(macOS): 项目树 Shift/⌘ 点击从鼠标按下事件读取修饰键 SwiftUI Button 的动作在鼠标抬起后才执行,此时 NSApp.currentEvent 已不再是这次点击, 读到的修饰键为空,Shift 点击因此退化成普通单选、范围不生效。 - 新增 ProjectTreeModifiedClick 覆盖在每一行上:只在带 ⌘/Shift 的左键按下时命中, 直接用该 mouseDown 事件的修饰键更新选择,并且不再传给 Button,因此不会打开文件或折叠目录。 - 普通点击和 Control 点击(右键菜单)都穿透到原有的 Button 和右键捕获视图,行为不变。 - selectRow 改为接收修饰键参数,Button 动作只做普通单选。 - 新增窗口级回归测试,覆盖 Shift 正向/反向范围选择、⌘ 取消选择、修饰点击不触发打开,以及 Control+Shift 不被捕获。 * test(macOS): 项目树修饰点击回归改为经窗口命中测试分发 审查指出原测试直接调用 overlay.mouseDown,绕过 hitTest 和窗口分发,即使 生产 hitTest 恒返回 nil 也会通过。 - 测试改为把合成的鼠标按下/抬起事件交给 NSWindow.sendEvent,由窗口命中测试 决定交给覆盖层还是底层 Button;覆盖新增普通点击建立锚点、Shift 正反向范围、 ⌘ 增减选择、修饰点击不触发 Button(不打开文件、不折叠目录)以及 Control 点击穿透到底层行。 - ProjectTreeModifiedClickView 的当前事件来源改为可替换的 currentEvent,默认仍是 NSApp.currentEvent。测试进程里经 NSApp 事件队列取出事件会让 swift-testing 的 主队列提前退出,后续测试不再执行,所以测试只替换这一处查询。 - 反向验证:hitTest 恒返回 nil、或捕获 Control 点击时,新测试均失败。 * fix(macOS): 批量文件操作在整批结束前持有操作锁 处理 #991 审查的 P2/P3: - 批量创建副本和批量移到废纸篓原来逐项调用单项入口,每项结束后都会先释放 isPerformingProjectItemOperation 再 await 刷新。刷新期间用户可以启动别的文件操作, 原批次的后续项随后被入口守卫静默跳过。现在由外层批次持有操作锁直到整批结束, 内层拆成不自行加锁、不刷新的单项实现,整批完成后只刷新一次。 - 单项粘贴和单项删除仍通过同一实现,行为不变。 - 中文“Copied files”改为“已拷贝文件”,与菜单“拷贝”一致。 - 新增回归:第一组复制暂停时操作锁保持占用、插入的粘贴被拒绝,批量复制和 批量删除各只在最后一项完成后刷新一次。恢复旧实现时该测试失败。 --------- Co-authored-by: Lichenkang <2188718831@qq.com> * fix(agent): 修复 Claude 鉴权、会话切换和品牌图标 (#1007) * fix(agent): 修复 Claude 鉴权、会话切换和品牌图标 通过 Claude 适配器公开会话参数传递 API Key 路由,同时覆盖新建与历史恢复,清除冲突鉴权并保留 Codex gateway 行为。 直接观察所选 Agent 连接,等待会话设置确认后展示模型,防止旧配置闪现及准备期间提交。 统一底栏、模型入口与 Agent 菜单品牌图标,保留原生单选勾选,并补充回归、共享契约、Agent Note 与功能矩阵。 * test(git): bound graph frame sampling to visible viewports 修复 PR #1007 CI:原用例重复绘制完整 1000 行历史,CI 耗时 11.791 秒超过未调整的 10 秒上限。改为开头、中段、末段 40 行视口采样,校验实际像素,保留完整结构、单帧预算与 Release 基线;同步 CI 文档和中文工程笔记。 * feat(agent): 显示 Agent 思考、计划与斜杠命令补全 (#1016) * feat(agent): 显示 Agent 思考、计划与斜杠命令补全 共享 Host 原本就透传 ACP SessionUpdate,但 macOS 只处理消息、工具、 标题、配置和用量,其余更新被丢弃。本次补齐四类标准更新: - agent_thought_chunk:独立的可折叠思考块,流式输出时展开、回复或 工具到达后收起;缓冲按角色切分,思考与回复不再混在一起;导出 Markdown 以引用块保留,历史消息数不计思考 - plan:计划栏固定在活动统计栏上方,显示进度与当前步骤;每次整体 替换,空计划清除 - available_commands_update:输入 / 弹出 Agent 上报的命令补全,前缀 优先;Codex 的 $Skill 补全为 $name(codex-acp 不解析 /$name), 输入 $ 只列 Skill;macOS 14+ 支持方向键与 Tab - current_mode_update:Agent 自行切换模式时同步权限模式选择器 另外把模型层的兜底文案、stopReason 提示和工具证据标题改为本地化, 补齐 zh-Hans 翻译。 fixture 新增四类事件样例并加入 Rust 往返测试;更新功能矩阵与 shared-acp Agent Note。真实 Agent 已在协议层确认 Codex/Claude 会上报 命令列表、Claude 会上报思考,界面层验证仍待完成。 * fix(agent): 修复命令补全布局和思考搜索展开 为输入区保留可编辑行,空间不足时在对话布局中显示可点击的命令浮层。搜索临时展开匹配思考块,清空后恢复手动选择。补充三项回归测试并同步功能矩阵与架构笔记。 * fix(ci): release Windows Bun install process trees before retry --------- Co-authored-by: Yao Jingxi <23722032@bjtu.edu.cn> * feat(windows): 活动栏/标题栏/Run 面板图标替换为 IDEA 图标,并对齐 Commit 面板与共用控件 (#1025) * feat(windows): 共用 UI 组件补齐 IDEA 风格交互基础能力 - 滚动条:修复 ScrollArea 滑块带 flex-1 被拉满整条轨道的问题,改由 Base UI 按可视比例计算滑块长度(全局所有滚动区域受益) - 触控板:ScrollArea 的滚轮转发支持横向滑动,仅横向可滚动的容器才 处理,纵向列表行为不变 - 树行 SidebarTreeRow: - 新增 leadingAction 左侧操作位(位于展开箭头之后、图标之前), 渲染在行按钮外并按缩进定位,避免按钮嵌套交互元素 - 新增 variant="idea":16px 细线 chevron(IDEA expui 路径、 tree-chevron 颜色、悬停不变色),箭头/复选框/图标/文字 4px 间距, 图标与文字垂直居中 - 文字区增加 data-sidebar-tree-label 供横向滚动测量 - 复选框:支持 indeterminate 半选状态,显示短横 - 拖动会话 document-resize-session:新增 axis: "y",可驱动纵向高度 - 新增对应单测(横向滑动、纵向拖动) * feat(windows): 默认主题 Git 状态配色与表单控件 token 对齐 IDEA 配色取自 intellij-community expUI 明暗配色方案(FILESTATUS_* 与 *_LINES_COLOR): - modified / renamed:#0033B3 / #70AEFF;暂存修改为浅蓝派生 #2652BE / #85BAFF - added:#067D17 / #73BD79;untracked:#B23247 / #E88F89 - 新增可选 token git-file-deleted(删除文件灰 #6C707E / #6F737A); git-deleted 仍为红色,继续用于 diff 删除行与 -N 计数 - 新增可选 token git-gutter-added/modified/deleted,编辑器 gutter 使用 IDEA 浅色行标记(#7FC784/#88ADF7/#767A8A,暗色 #549159/#375FAD/#868A91) - 变更列表、提交文件树、文件树的状态颜色统一走 git-* token, 不再使用 text-info / 灰色 / 红色临时映射 - 新增 IDEA 表单控件 token:control-border、checkbox-border、 checkbox-background、tree-chevron - 以上新 token 在其他主题中未定义时,均回退到原有颜色 - rebase / patch / 历史改写对话框的警告文案与操作横幅由误用的 text-git-modified 改回 text-warning,避免随 modified 变蓝 * feat(windows): Commit 面板对齐 IntelliJ(提交区、变更列表、Amend 与视图选项) 面板与入口 - 侧栏 git 入口与面板标题由 Changes / Source Control 更名为 Commit(提交), 图标换为 IntelliJ commit 图标;中英文案、tooltip、aria 同步 - 移除面板内 Changes / History 标签栏与 git-commit-history 组件,提交历史 统一通过底部 Git Log 工具窗浏览;命令面板去掉 "Git: Show History" - 清理仅服务于标签的设置 rememberLastGitPanelMode / gitLastPanelMode / gitSidebarTabOrder,并加入 RETIRED_SETTINGS_KEYS 清理升级用户的残留键 - Git 数据控制器不再读取 `git log --all`;"与提交比较"选择器打开时才按需 加载最近 50 条提交 提交区(对齐 NonModalCommitPanel / CommitInputBorder / CommitLegendComponent) - 布局:顶部左侧 Amend、右侧已选文件图例(N 添加 / N 修改 / N 删除,未跟踪 计入添加并显示为“新增+未跟踪”,重命名计入修改,按状态着色);中间提交 说明框;底部 Commit 与 Commit and Push... 线框按钮,右侧推送/拉取计数、 AI 与设置 - 与变更列表之间只有一条分割线、无外框;拖动分割线或聚焦后按上/下方向键 调整说明框高度(变更列表至少 96px,说明框最小 48px),松手后持久化 - 说明框:占位文字 "Commit Message / 提交说明";4px 圆角、1px 控件边框, 聚焦 2px 强调色,出错时错误色 - 按钮不再因未选文件或说明为空而置灰;点击后按 IntelliJ CommitProgressPanel 文案提示(请选择要提交的文件 / 请填写提交说明 / 二者合并),修正后消失; 焦点在提交区内时 Commit 变为蓝色默认按钮 - Amend:勾选时读取当前 HEAD 的完整说明(标题 + 正文,新增 getHeadCommitMessage,不再取 `git log --all` 列表首条);空草稿自动载入、 不覆盖已输入内容、取消勾选且未编辑时恢复原草稿;HEAD 无提交时提示; Amend 模式下主按钮显示 Amend Commit,Commit and Push... 禁用(修正后 需要强制推送),多仓库确认对话框按钮同步 变更列表 - 分组改为 IntelliJ 的 "Changes / 更改" 与 "Unversioned Files / 未版本控制 的文件" 两个可展开树节点,节点名后显示灰色 "N 个文件" - 文件、文件夹、分组节点的包含复选框移到左侧(箭头之后、图标之前), IDEA 新 UI 样式,分组与文件夹支持三态;文件夹名后紧跟灰色文件数 - 去掉文件行末尾的暂存 +/- 按钮及其工具代码 - 长文件名不截断,改为横向滚动:按文字真实小数宽度判断截断并留 1px 余量, 面板从隐藏切回显示、侧栏改宽或字体加载完成后重新测量;状态刷新不再重置宽度 - 参考 IntelliJ ExpandableItemsHandler:名称超出可视右边缘时,悬停该行在 行上方弹出完整名称(含文件数或目录),移开、滚动或点击即消失 - 工具栏新增 View Options(眼睛图标),菜单直接列出 "Directory / 目录" 与 "Flat List / 平铺显示" 并勾选当前项,两次点击切换 其他 - 新增/更新单测:提交面板 Amend 与提示、拖动高度、分组节点与三态、复选框 位置、行宽测量、悬停提示判断、视图选项切换、数据控制器不读取历史 - 更新 platform-feature-matrix 的 git-status-commit、git-history 行,新增 git-status-colors-idea-alignment 行,并重新生成视图 * feat(windows): 活动栏、标题栏菜单与 Run 面板图标替换为 IDEA 图标 - 活动栏 Project / Git Log / Terminal / Run / Diagnostics 改用 IntelliJ expui toolwindows 图标 (project、vcs、terminal、run、problems),亮色与暗色各一套 - 标题栏紧凑菜单开关新增 MenuIcon(expui/general/menu),不影响其他使用 ListIcon 的位置 - 新增 RunToolWindowIcon / ProblemsToolWindowIcon,避免改动全局 25 处使用的 WarningIcon - Run 面板标题、空状态与命令面板统一使用 RunToolWindowIcon,移除自绘 RunIcon - generate-idea-icons.ts 支持映射项 destination 字段,用于终端图标这类 位于 platform/icons/src 之外(Jewel showcase 资源)的素材 - 更新 icons.test.tsx 的导出数量与双色渲染断言 * test(windows): svg-editor 测试结束后还原 locale-provider mock bun 的 mock.module 在同一进程内会泄漏到后续测试文件。svg-editor.test.tsx 把 @/i18n/locale-provider 替换成只含 useTranslation 的桩且不还原,导致全量运行时 Commit 面板等依赖真实 LocaleProvider 的测试拿到桩而失败。 在安装 happy-dom 之前保存真实模块,并在 afterAll 中还原。buffer.store 的 mock 无法同样处理(导入真实模块会触发 Tauri 窗口 API),保持不变。 * fix(windows): Amend 按实际暂存仓库读取 HEAD,提交说明分隔条从显示高度起算 - Amend 目标由有暂存文件的仓库决定,而不是活动仓库:单仓库参与时读取该仓库的 HEAD, 多仓库同时有暂存文件时禁用 Amend 并给出提示;目标仓库变化时撤销已加载的说明, 并在说明未被修改时恢复原草稿 - 提交说明编辑框最多显示视口的 60%,拖动与方向键调整改为从实际显示高度开始, 渲染与交互共用同一上限常量 - 补充 A 活动 / B 暂存、活动仓库无 HEAD、多仓库禁用、目标变化,以及窗口缩小后 拖动与键盘调整的回归测试 * fix(git): Amend 绑定到明确的仓库与 HEAD,由 Core 拒绝多仓库改写 Core 建立提交计划时会重新读取所有仓库的暂存状态,而界面只基于缓存的状态读取 并展示了某一个仓库的提交说明。若读取之后又有别的仓库被暂存(例如外部终端, 且关闭了自动刷新),一个全局 amend 会让两个仓库共用同一条说明并同时被改写。 - Core: amend 时有暂存更改的仓库最多一个,否则 invalid_request 且不写入 - Core: 新增可选 amendTarget{repositoryId, expectedHead},拒绝未知目标、 不一致的暂存仓库以及 HEAD 已移动(新提交或切分支)的请求;重试时目标 已提交则跳过 HEAD 检查 - Core: 约束只在 prepare 校验,不写入 Plan,避免 macOS 的计划模型丢弃未知 字段后 amend 悄悄变成普通提交;validate_session 兜底拒绝涉及多个暂存仓库的 amend 续接。缺省 amendTarget 时保持单仓库下限,macOS 无需改动 - Windows: getHeadCommitMessage 同时返回 HEAD hash;面板记录读取说明的仓库与 hash,提交与重试时转换为仓库标识发送,无法确认时提示重新加载 - 补充 Rust 单元与真实 Git 集成回归、前端面板与 API 测试 - 更新 rust-core-api 契约与工作区提交 Agent Note * fix(git): 重试沿用计划记录的 Amend 仓库,父仓库不再被误改写 子仓库 Amend 成功而父仓库提交被钩子拒绝后,父仓库已暂存了新的子模块引用。 重试时 Core 根据当前暂存区重新推断 Amend 目标,会把它当作唯一的暂存仓库并 改写其原 HEAD,用户确认的“新增父引用提交”变成了历史改写。 - Core: Plan 新增 amendRepositoryId,首次建立计划时确定;step 只 Amend 该 仓库,其余仓库(含只记录子模块引用的父仓库)做普通提交 - Core: 重试沿用 previous.plan 记录的仓库,指定不同 amendTarget 时拒绝; validate_session 拒绝指向未知仓库或非 Amend 计划的续接 - Core: 字段缺失(丢弃未知字段的客户端)时退回按暂存路径判断并限制为一个仓库, 行为与此前一致 - Windows: 重试不再根据组件 ref 推断目标,直接交还上一次计划 - 补充真实 Git 回归(子 Amend 成功、父钩子失败、恢复后重试)及单元、前端测试 - 更新 rust-core-api 契约与工作区提交 Agent Note * revert(windows): 移除 Commit 面板的 Amend 功能 Amend 在多仓库工作区里需要把“每个仓库的提交意图”做成 Swift 与 Rust 共享的 模型,当前 PR 内无法在 macOS 缺少构建验证的情况下安全完成,且反复引入回归。 本次先移除,Windows 提交面板恢复为只做普通提交。 - Windows: 删除 Amend 复选框、HEAD 说明读取、目标仓库推断、重试目标处理, 以及确认框的 Amend Commit 文案和相关 i18n 键 - Core: workspace_commit 及其测试、共享契约与 Agent Note 还原为与 preview 一致, macOS 的 amend 布尔字段保持不变,Swift 无需改动 - 测试: 删除全部 Amend 用例,新增“提交始终为普通提交且不显示 Amend”断言 - 矩阵: 去掉功能说明中的 Amend 描述并重新生成 - 保留与 Amend 无关的修复:提交说明分隔条按实际显示高度调整、图标替换、 svg-editor 测试的 locale mock 还原 * feat(agent): 显示 Agent 思考、计划与斜杠命令补全 (#1016) * feat(agent): 显示 Agent 思考、计划与斜杠命令补全 共享 Host 原本就透传 ACP SessionUpdate,但 macOS 只处理消息、工具、 标题、配置和用量,其余更新被丢弃。本次补齐四类标准更新: - agent_thought_chunk:独立的可折叠思考块,流式输出时展开、回复或 工具到达后收起;缓冲按角色切分,思考与回复不再混在一起;导出 Markdown 以引用块保留,历史消息数不计思考 - plan:计划栏固定在活动统计栏上方,显示进度与当前步骤;每次整体 替换,空计划清除 - available_commands_update:输入 / 弹出 Agent 上报的命令补全,前缀 优先;Codex 的 $Skill 补全为 $name(codex-acp 不解析 /$name), 输入 $ 只列 Skill;macOS 14+ 支持方向键与 Tab - current_mode_update:Agent 自行切换模式时同步权限模式选择器 另外把模型层的兜底文案、stopReason 提示和工具证据标题改为本地化, 补齐 zh-Hans 翻译。 fixture 新增四类事件样例并加入 Rust 往返测试;更新功能矩阵与 shared-acp Agent Note。真实 Agent 已在协议层确认 Codex/Claude 会上报 命令列表、Claude 会上报思考,界面层验证仍待完成。 * fix(agent): 修复命令补全布局和思考搜索展开 为输入区保留可编辑行,空间不足时在对话布局中显示可点击的命令浮层。搜索临时展开匹配思考块,清空后恢复手动选择。补充三项回归测试并同步功能矩阵与架构笔记。 * fix(ci): release Windows Bun install process trees before retry --------- Co-authored-by: Yao Jingxi <23722032@bjtu.edu.cn> --------- Co-authored-by: muchen <145200703+Mucheen@users.noreply.github.com> Co-authored-by: Yao Jingxi <23722032@bjtu.edu.cn> Co-authored-by: lick <2188718831@qq.com> * refactor(matrix): split capability records and generate views in CI (#1026) --------- Co-authored-by: muchen <145200703+Mucheen@users.noreply.github.com> Co-authored-by: sinth <2235390062@qq.com> Co-authored-by: Yao Jingxi <23722032@bjtu.edu.cn> Co-authored-by: 若水 <frankw.wang@qq.com>

Closes #987
问题
macOS 项目树只能对单个项目操作,没有多选状态和范围选择锚点,无法对一组选中项批量拷贝、创建副本或移到废纸篓。
定位
按 issue 中维护者最新的评论,在上游
preview上确认:修复
选择
ProjectTreeModifiedClick覆盖层,只在 ⌘ / Shift 左键按下时命中。SwiftUI Button 的动作在鼠标抬起后才执行,那时NSApp.currentEvent已不是这次点击,读不到修饰键。剪贴板与快捷键
批量文件操作
文案与构建
SDKROOT,与宿主构建保持一致,修复本机安装多个 SDK 时插件选到不兼容 SDK 的问题。审查意见处理
preview,保留双方的 JSON 内容后重新生成 Markdown / CSV,共 121 项能力。验证
preview最新提交afb7a766。scripts/build-macos.sh --configuration debug --triple arm64-apple-macosx和scripts/preview.sh构建通过。./.agents/skills/write-stable-tests/scripts/test-stability-macos.sh -- --disable-xctest --filter 'LitheTests':1345 个测试全部通过,并生成每个测试的计时报告。新增和修改的测试包括:NSWindow.sendEvent和真实命中测试分发,覆盖普通点击建立锚点、Shift 正反向范围、⌘ 增减选择、修饰点击不触发 Button(不打开文件、不折叠目录)、Control 点击穿透。ProjectTreeBatchFileWorkflowTests:使用真实的 AppModel、服务容器、Rust 工作区扫描和磁盘文件。./scripts/verify-runtime-bundle-immutability.sh./.agents/skills/write-stable-tests/scripts/verify-test-stability.sh./scripts/verify-shared-contracts.sh./scripts/verify-module-boundaries.sh./scripts/verify-platform-feature-matrix-change.sh origin/preview HEAD./scripts/verify-platform-feature-matrix.shgit diff --check./scripts/verify-service-boundaries.sh未通过:被上游已有的AppModel+RunConfiguration.swift(616 行,超过 600 行上限)阻断。该文件与上游一致,本 PR 未修改。