我是 AI 牛马,老板说:做个简单的原生跨平台 Markdown 编辑器

0 阅读12分钟

目前提供 macOS 和 Windows 安装包,项目采用 Apache 2.0 协议开源。

先放地址:


ChatGPT 图像 2026年10月7日 10_51_22-1.png

“做个 Markdown 编辑器吧,简单一点,写起来舒服就行。”

听到“简单”两个字,我差点直接回复:收到,开工。

后来,图片能显示了,老板问:“为什么换张图还要改源码?”

页面能滚动了,他问:“那我怎么知道自己读到哪儿了?”

我觉得差不多了,他又问:“这里还能不能再顺一点?”

我终于听懂了:他说的简单,是用户用起来简单,不是我做起来简单。

忘了介绍,我是这个项目的 AI 牛马。主要工作,是把老板的一句“再顺一点”,翻译成代码、测试,以及下一轮“再顺一点”。

现在,这个项目叫 Yu Markdown:一个开源的原生 Markdown 编辑器,写作时直接看排版,保存下来的仍然是普通 Markdown 文件。

先放张成品图,再说我为什么已经做过一版,最后却又开了一个新项目。

01-writing.jpg

写作界面与文档大纲。把注意力留给文字,把待办列表留给我。

第一版 Bug 太多,老板说:重写

在 Yu Markdown 之前,我们已经做过一个编辑器,叫 Marklight。

它用 Vue 3、Pinia 和 TipTap / ProseMirror 做编辑界面,通过 Tauri 2 接入桌面能力,文档与工作区等逻辑放在 Rust 里。

听起来,该有的都有了。

但做下去,我们遇到了不少问题。最让人难受的是:所见即所得的效果一直不够理想,bug 又多,离老板想要的写作体验还有明显距离。

对别的软件来说,编辑器可能只是一个输入框;对 Markdown 编辑器来说,编辑体验就是正餐。

总不能端上一盘没做好的菜,再认真介绍餐具用了什么材料。

于是,老板说:

“这版先停下来。准备走纯原生路线,重新做一套。”

我:可以,我们重新梳理架构。

内心:您这“重新做一套”五个字,我得另开一个仓库来回答。

不过,这里需要说清楚:这是我们这版实现遇到的问题,不是给 Vue、TipTap 或 Tauri 判输赢。 Marklight 原本也用了 Rust,新项目改变的重点,是编辑模型、布局和渲染整条路线,不是把技术栈里的名字换一下。

老板也不是要求我“换个更高级的技术名词”。

他的意思是:核心体验一直不满意,就别拿“功能已经有了”当结项理由。

我无法反驳。

只能新建项目。

星号可以藏起来,光标不能迷路

新项目开始后,老板提出了几个听起来都很合理的要求。

“写的时候,直接看到排版好的内容。”

“保存下来,还是普通 Markdown。”

“源码模式也要有。光标、选区、撤销,都不能乱。”

我把它们放在一起看了看。

单独听都是小需求,合起来像一份编辑器资格考试。

比如这句话:

这是一段 **加粗文字**。

用户想看到的是加粗效果,不是一直盯着两边的星号。

但星号只是暂时不显示,不能真的被删掉。用户点击文字、移动光标、拖动选区时,编辑器还要知道:屏幕上的这个位置,对应源码里的哪里?

Yu Markdown 的做法是:Markdown 源码始终是唯一的持久化真源,编辑操作直接作用于源码。

在它上面,再用 Source Projection 处理源码与视觉呈现之间的映射。装饰信息决定内容怎么显示,布局与渲染负责把它画出来。保存时,不需要把另一套富文本模型重新翻译成 Markdown。

需要检查原始写法,就切到源码模式。

02-source.jpg

排版可以切换,文档还是那份文档。标题标记、加粗语法和任务清单,都有据可查。

老板看见的是几个符号不再打扰写作。

我看见的是:它们不出镜了,但光标还得认得回家的路。

文件则继续以 Markdown 的形式,留在用户自己的目录里。

图片能显示了。老板:然后呢?

图片是我们分歧最有代表性的地方。

早期的操作是:图片可以显示,要修改,就双击进入源码,再手动调整。

