我是如何使用 ad 编辑器的
2026-02-05
曾经关于编辑器的圣战,vi / emacs 是永恒的话题。后来 vscode 横空出世,暴杀它俩。如果按代码编辑器的市场占有率来看,emacs 现在都快被干到“其它”的归类去了。
我很长很长的时间,都是 emacs 的用户。其实我也一直是 vi 用户,至少一直是 vi 键位的。至于 GUI,则一直不太感冒。其实这背后都是关于“效率”的哲学。
IDE vs editor
如果没有把所有的编辑器,IDE 折腾一遍,是不能够理解这背后的差异的。因为不同的工具形态有不同的效率高的点,可惜新时代的小朋友们太浮燥了,已经没有机会去使用去对比这背后的哲学了。
IDE 效率为什么高?是因为它专注特定的任务领域。比如就负责某一种语言,把这种语言的方方面面都做好。于是对小白用户来说,写某种语言就用某个 IDE,典型的像 Java,特定的场景就效率高。
而文本编辑器呢?早期即没有符号高亮,也没有跳转,也没有那么多插件,一样能写代码。好的地方在于,它可以编辑任何的文件,无论用的 C 语言,Go 语言,lisp 语言,等等等,编辑器都是一样的。
当 vscode 把插件带给大众,把 lsp tree-sitter 等等东西标准化之后,大家终于反应过来,原来 IDE 是笨重的,原来 editor 可以如此强大。
这个理解,只算是把 emacs 原本的强大,发挥了 70%。只是让平庸的使用者,工具的使用效率提升到了几十年前的黑客水平。
GUI vs TUI
TUI 和 GUI 之争的背后,其实是在于鼠标与键盘之争。对于写代码这件事情来说,键盘操作的效率是高于鼠标操作的。频繁在键盘和鼠标之间切换,会影响打字效率。当然,打字效率跟开发效率不能混为一谈。
vi 键位就是打字效率的代表,vim / tmux 等等这一套东西搞下来,打字速度飞起。用习惯了全键盘操作,再切鼠标点点点,就很难受。不过,浏览器之类的一些其它非编程场景,鼠标还是很重要的。
vim 最初的定位,它是一个编辑器。这完全搞错了!直到 neovim 才纠正过来,它是一个 TUI,是一个工具组合构建的开发环境,而不仅仅是编辑器。
emacs 的定位则更高一点,它是一个 OS,是一个 lisp machine,是一个伪装成编辑器的操作系统,只是恰好内置了一个编辑器。理解这一点非常非常重要,不然永远也不明白为什么 emacs 是神一样的存在。
为什么不用 emacs 了
emacs 和 vscode 的差异是,emacs 是一个用户随便折腾,随便按符合自己习惯去配置的。而 vscode 由于有庞大的用户基数,给它写插件的人特别多,所以开箱即用的友好程度要高得多。
emacs 更像是一个人在战斗,每个人的配置都只是让自己使用更方便一点,而 vscode 则是,由于平庸,所以抱团取暖。由于抱团,所以强大。主要是一个人再强大,也只是让自己更方便,但是通过抱团可以让全部使用者享受到这种便利。
主要差异就是插件语言选择,javascript 的用户基数庞大,而 lisp 只有真正黑客才去玩。其实我自己也是不写 elisp 插件的,是只使用别人写好的插件,改改配置这种。
说到底,我自己也不喜欢 elisp。lisp 方言还是有许多种的,我自己有开发自己的 lisp 方言。每个黑客再搞自己的东西,就让分裂的社区更分裂,合作不来。
所以这种结果就是让自己很高效,但是永远是一个人在战斗。而一群人的协作,是强大于一个人的。尤其是站在对新手的“开箱友好”这个角度。这样的优势,更加让 emacs 更加吸引不到新人。 当我感觉到 emacs 是一艘走向沉没的巨轮时,又在寻找其它替代品了。
那为什么不用 nvim 呢?因为 nvim 也会慢慢走向 emacs 的结局。只要伪装成一个操作系统,大家都瞎改,都会越来越 强大 臃肿,不可维护。这是因为它们从哲学底层就错了。
我现在日常使用的编辑器是 ad,在原版的 ad 上面 fork 了一个自己的分支出来,日常使用的就是魔改版本的 ad 编辑器。
"可配置" "可扩展" 和"可组合"
我想说,ad 才是对的。"可扩展"和"可组合" 是两个不同概念。
像 vscode 或者这一类的编辑器为例,它们其实只是"可配置"的。有插件这样的体系,但是大家就是用户,只是去配置一些插件。插件语言对底层的修改能力还是受限制的。都算不上真正的 "可扩展",顶多就叫 "可配置"。
而 emacs 和 nvim 这一类,它们算是 "可扩展"。任意一个提供脚本语言来扩展的编辑器,都是朝这个方向在发展。你想改东西,可以用 lua / elisp 去扩展它。所以是可扩展。
ad 是 acme 思想的继承者,acme 这条路线,是真正的 "可组合"。要扩展它,不是通过某一种脚本语言,去为它写个插件实现一个什么样的功能。它是提供了 9p 这样的协议,将编辑器内部暴露到外部,从而可以用任何的语言来扩展。 这才是可组合,可以用 shell 写,可以用 Go 写,可以用自己熟悉的任意语言,只要能 read 和 write 文本,就能够扩展它。就是通过文本协议,把编辑器的内部暴露给外部去控制。
没有使用过 acme 的人,确实很难理解为什么 acme 才是“可扩展”。
正是因为这种“可扩展”,它不会像 emacs 那样爆炸,想想 emacs 多少万行的 lisp 代码量。而 ad 只有很小巧的内核,以至于像我这么普通的程序员都有可能维护得动了(其实维护归功于AI,我只是 AI 鼓励师)。
Integrated Development Environment
ad 是一个 launcher,就跟交互式 shell 是一个 launcher 类似的定位。
使用 ad 或者 emacs 这类,它们可以消除 90% 的 shell 的使用,这才是它们的定位。这是一种全新的交互方式,所有常用的东西都可以变成一个脚本,然后脚本绑定一个按键,然后在 ad / emacs 里面按键操作来完成任务。
举一个具体的例子。比如说,我要查找当前项目中的某个关键字,在 shell 里面操作,它可能是输入 find . -name '*.go' |xargs grep 'keyword' 这样一条命令,然后按回车,然后在 terminal 里面就会显示有哪些文件,行号是多少。 然后再打开编辑器,找到对应的行,然后开始编辑。这太低效了!
在 IDE 里面,我们会 ctrl + shift + f,弹出来一个搜索框,然后搜索具体的内容,搜索到具体内容之后,我们直接打开就是相应的内容。比手敲 shell 高效得多,但是有不好的地方是,它是一个黑盒,这套流程是固化到代码中的,我们控制不了。
用 vim/emacs 这一类的好处就是,我们可以自己定制一些脚本,把这一整套过程绑定到几个 key 来完成,于是我们 p f 然后弹框输出关键字,如果有 vim 的 quickfix 或者 emacs 的特定子模式,我们就可以直接跳转,全键盘操作。 这些脚本对我们而言是白盒的,这比 IDE 好多了。我们完全可以修改脚本去实现不同的工作流,加个 fuzz search,加个条件过滤,tmux / popup / fzf 等等都整上去,组合出更强大的功能。
如果以 shell 为起点,就会导致我们不停地在 shell 和编辑器之间切换。要编译了,切回来。要改代码了,切过去... 这种切来切去,其实就是没有真正的把 emacs 当操作系统来使用。
曾经在很长一段时间里,我的开发环境就是开三个窗口: 一个 terminal,一个给编辑器,一个给浏览器。然后 alt tab 在三者之间切来切去。在 terminal 里面我用 e 这样的 command alias 绑定到 emacsclient,就可以更方便 terminal 跳转到编辑器。
后来我发现了更方便的使用姿势,是在编辑器里面直接支持 terminal,比如 emacs 里面有 vterm,而 nvim 内置的 terminal。这样就只需要两个窗口了(至于 vscode,别提它,它就是个弟弟,terminal 的功能等于残废的,切进去后就失去原始的按键绑定了)。
直到对 ad 越来越深入的使用,我才发现如果 shell 不是 launcher,ad 才是的时候,我又发现了不一样的世界。
我!不!需!要!REPL
交互式 shell 的工作模式就是 REPL。用户输入,执行程序输出,如此循环。
假设我们都可以在编辑器里面,新开一个 buffer 用于程序的输出,会怎么样?我们不再需要 shell 了!我们可以在编辑器中输入命令,然后直接选中命令,按下某个快捷键,然后再把输出展示到 buffer 上。我们可以脱离终端下的那种 REPL 的工作模式了!
在 ad 里面,这个默认的按键绑定是 @, 只需要选中一段文本,按 @ 按钮,就会把文本当 shell 的命令执行,并把结果显示在另一个叫 +output 的 buffer 中。在 acme 里面,同样的操作是用鼠标中键点击来完成的。它们都是不那么依赖 shell 了,执行文本是全新的操作模式。
这会额外带来的一个好处是,我们可以开一个常驻的文本,把日常使用的命令就写在里面,比如我就写 go test -tags intest,nextgen pkg/executor -run TestXXX 这一长串,选中它,按 @,就开始执行了,结果会在一个 buffer 中。这可比我在终端下敲这样的命令要方便多了。
在 ad 里面,alt+; 就会唤出这个 scratch 文本区,放这些常用命令。在 acme 里面,是在上方状态栏放这些常用命令的。
习惯了之后,通过编辑器来交互,要比通过 shell 交互爽得多。shell 那边的交互,会造成在终端和编辑器之间的切来切去,会需要重复敲一些命令(即使有补全),会造成编辑和修改 shell 命令的时候使用不上 vim 键位。回看历史记录,如果需要用鼠标滑,就是一种打断。而如果是 buffer,vim 按键就可以全键盘操作。
可执行文本
ad 把文本方式交互,发挥到了极致。这也是从 acme 那边学过来的。
比如说有个 http://www.baidu.com 这样的网站的文本,在 ad 里面用 * 就会选中这段文本,按回车,就会自动在浏览器中打开。如果是 文件名.pdf 这样的,选中文本,按回车,就会自动用 pdf 阅读器打开。
这背后就是中 plumber 的工作机制,用户可以自己定义文本的 pattern,然后发送给 plumber 调用不同的方式进行处理。这就像 window 里面,不同的文件后缀名,就对应用不同的应用程序打开一样。
在 ad 或者 acme 里面,文本都可以绑定到某种打开机制,并且是可配置的。比如 mailto:// 开头的用什么打开,甚至是一条长得像 git commit hash 的字符串触发怎么样的 "打开" 机制...用户都可以自己写个脚本定义处理规则。
还是举个例子,在 GUI 里面,我们是用鼠标点点点,然后进入到某个文件夹,先中某个文件,执行什么操作。在 ad 里面我会这样做: f f 按键我绑定了一个弹窗直接输入路径,可以带补全(这里用 z 之类的跳转脚本替代也可以,自己定制),然后就在 ad 中打开了相应路径。 或者是,直接敲 ls path/to/dir ,选中文本,执行,就打开文件夹所在的目录。然后选中文件的文本,打开就是按回车。删除,如果没绑定按键,直接执行命令,把选中的文本当参数传递就行。
coding agent
曾经,我们需要编辑器,是因为我们需要写代码。今天中午我还发了个段子:
写代码是不可能写代码的,这辈子都不可能再手写代码的。调试又不想搞,分析又懒得弄,只能换个prompt再莽一莽,才能维持得了我还是程序员这样子。读代码理解代码?不存在的!AI写的跑不通太正常了,反正老子一行都懒得看,换个说法再让它干就完事,莽过去就是胜利。现在的coding agent啊,个个都是人才,代码写得又快又好,我超喜欢的!
虽然是段子,但如今我们不用写代码了,那编辑器还要么。以前像 cursor 一类的,copilot 模式,即有 AI 的帮助,又有 vscode 本身的能力。现在 claude code 和 codex 都是更好的交互,它们都是 CLI 模式的。
我又想要用这些工具强大的能力,又不想在 ad 和 coding agent 之间切来切去(回到曾经的编辑器和shell间切换的时代),那怎么办?
ad 跟 emacs 是一样可扩展的。是不是可以在编辑里面内置一个 terminal,然后再在内置的 terminal 里面开启 claude 或者 codex。这么做当然是错的!
第一步我们应该把 REPL 模式改掉,变成 可执行文本 + buffer 的模式。我实现的做法是,起一个 coding agent,比如 pi,然后开 rpc 模式,用一个程序桥接,一边是跟 coding agent 的 rpc 通信,另一个是接收 ad 这边的命令发送给 coding agent。
我只要在 ad 里面选中一个文本,然后按绑定的键 a i,就会把内容发送给桥接程序,桥接程序会把文本通过 rpc 传到 coding agent,再收到 coding agent 的回复之后,把内容刷新到 ad 这边的 buffer 中。 于是我就可以不离开 ad 继续利用 coding agent 写代码了。
这不就是一个插件么?对,就是一个插件。只是我以前使用 vscode / emacs 的时候,是使用别人写好的插件。但是现在我可以自己写插件了。咋写的?告诉 AI 我想要的使用姿势,把 pi 的代码和 ad 的代码目录都丢给 AI,让 AI 写的。
- unix 操作系统已经过时,但 unix 的精神永远流传
- plan9 操作系统已经死掉了,但是其实它在很多地方重生
- emacs 会走向消亡,lisp machine 的影响也不会消失
- 手工的代码会被AI代码替代,好的架构还是能经受时间洗礼
- 好的品味永不过时,hacker 精神永存!