选中一批对象,想统一加个日期前缀、或把 .jpeg 批量改成 .jpg——这是很常见的整理操作。Nebula 的批量重命名支持三种规则:加前缀 / 加后缀(扩展名前)/ 查找替换,并且先预览后执行

做这个功能最容易踩的坑是:预览用一套字符串拼接逻辑,真正执行时又写一套。两套逻辑早晚会分叉,于是「预览显示的新名字」和「实际改成的名字」对不上。

一个纯函数,两处复用

我们把「算出改名计划」做成 app-core 里的一个纯函数,预览和执行都调它:

/// 为一批对象路径计算重命名计划。父前缀不变,只改基名;
/// 新基名为空、或结果与原路径相同的项会被跳过(不出现在计划里)。
pub fn plan_batch_rename(paths: &[String], rule: &RenameRule) -> Vec<RenamePlan> { /* … */ }

前端在你调参时实时把它当预览(本地 IPC,基本无延迟),点确定时再拿同一份计划逐个执行。因为是纯函数,它能脱离 GUI 直接 cargo test,规则边界一目了然。

三个设计约定

只改基名,父前缀不动。 重命名只作用于路径的最后一段。如果允许改父前缀,一次「查找替换」就可能把整个目录名换掉、连累所有兄弟对象——那是移动 / 整目录改名该干的事,不该混进「批量重命名对象」。

后缀插在扩展名之前。 a.jpg 加后缀 -v2 应得到 a-v2.jpg 而不是 a.jpg-v2。同时,.gitignore 这种「点开头」的隐藏文件不当作有扩展名,直接追加。

无变化和目录项自动跳过。 查找串不匹配、或结果和原名相同的项,不会进计划;以 / 结尾的目录项也跳过。所以预览里看到的每一行,都是真会改动的。

执行:复用已验证的单个重命名

算出计划后,逐条**复用已有的单对象 rename(copy + delete)**并发执行,失败项单独计数,不影响成功项。没有为批量重新发明一套改名路径——批量只是「把一个可靠操作跑很多遍」。

顺带一提:写测试时我一开始把「替换父前缀里的词」误当成应该生效,结果测试挂了——正好证明了纯函数 + 单元测试的价值:它逼你把「只改基名」这条约定写清楚、并守住。