文章

Org mode:一个纯文本文件,凭什么管住了我的笔记、待办和代码实验

先看一个你可能熟悉的场景:待办事项在滴答清单里,读书笔记在 Notion 里,代码实验的草稿散落在各个 IDE 的临时文件里,每周写周报时打开五个窗口来回切。然后某天某个 app 改版了、收费了、停止维护了——你的数据跟着陪葬,或者沦落成一份再也不会打开的导出文件。

现在有一个反直觉的事实:一个诞生于 1986 年的文本编辑器里附带的一个模式,至今仍是无数程序员和研究者的笔记中心、任务管理器和”第二大脑”。它没有数据库,没有云同步,没有漂亮的前端——文件本体就是纯文本,你用记事本都能打开它。它叫 Org mode。

更反直觉的是:在 Org 文件里,”待办事项”只是普通文字里的一个词。一行 * TODO 学完 Org mode,TODO 就是三个字母,没有任何隐藏标记。但当你把光标移到这行、按下 C-c C-t,这个词会变成 DONE,时间戳自动补上,而且这件事会同时出现在你另外三个文件的”周视图”里。一行平平无奇的文本,怎么长出了这种行为?这篇文章就沿着”先用起来 → 看懂它为什么这样工作 → 想透这个设计”三层往上爬。

L1:先让它跑起来——Org 的三个最小可用法宝

你在第一层。目标:装好 Emacs,建第一个 .org 文件,十五分钟内体验到大纲、待办、代码块这三样东西的威力。不用写一行配置——Org 是 Emacs 自带的。

装好 Emacs,C-x C-f demo.org RET(按 Ctrl+x 再 Ctrl+f,输入文件名回车),就进入了 Org mode。然后试试下面这个文件,建议真的动手敲一遍,后面有预测环节:

1
2
3
4
5
6
7
* TODO 学完 Org mode
** DONE 安装 Emacs
** L1 大纲与折叠
** L2 待办与日程
* TODO 写周报
* 学习笔记
这里放随便什么正文,普通文本即可。

第一个法宝:大纲折叠。 每行开头的星号数量表示层级,一个 * 是一级,两个 ** 是二级。光标放在任意标题上按 TAB,这一节折叠/展开;按 S-TAB(Shift+TAB),整个文件在”全部展开 / 只显示一级标题 / 什么都不显示”之间循环。你得到的是一棵可以开合的目录树——写长文档时,一屏只看当前关心的那一节。M-S-RET(Alt+Shift+回车)在当前标题下插入一个同级新标题,M-RET 插入同级列表项,不用手动敲星号。

第二个法宝:待办。 光标放在 * TODO 学完 Org mode 那行,按 C-c C-t——TODO 变 DONE,再按变回普通标题。S-← / S-→ 也能切换状态。在 DONE 状态下关闭记录(需要一行配置,见文末速查表),Org 会自动附上时间戳:

1
2
* DONE 学完 Org mode
CLOSED: [2026-09-02 Tue 21:14] 

注意这个时间戳是 Org 写进去的普通文本,不是元数据——这一点先按下不表,第二层会兑现。

第三个法宝:代码块(Babel)。 在文件里敲 <s 然后 TAB,Emacs 自动展开成一个空的代码块骨架(这是 Org 自带的快捷输入)。填上语言和代码:

1
2
3
#+begin_src python
print(sum(range(10)))
#+end_src

光标放进代码块,按 C-c C-c,块下面会插入结果:

1
2
#+RESULTS:
: 45

这就是”文学编程”的入口:代码、说明文字、运行结果活在同一个文件里,像一本可以直接执行的笔记本。Jupyter 用户看到这里应该已经认出亲戚了——Jupyter 的前人正是这个思路。

到这里,你已经能拿 Org 当大纲编辑器 + 待办清单 + 可执行笔记本了。但用着用着你会撞见一些怪现象——

怪现象一:你不用快捷键,手动把 TODO 四个字母改成 DONE(就是普通打字),再按 C-c C-t,它居然正常工作,好像什么都没发生过。 怪现象二:你的待办分散在五个不同的 .org 文件里,按一个键,它们被汇总成一张按日期排好的周视图——没有任何”数据库”参与其中。

如果 TODO 是一种语法关键字,手改应该出问题;如果没有数据库,跨文件汇总从哪儿查出来的?该去第二层了。

L2:懂原理——文件里没有魔法,魔法全在编辑器这侧

你在第二层。目标:解释上面的怪现象,顺便搞清折叠、Agenda、代码块各自的底层机制。

两个怪现象其实是同一个答案:Org 文件里只有数据,没有行为;所有的”功能”都是 Emacs 在读取文件时对文本的即时解释。

为什么手改 TODO 不出问题? 因为 TODO、DONE 根本不是保留字——它们只是一个约定集合里的字符串。Org 逐行读文件时做的是正则匹配:”行首有星号 → 这是标题;标题里出现了集合 #{TODO, DONE} 中的词 → 这是个待办,状态就是这个词”。你手敲 DONE 和快捷键敲 DONE,落到文件里的字节一模一样,Org 没有立场区分它们。快捷键只是帮你打字的”高级输入法”外加顺手补个时间戳。

