我想用 Go 写个编辑器,先把「轻量」这件事想明白
最近开始做一个自己的编辑器,叫「砚」,命令行名字是 yan。
出发点很具体:读代码、翻中文 Markdown、偶尔改几行时,我想要一个打开就能用的小工具。同时也想借这个项目深入学 Go,碰一碰文本存储、并发、文件系统这些平时写前端不太会完整接触的东西。
我依然喜欢 VS Code 的交互。快速打开、命令面板、资源管理器、标签页,这些都很顺手。所以最初的要求其实有点矛盾:操作要熟悉,程序要尽量轻,核心又希望自己用 Go 做。
这个系列就记录砚是怎么一点点长出来的。先从还没开始画窗口的时候说起。
先别急着选 UI 框架
一开始的调研整理出了三个方向:
| 路线 | 吸引我的地方 | 当时要承担的问题 |
|---|---|---|
| Gio 原生窗口 + 自己写内核 | Go 占比高,直接做桌面交互 | 文字排版、中文输入法、自绘代码视图要一起验证 |
| Wails + CodeMirror,Go 做服务 | 能较早得到成熟的编辑和 Markdown 体验 | 两套技术栈,编辑器最核心的部分主要交给前端库 |
| 无头 Go 内核 + 先终端后窗口 | 文本、撤销、搜索能先独立做起来 | 终端中文阅读和快捷键有边界,GUI 仍然要补 |
这些比较是针对砚的目标。当时记录的 hello 程序体积、内存、启动时间,也只是对应环境和负载下的样本。我不准备拿一个终端 hello 和开着大量扩展的 VS Code 做"快了多少倍"的标题。
最终先选了第三条路:把核心做成不依赖界面的 Go 包,终端前端先接上,后面再接窗口。
这样至少能把问题拆开。文本插入错了,去查内核;鼠标点歪了,去查坐标映射;窗口候选框位置不对,再去看平台输入法。否则每次看到一个异常,都不知道是哪一层在出问题。
「轻」得变成能检查的东西
项目最初给终端版定了 6 MB 的体积预算,也给启动、内存和帧耗时写了目标。后面引入 Chroma,高亮的语言覆盖面变大,终端版体积门禁也改到了 10 MiB。这段取舍后面会展开。
这里先记一条经验:预算可以调整,但调整要说清楚换来了什么。
另一个容易混淆的地方是"测量值"和"承诺"。设计里的几十毫秒启动、很低的空闲内存,是要验证的目标;某个旧版本测出来的数据,也不能一直贴在后来不断加功能的版本上。
对一个小项目来说,最有用的性能要求可能是:每次加依赖,都知道二进制为什么长大;遇到卡顿,能说出在等哪个环节。
第一版先画出边界
我想先完成一条最短的使用路径:
text
打开文件 → 找到位置 → 看懂内容 → 改几处 → 保存 → 退出
再扩展成打开项目、查找文件、搜索内容。扩展市场、调试器、账号同步、内建 AI 聊天这些没有一起塞进来。AI 命令行工具后来可以在内嵌终端里运行,但这和编辑器自己实现聊天系统是两件事。
初版路线图分了 M0 到 M8:先去风险验证,再做文本内核、只读查看、工作区、高亮、搜索、Markdown、编辑与加固。原计划后面才接 Gio,最后做 LSP 和 Git。
实际开发没有严格照着这条线走。最早的骨架就已经能编辑和保存;看到 Mayoi 之后,窗口、Git 和不少交互又提前进来了。那些里程碑更像帮助拆解问题的地图,不能据此反推"每一阶段都已验收"。
为什么要留下三份文档
立项时留下了产品设计、架构和 ADR 三类文档。
产品设计回答"用起来是什么样";架构回答"状态放哪、包怎么依赖";ADR 记录"在当时已知条件下为什么这么选"。后面从 Gio 换到 MyGo,从手写高亮换到 Chroma,这些记录就很有用:能知道改的是哪条假设,哪些约束仍然要保留。
当然,文档也会落后于代码。现在回头整理,旧路线里的一些"以后再做",已经在预览版里出现了。开发日志的作用,就是把这些变化接起来,而不是把第一份方案重新包装成最终成果。
到这里,砚还没有漂亮的窗口。但有了一件很关键的东西:接下来写的每一块代码,都知道自己在为哪条使用路径服务。
下一篇从最底下开始:编辑器怎样存一段文字,又怎样把一次输入撤销回去。