计算机科学家 David Chisnall 用 Vim 超过 20 年,写过 5 本书、1 篇博士论文和 150 多篇文章。对他来说,肌肉记忆里最值钱的安全网是持久化撤销:上周删掉的段落、重启系统前的草稿,随时能跨越数月回溯找回。
然而,当他初次尝试作为现代化分支的 Neovim 时,这一沿用 20 年的底层安全网被直接打碎。Neovim 不仅无法读取旧的撤销历史,还将 Vim 原有的持久化撤销文件覆写为 Vim 无法解析的格式,导致两边的历史记录彻底失效。更令他退坑的是社区反馈时的冷淡回应:持久化撤销格式并不稳定,用户“不该依赖名为 persistent 的功能来保存数据”。
这件事很快在开发者社群蔓延开来。表面上是一起兼容性故障,底层却牵扯出基础软件开发中极具破坏性的认知裂痕:当激进重构遭遇传统生产力,开源工具到底对用户负有怎样的注意义务?
版本跃迁背后的格式断裂
很多人误以为这只是一次粗糙的代码删除,但深入技术细节会发现,这是一场由现代架构演化引发的工程事故。
Vim 与 Neovim 的持久化撤销机制,本质上是把缓冲区的撤销树和文件哈希序列化写入磁盘。在 Vim 体系中,写入文件的协议版本常年停留在 version 2。而 Neovim 推进到 0.5 版本时,为了深度集成 Tree-sitter 语法树解析与异步插件生态,合并了 PR #13973,强制将 extmark 字节级编辑信息存入撤销文件。
这一改动直接将 Neovim 内部的撤销文件规范推升至 UF_VERSION 3。
两者在底层共用了相同的文件魔数,协议却不再互通。官方文档注明 Neovim 不会自动主动删除 undo 文件,但当用户在共享目录下用 Neovim 写入新格式时,同名文件被覆写;加之文件内容只要发生哈希不匹配就会被软件直接忽略,旧版 Vim 打开后面对 version 3 格式更是直接拒绝加载。对终端创作者来说,现象极其冰冷:积攒多年的撤销链条,在一瞬间全部打不开。
官方 breaking changes 追踪 issue #14090 明确记载,新版本无法读取 PR #13973 之前的撤销文件。官方给出的恢复路径,竟是让用户自行寻找并编译 Neovim 0.4 或 commit bda1292 之前的旧版本,以此导出旧数据。
社区曾有人提交 PR #14978,试图实现一套撤销文件格式迁移转换工具,但在权衡维护成本后,该 PR 最终被关闭。技术演进向前奔跑,旧历史的处理被直接留在原地。
内部缓存还是数字资产
这场冲突的本质,是工具开发者与终端使用者之间心智模型的剧烈错位。
软件一旦将数据持久化到文件系统,就承担了保全该数据的信义义务。
在 Neovim 核心开发者的认知里,撤销文件是一份高度依赖编辑器内部 AST 语法树和缓冲区字节的高级内部缓存。为了给现代化插件生态让路,内部缓存可以推倒重来;拒绝读取校验不符的文件,是为了防止用户误回滚导致当前文件坏损。
但在严肃作者眼中,带有“持久化”标识的文件绝不是阅后即焚的缓存。它是写作者的撤回权、容错率和安全垫,是一项受法律与伦理保护的数字资产。
人机界面先驱 Jef Raskin 在 2000 年的著作《人机界面》(The Humane Interface)中提出了著名的拉斯金定律,第一条便是:计算机不得损害用户的工作,亦不得因不作为而致使用户的工作受到损害。
Neovim 在工程上为了引入 Tree-sitter 没有错,错在技术治理的傲慢与粗糙。如果需要推翻格式,完全可以更换文件扩展名、使用独立的目录命名空间,或者保留迁移工具做版本过渡。直接复用同名机制、覆盖磁盘存储,并在事后告知用户“不该信任该功能”,直接击穿了软件最基础的注意义务。
生产力工具的防线
眼下,撤销文件的死锁仍未彻底解决。Neovim 仓库中 issue #25690 仍在反复讨论是否要引入 :rundo! 这种强制读取指令,以挽救源文件发生外部变动后被编辑器主动拒认的历史树。这表明由严格哈希校验导致的撤销链条失效,至今仍困扰着生产环境中的重度用户。
官方安全指引随后做出了修正:明确要求 Vim 与 Neovim 必须配置各自独立的撤销目录,并强调持久化撤销仅应作为便捷恢复手段,而非可靠的长期归档。
- 建议.混用两款编辑器的写作者,必须在配置文件中严格隔离
undodir路径,避免底层格式覆写造成灾难。
技术的革新往往伴随代价,但代价不该由用户的心血来单向买单。老派 Unix 工具之所以能跨越数十年依然受人尊崇,正是因为它们在极其克制地守护着数据的连续性;而激进的重构一旦轻视了对用户资产的敬畏,走得再快,也会失去立身之本。