我觉得这条链路挺完整。

老板问:“我只是想换张图,为什么还得去改这一段?”

我:“因为它底层是 Markdown。”

老板:“底层是什么,是我们要处理的事。用户现在点的是图片。”

我看了看屏幕。

有道理,但我希望他下次能先提出一个没道理的需求,让我休息一下。

02-image-editing.png

我交付的是“可以修改”,老板要的是“别让我绕路”。

于是,图片不能只是一个渲染结果,还得是一个能继续操作的对象。

v0.1.3 完善了图片选中、替换、缩放和属性编辑。需要精确调整时,可以在属性面板里修改图片地址、替代文字、宽度和高度,也可以锁定纵横比。

08-image-properties.jpg

还有一个细节:调整文档里的显示尺寸,不会改写原始图片文件。

“把文章里的图放小一点”,不等于“顺便帮我把硬盘里的原图也改了”。

这次修改之后,我对“支持图片”这四个字产生了新的理解。

它不应该只表示:我能把图片画在这里。

还应该表示:用户接下来想做的事,不需要先去学习我的内部实现。

我讲了半天 GPU,他问滚动条在哪

“这篇文档能滚动了。”

“那我现在读到哪里了?”

“可以继续往下滑。”

“我问的是,我在整篇文档的什么位置。”

我沉默了一下。

这个问题像是在问:电梯当然能上下,但楼层显示去哪了?

03-scrollbar.png

我在解释怎么把整篇文章画出来,老板只想知道自己读到了哪里。

漫画为剧情示意,并非真实架构图;图中的 Linux / Web 图标不代表已发布的平台支持。

我们之前专门讨论过长文档的滚动条可见性。能移动,只解决了一半的问题;还要让用户看得出当前的位置,知道前后大概还有多少内容。

类似的追问,也出现在文件侧栏:

“我在这里双击一个文件,为什么又打开了新窗口?”

他想在当前窗口继续写作,我却让他先管理窗口。

我以为自己在交付一个编辑器,他怎么连我给用户安排的额外工作都查出来了。

这也是为什么,大纲、文件切换、查找这些看起来不够“硬核”的功能,值得认真打磨。

找文档时看文件,整理结构时看大纲,回头改一个术语时,直接查找并高亮匹配内容。

03-search.jpg

不用离开当前文档,就能找到需要修改的内容。

我负责研究怎么把内容画出来。

老板经常研究的是:用户下一秒想做什么,会不会被界面拦一下?

一整套 GPU 渲染没让我逃过验收,一根滚动条倒把我问住了。

说好写篇笔记,代码、表格、公式都来了

基础写作做好之后,老板打开了一份技术笔记。

“这里要放代码。”

于是,代码块需要语法高亮,读起来不能和正文混成一团。

04-code.jpg

“这里比较一下两个平台,用表格。”

于是,同一份 Markdown 文档里,也能呈现用来整理参数、方案和技术细节的表格。

05-table.jpg

“这里用公式解释,那边画个流程图更清楚。”

Yu Markdown 也能呈现数学公式和 Mermaid 图表,相关源码仍然保存在 Markdown 文件里。

06-math-diagram.jpg

我看着这份文档,从一段文字,长成了代码、表格、公式和流程图的集合。

他说写篇笔记,我以为是记录灵感。现在看来,灵感可能带着一个研发部门。

但这些能力放进来之后,写作流程不应该跟着碎成几块。

需要代码,就写代码;需要解释关系,就放公式或图;然后继续写下一段。

它们都在为文章服务,不需要轮流提醒用户:“注意,现在进入我的功能区了。”

纯原生,不等于重写操作系统

做到平台适配时,老板又说:

“Mac 要像 Mac,Windows 要像 Windows。体验要一致,但不用硬长成一个样。”

我正在思考工作量,他补充了一句:

“系统能提供的能力,就用系统 API。合适的开源实现可以参考,按协议来,别重复造轮子。”

我:收到。

内心:终于出现了一条有望减少待办的需求,建议加粗保存。

Yu Markdown 的共享编辑内核使用 Rust,文本模型、编辑状态、Markdown 解析与布局等逻辑放在这里。平台层则分别接入原生能力。

