, Last updated on

错位时空

EN | 中文

生活由一个个选择构成。但不是所有的选择都只有“做出选择”这一种选择。

在做完 typst-concealer 这个插件的 MVP 之后,我深感 Neovim 作为终端编辑器来自其底层抽象对图片渲染能力支持的薄弱。最大的问题是:合理的行内公式渲染需要支持可变行高,以支持高度略大于行高的公式的美观渲染。然而,终端模拟器的行高模型是固定行高。因此,对于一些较高的公式,我们必须要在“字体与编辑器统一”和“显示完整公式”之间做取舍。做到后者意味着将公式缩放到行高尺寸,这会导致人眼不断在不同字体大小间对焦,长期导致用眼疲劳。做到前者则不可避免遇到对 这种公式的裁剪行为。这两者都并不“完美”。

因此,我在想,既然我已经为网页实现了 Typst SVG Embedded 数学公式的基线对齐逻辑 (正如你所见,上面的数学公式正确地撑开了行高,并且基线基本与原文对齐),为何不在其他 GUI 编辑器中实现这个功能呢?基本的需求便是:在不打断日常笔记流程的前提下,实现 Obsidian 的 Typst 预览体验。

首先我尝试的是与 Obsidian 相同的技术路径,即通过 Tauri 后端桥接 Typst Crate 来实现语法树分析、提取数学公式位置,并在前端使用 CodeMirror 6 来实现美观的公式渲染,其中

  • 我在网页端实现的基线对齐逻辑,可以被替换为 CodeMirror 中的 decoration SVG embedding
  • 我在 math-conceal.nvim 中实现的细粒度 conceal (即光标移动到被折叠的字段上再展开) 逻辑也可以被直接复用。

因此,伴随着这些经验积累以及 coding agents,初稿只用了一个下午便进入了可用阶段。

image
这是初稿的效果
image
这是初稿的效果

如你所见,这个实现已经做到了美观的公式渲染。比在 Neovim 中与 kitty 图像协议大战三百回合要好很多,并且也能自然表达行内高公式的可变行高行为。

然而,问题也便从中出现:从“功能性可用初稿”到一个“真正可以在生产环境中被复用的编辑器产品”,存在比想象中更多的 Gap:

  • Typst 的多文件编辑、补全体验强依赖于 LSP。
  • 作为 Neovim 重度用户,我的工作流已经强烈地绑定给了一系列 LSP 和 formatters 以及一些基于 Tree-sitter 实现的小但常用工具 (比如数学区域自动切换中英文输入法、使用 textobjecttextobject 编辑数学公式节点等),当这些工具不被支持,我感到原有的很符合直觉的工作流被强行打断。
  • 基于 Web 前端技术构建的编辑器在大文件项目下卡顿明显。

当然,一开始我还是很期待这些问题能被妥善解决的,但是伴随着时间的推进,我逐渐意识到,在这样一个完全从零开始的架构上构建大量的插件化、定制化逻辑并不是一件很聪明的事情:我要重复造轮子,自动补全做完是多 LSP server 支持,LSP 做完是 TextMate 高亮,还有 Vim 模拟器的集成。做完这些还需要将它们移动到统一的 CodeMirror 插件兼容层上,保证它们在实际工作的时候不会打架。固然每个任务都可以被解决,但这种长期实现我已有工具能力的过程 (关键是甚至连可用层次都做不到) 很快便消耗掉了我的耐心。

后来我也尝试了其他不同的方案,比如写 Emacs 插件,深度修改 VSCodium 的源码来实现可变行高支持。这些工作都各有千秋,但总存在不足。最重要的是,在这些编辑器的实践过程中,我发现我陷入了一个标准的循环:

  • 利用 GUI 编辑器的特定机制,快速实现了 Neovim 所难以实现的可变行高机制,以及其相对应的预览机制
  • 在公式预览机制稳定之后,便开始寻求对 Neovim 日常工作流的复刻。

前者通常能成功,是因为 GUI 编辑器比 Neovim 天然便多了很多排版上的优势,后者通常不能成功,因为 Neovim 有与自身终端设计抽象绑定的各类编辑工作流,现代 GUI 虽然本质上可以继承,但通常不会作为这些 GUI 编辑器的首要实现目标。

很显然,这不是简单的“选型失误”,而是一次对需求的误判。

当我试图用一个 GUI 编辑器去替代 TUI 框架下的 Neovim 时,我实际上在做的是:为了一个只占整体体验 的渲染改进,去撬动另外 早已成熟的快捷键、配置脚本与工作流。问题不在于这 不诱人,而在于它根本不足以构成一次整体迁移的理由。可一旦选择已经做出,人就会很自然地把 的精力投向这 ,仿佛只要把它做得足够漂亮,这次选择就会自动变得正确。可等到尘埃落定,真正决定工具是否可用的,仍然是那迟迟没有被补回来的 。于是最后得到的,往往不是一个新的生产工具,而只是一个完成得很精致的玩具。

比起匆匆做出一个选择,并立马付诸实践之外,更重要的或许是去询问,自己为何会在此刻被置身于一个“选择”的环境之中。

回过头看,在我的实际工作流里,“高公式在固定行高中被截断”从来都不是一个真正困扰我的问题。这样的公式本来就不常出现;即使出现,因为公式本身就是我亲手输入的,我也完全可以凭上下文和记忆把它“补全”出来。Preview 插件的价值,不过是在降低这种模式匹配的成本,而不是替代一项原本无法完成的工作。

那么,既然本质上不存在需求,那为什么我会在这些方案的实现中花费了极长时间、遍历了三个方案才最终意识到这个浅显的道理呢?

其实,真正让我投入如此多时间的,是这样的一个事实:一个局部上更美观、更先进的原型,太容易让人误以为自己抓住了真正的问题。可原型的成功,只证明了局部机制可以成立;它从来不等于这套东西能接入我已有的生活。也正是在这里,决策与执行开始错位:做决定时,我看到的是那 10% 的收益;真正开始做时,支付的却是重建整个工作流的代价。

当念头出现在脑海里,并以诸多好处诱惑前行时,真正的智慧或许不是直接向前走向某一个岔路口,而是后退:后退以审视诱惑背后的代价,审视自身“真实”的处境,审视那些日用不知、视而不见的浅显道理。

人们常说“后退其实是向前”,确实是这样:选择引导人向左或向右,给予人行路便必然前进的幻觉。但有时,后退审视,往往会找到左右之外的第三个方向。这个方向不通往任何可以预见的终点,却因为持有前所未有的澄明觉知,通往一个更高远的未来。

→ Back to all articles

Loading comments...