功能越多,越要小心那一次保存和替换
接入窗口之后,砚陆续有了行编辑、查找替换、跨文件搜索和自动保存。看上去,编辑器已经越来越完整。
但第一次预览发布前,最重要的一轮工作却是把几个数据安全问题找出来:自动保存可能盖掉外部修改,旧搜索结果可能跨工作区返回,单条替换可能操作过期的位置。
这些问题很适合写下来。它们很少出现在演示的最顺路径上,后果却比颜色画错严重得多。
从编辑命令到查找替换
基础编辑逐步加入了缩进、行注释、移动与复制行、智能回车、括号引号自动闭合、排序行、驼峰转换等。它们走共享内核,多光标也要一起工作。
文件内查找支持大小写、全词、正则和替换分组。跨文件搜索支持包含和排除规则,已经打开且未保存的文件也要以缓冲区内容为准,否则用户刚输入的内容会像"搜不到"一样消失。
这些功能共同遇到一个要求:展示给用户的结果,与真正修改时的内容,必须仍然对应。
自动保存先问一句:磁盘还是刚才的版本吗
想象这个过程:
text
砚打开文件 → 我在砚里修改
→ 外部程序也修改同一个文件
→ 砚失去焦点,触发自动保存
如果最后一步只管把缓冲区写出去,外部修改就没了。即使底层用了临时文件和 rename,这次覆盖也会执行得很完整。
现在 Buffer 记录读取或保存时的磁盘标记,用修改时间和大小比对。发现变化时,普通保存与自动保存拒绝覆盖,并让用户选择"覆盖磁盘上的版本"或"还原为磁盘上的版本"。还原也可以撤销。
这不是文件锁或完整的事务系统;当前标记也不是内容哈希。因此文章里更准确的说法是:已经增加外部变化检查和明确冲突处理,不能承诺任何并发写入条件下都绝无覆盖风险。
保存触发点也不能漏。空闲定时器、失焦、切标签、关闭、切文件夹、保存全部、Git 操作前保存,都要沿着同一套保护走。
取消了任务,不等于它一定不会回来
另一个场景是先在项目 A 搜索,马上切到项目 B。A 的后台任务可能已经把结果排进队列,取消信号也撤不回已经发生的事。
砚给工作区加了递增的 epoch。回调带着发起时的身份,执行前确认自己仍属于当前工作区。搜索还要检查查询代数,切目录时旧定时器与任务也被停止。
text
任务返回
→ 工作区仍一致?
→ 查询仍是这一轮?
→ 才更新结果
Git、blame、HEAD 内容读取等后台结果,也需要类似边界。单一所有者保证不会多线程乱写,epoch 则解决"串行地写入了过时内容"。这是两个不同问题。
单条替换尤其容易放松警惕
搜索时某处是 cat,用户后来把它改成同样长度的 dog。旧结果中的偏移仍然合法,如果只检查范围没越界,点击替换就可能把 dog 改掉。
所以替换前还要核对查询与选项、来源版本、原始匹配文字,以及路径是否仍在工作区范围内。内容变了就刷新搜索,不沿着旧坐标继续写。
这里"同样长度"很适合作为负例:它能绕过很多只验证边界的测试。
用反例证明保护确实生效
发布报告记录了针对这些问题的回归测试。例如先扣住 A 的实际搜索结果,切到 B 并开始 B 的搜索,再投递 A 的结果,检查 A 没被改、B 的替换仍然正确。
一些测试还通过临时移除保护验证会失败。这样能确认测试确实命中了那条危险路径,而不是碰巧跑过一个不相关的成功场景。
这轮之后,我对"功能完成"的理解具体了一些。查找替换不只是替换成功,还包括结果过期时拒绝修改;自动保存不只是按时写入,还包括冲突时把失败告诉用户。
这类工作不会让截图多几个按钮,却决定了工具能不能被用来打开真正重要的文件。