这个设计还有个隐藏福利:状态集合是你自己定义的。在你的配置里写一行:

1
(setq org-todo-keywords '((sequence "TODO(t)" "等待(w)" "|" "DONE(d)" "放弃(c)")))

竖线左边是未完成态,右边是终态。从此你的待办可以是 * 等待 老板回邮件——中文语境下比 TODO 顺手得多。语法关键字是不允许用户改的,字符串约定随便改。

折叠是怎么实现的? 答案会让人有点失落:根本没有实现——文件里不存在任何”折叠状态”这个信息。Emacs 打开文件时扫一遍星号数量,在内存里的每行前面根据层级计算出”折叠属性”,渲染时把深层内容临时藏起来。你按 TAB 折叠一节,磁盘上的文件一个字节都没变。可以理解为:Org 文件是一张写死的地图,而 Emacs 是一个拿着地图的导游,”这里暂时不看”是导游的口头禅,不是地图上的标注。

跨文件汇总是什么? Agenda 本质上是一次针对所有 .org 文件的结构化 grep。Emacs 按同样的正则规则把每个文件的标题、待办、时间戳扫出来,在内存里建一张临时表,再按你的视图条件(本周 / 所有未完成 / 某个标签)过滤排序,渲染成一个临时 buffer。你 quit 掉这个视图,表就没了——下次再按 M-x org-agenda,重新扫一遍。没有索引文件、没有缓存、没有守护进程,笨,但永远不可能和文件内容不一致。

代码块呢? Babel 的机制直白得可爱:C-c C-c 时,Emacs 把块里的文本原样发给对应语言的解释器进程(Python 就开个 Python 进程,bash 就开个 shell),拿到 stdout,把结果作为文本插回 buffer,用 #+RESULTS: 标注。就这么一次”把文本递给子进程、把输出贴回来”的往返。但因为 Org 的块可以互相引用——

1
2
3
4
5
6
7
8
#+name: data
#+begin_src python
return [x * x for x in range(5)]
#+end_src

#+begin_src python :var nums=data
print(sum(nums))
#+end_src

——第一个块的输出成了第二个块的变量,这个笨机制串起来,就构成了可复现的数据分析流水线:改一处数据,C-c C-v C-b 依次重跑全部代码块,整篇”文档”重新生成。论文作者的良心工具。

到这里机制都通了。但一个更根本的问题悬着:为什么要设计成”文件里什么都没有”? 把状态存成隐藏标记(比如 Notion 的 JSON、Word 的 XML)明明更高效——不用每次打开都全文扫描,折叠状态也能持久化。Org 的作者们又不是没能力做。这层窗户纸,第三层捅破。

L3:想得透——”傻”文件是最聪明的决定

你在第三层。目标:看懂这个设计背后被否决掉的替代方案,以及它们的失败场景。

Org 的本质,一句话:一份用纯文本写成的”数据协议”——文件只负责陈述事实,所有智能都是读文件的一方临场发挥。

去掉术语再讲一遍:Org 文件像一张手写的便条,上面写了什么就是什么;Emacs 像一个特别会看便条的助理,”这行有个 TODO,我给你标出来、给你排进日程”。换一个助理(比如 org-rs、Vim 的 org 插件、甚至你自己写的脚本),便条一个字不用改,照样能看懂——因为格式就是”几个星号、几个约定词”这么朴素的约定。

现在反问:为什么不用更”现代”的方案? 三个显然的替代方案,各自死在哪里:

方案一:隐藏标记 + 富格式(Word / Notion 路线)。 折叠状态、待办状态、视图配置都存进文件,下次打开秒恢复,还支持富文本。失败场景:文件离开它的原生 app 就是一堆乱码。二十年前 Word 的 .doc 文档今天要用专门软件才能打开,而 1996 年的 Org 风格纯文本今天在手机上都能看。工具的寿命普遍以年计,纯文本的寿命以十年计——你的笔记大概率要活得比任何一个 app 都久。

方案二:数据库(Notion / 滴答清单路线)。 真正的索引,查询不用全文扫描,天然支持多人协作和云同步。失败场景有二:其一,数据在别人的服务器上,格式是别人定的——停止维护、改版收费、导出阉割,任一发生你的”第二大脑”就脑死亡;其二,数据库没法进 git。而纯文本 + git 意味着你拥有每一次修改的历史、可以在任何机器上无缝同步、能用 grep 在一秒钟内搜遍十年笔记——grep -r "TODO" ~/notes/ 就是一个免费的、永不过期的全局 Agenda。

方案三:更激进的纯文本双链(Roam / Obsidian 路线)。 反向链接、块引用,看起来比 Org “先进”。失败场景:双向链接的信息其实散落在数据库/缓存里才高效,Obsidian 的 .md 文件本身承载不了反向索引,一旦离开它的索引机制,链接网络就退化成一堆普通锚文本。Org 选择的”每次全文扫描”看起来笨,换来的是没有任何一层结构活在文件之外——文件即全部真相,性能靠文件小、机器快硬扛。这在 2003 年 Org 诞生时是个赌注,在 2026 年的 SSD 面前已经基本不成立成本。

顺带一提历史:Org mode 的作者 Carsten Dominik 是一位天体物理学家,2003 年写它纯粹为了管理自己的科研笔记、TODO 和时间安排,第一版只有几千行。二十年过去它长成了 Emacs 生态的脊柱,但 DNA 没变过——为一个具体的、自己的真实工作流服务。这也是它所有设计取舍的坐标系:作者是自己的第一个用户,而不是产品经理的第一个用户。

动手:三个小实验验证你懂了

实验一(预测环节)。 建一个新文件,敲下:

1
2
* TODO 买牛奶
* done 交周报

先别按任何快捷键,预测:第二行会被 Org 认成待办吗?Agenda 里它会出现吗?

想好了再看答案:不会。 done 是小写——约定集合里没有这个词,所以 Org 眼里这就是一个普通标题。这直接验证了第二层的结论:没有隐藏标记救它,文件里写的是什么就是什么。而如果你把第一行的 TODO 改成 TODOO(多打一个 O),它也会立刻”退化”成普通标题——所谓待办属性,从头到尾就是三个字符的匹配游戏。

实验二(10 行以内)。 在任意 .org 文件里按 C-c / t(sparse tree,稀疏树),整个文件瞬间只剩含 TODO 的标题,其余全部折叠。再按一次 C-c / c 恢复。这就是 Agenda 的单文件版——没建任何索引,纯靠”扫一遍 + 临时渲染”。试试在一个有几百行的笔记文件里做这个操作,感受一下”笨办法”在现代硬件上的实际速度。

实验三(进阶,可选)。 把 L2 那个 :var nums=data 的双块示例跑通,然后把第一个块里的 range(5) 改成 range(10),两个块依次 C-c C-c,看第二个块的结果跟着变。恭喜,你刚刚手工执行了一次”可复现研究”的最小闭环。

收尾自测(能答出来说明真懂了,答案都在文中):

  1. 用记事本(完全脱离 Emacs)把一行 * TODO x 的 TODO 删掉存盘,再用 Emacs 打开,这行会经历什么?(提示:第三层)
  2. Agenda 视图按 q 退出后,它的”数据”去哪儿了?(提示:第二层)
  3. 为什么 Org 宁可每次全文扫描也不用索引文件?(提示:想想”文件即全部真相”的反面是什么)

收尾地图:什么时候用什么,以及下一站

场景决策表:

你的需求用 Org 的什么入口
写长文档/技术笔记,想折叠着看大纲 + TAB星号层级
管理个人待办TODO + sparse treeC-c C-t / C-c / t
多文件日程汇总AgendaM-x org-agenda a
可执行笔记 / 数据分析Babel 代码块<s + TAB, C-c C-c
表格(比如对比表)Org 表格C-c \|(竖线),TAB 换格
导出发布ox(导出器)C-c C-e

最小可抄的入门配置(放 ~/.emacs.d/init.el,五分钟能跑通本文全部功能):

1
2
3
4
5
(global-set-key (kbd "C-c l") 'org-store-link)
(global-set-key (kbd "C-c a") 'org-agenda)
(setq org-todo-keywords '((sequence "TODO(t)" "等待(w)" "|" "DONE(d)" "放弃(c)")))
(setq org-log-done 'time)          ; 完成时自动打时间戳
(setq org-agenda-files '("~/org/")) ; Agenda 扫描这个目录

常用键速查:

键作用
TAB / S-TAB折叠/展开当前节 / 循环全局折叠级别
C-c C-t切换 TODO 状态
M-S-RET新建同级标题
C-c C-c在代码块上:执行它
C-c / t稀疏树:只显示 TODO
C-c C-e导出(HTML / Markdown / PDF…)

下一站。 学会 Org 之后再看这些东西会有”原来如此”的连锁反应:org-roam(Zettelkasten 双链笔记,Roam 在 Org 上的实现——读完本文第三层你会预测到它的反向链接索引自建在哪一层);org-capture(任何地方一个键把念头收进 inbox,配合 Agenda 组成 GTD 工作流);ox-hugo / org-publish(很多技术博主的博客直接从 Org 发布——本文所在的这个 Jekyll 博客的不少同行,正文其实是在 Org 里写的);以及最宏伟的 literate programming:整个 Emacs 配置写成一个 .org 文件,代码块 C-c C-v C-t 生成全部 init.el——配置即文档,这是 Org 哲学的终极体位。

从一行三个星号的纯文本开始,就到这儿。它不会像现代 app 那样讨好你,但它承诺了一件那些 app 都承诺不了的事:二十年后,你今天的每一个字,都还清清楚楚地躺在那儿。

本文由作者按照 CC BY 4.0 进行授权