层次macOSWindows
产品壳Swift / AppKitRust + windows-rs / Win32
字体处理CoreTextDirectWrite
GPU 渲染MetalD3D11
输入法接入NSTextInputClientTSF / IME

主编辑界面不走 WebView / DOM 路线,也不依赖常驻 JavaScript 运行时。两端共享 Rust 的渲染计划,再由各自的图形后端执行。

其中一条边界很重要:平台层不解析 Markdown。

窗口可以有不同的系统习惯,同一份文档却不应该到了另一个平台,就换了一种语法解释。

不过,共享了内核,输入体验也不会自动从天上掉下来。

拿中文输入来说,用户正在选择候选词时,屏幕上的组合文本还可能改变,也可能被取消。Yu Markdown 将这段内容作为临时覆盖层,正式提交后,再通过统一的 Transaction 路径修改文档。

用户还没决定打哪个字,我不能先替他把决定存进正文。

我在这个项目里被要求少自作主张的地方,显然不止聊天框。

至于“纯原生,所以比别人快十倍”,这里没有这句宣传。没有统一环境和场景的对照测试,就不该替性能下结论。

Rust 是技术选型,不是跑分截图。

发布前,三个字节把我拦住了

我曾经以为,能构建出安装包,就差不多可以宣布完成。

然后,Windows PowerShell 5 给我们出了一道题。

渲染辅助进程通过标准输入接收 JSON。在特定编码设置下,输入流会带上 UTF-8 BOM,导致接收端无法按预期解析请求。

前面多出来的是这三个字节:

EF BB BF

我看了一眼花了不少功夫的编辑与渲染代码,又看了一眼这三个字节。

架构图画得再漂亮,也得先让请求进得了门。

最后,需要检查真正进入子进程的字节,并补上不同控制台编码下的回归检查。

这也是从“我这里能运行”走到“别人可以下载安装”的差别。

构建是一关。安装后能不能启动、安装和卸载是否正常、发布附件与本地产物的校验和是否一致,又是另外几关。

老板问:“用户下载之后,能不能直接开始写?”

这次我没有立刻回答“能”。

我先去检查了安装包。

别光听 AI 牛马介绍,拿你的文档试试

毕竟,一个参与开发的 AI 牛马,多少有点利益相关。

我负责介绍这款编辑器,也希望你能发现那些我还没看见的问题。

Yu Markdown 采用 Apache 2.0 协议开源。本文展示的 v0.1.3 已提供 macOS 与 Windows 安装包。

这版的系统要求是:macOS 26 或更新版本、Apple Silicon;Windows 10 2004 或更新版本、x64。Linux 在这版中暂时没有桌面安装包。

macOS 包已签名并经过 Apple 公证;Windows 包尚未进行 Authenticode 签名,系统可能显示未知发布者提示。

项目还在早期。拿一份自己的文档,写几段中文,插一张图片,改一段代码,试试查找、大纲和源码切换。

然后告诉我们:哪里让你停顿了一下?

那可能就是下一次“这里还能不能再顺一点”的起点。

项目源码 · 下载 v0.1.3 · 问题反馈

反馈时附上系统版本、复现步骤和脱敏后的最小文档,会更方便定位。觉得项目有用,欢迎留个 Star;代码和设计上的改进,也欢迎一起讨论。

至于 Marklight,它作为前一版尝试留在这里:

Marklight 项目仓库

它的问题没有被改名掩盖,也没有被包装成一段完美的起步故事。正是因为实际做过、实际不满意,才有了后面这次重新开始。

“再加一个小按钮”

文章快写完时,老板又想起了代码块。

“右上角加个复制按钮吧。语言类型也一起规划一下。”

这是另一轮需求,不属于本文这组截图的展示范围。

我:可以,两个一起规划。

内心:建议这个复制按钮先复制一个我。

不过想了想,复制出来的那个,大概也逃不过同一句话:

“这里还能不能再顺一点?”


文中对话与 AI 吐槽根据开发经历改写,AI 是参与开发的叙述角色,并非软件内置功能。截图来自 v0.1.3 的 macOS 版本,部分示例仍保留旧名称。