1. 为什么我最终把任务管理交给了 org todo
折腾 Emacs 这么久,写到现在第三十四篇,被问最多的一件事就是:你到底拿 Emacs 干什么?写文档、剪视频、记笔记也就罢了,任务管理也塞进 Emacs,是不是有点魔怔?
说实话,我一开始也是这么想的。任务管理软件我试过不少,Things、Todoist、滴答清单,甚至 Notion 搭数据库,都能用,但总觉得哪里不对。直到我用 org todo 记了半年任务之后才想明白——不是那些工具不好,而是它们都要求我"迁就工具的操作习惯",而 org todo 允许我按照自己的思维方式去定义什么叫"做一个任务"。
这篇就聊聊我折腾 org todo 过程中,那些真正让我觉得"值了"的配置和思路。不是给你贴一段 .emacs 就完事,而是把这些设置背后的逻辑说透。毕竟 org todo 这东西,默认配置就能用,但用得好不好,全看你怎么理解它的状态机设计。
要说清楚 org todo,先得说清楚它和普通待办清单的本质区别。绝大多数任务管理工具,任务状态无非是"未完成/已完成"两个状态,顶多加个"进行中"。这对应的是日常场景里"做了/没做"的二元判断。但实际工作中,一个任务往往是"还没开始→正在做→遇到阻塞→重新开工→终于完成"这样一条完整链路。链路上每个节点需要的信息完全不一样——卡住的时候需要记录卡在哪,做完之后需要知道花了多长时间,重复任务需要知道下次该什么时候出现。
org todo 干的其实就是这么一件事:把任务状态变成一套你可以自定义的状态流,然后把状态切换的时机、日志、重复规则全部交给你控制。这句话听起来简单,但实际操作中,它能省掉的重复劳动远超想象。
在展开细节之前,先给没接触过 org todo 的朋友一个最小可用的概念。在 org-mode 里,每个标题行就是一个任务,比如:
org复制* 写季度总结
用 C-c C-t 可以在状态之间循环切换,默认状态下,标题最前面会出现 TODO 或 DONE 字样。这就完成了从"普通标题"到"任务"的转变。而这一切都发生在纯文本文件里,不依赖任何数据库。这既是 org todo 的起点,也是它所有高级玩法的地基。
接下来,我按照自己实际折腾的顺序,从状态流定制、日志记录、重复任务、与 agenda 的联动、常见坑这几个方面,把完整的思路和配置拆开讲。每一部分我都会告诉你"为什么这样做",而不只是"这样做"。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 定制你自己的任务状态流——org todo 的灵魂
2.1 默认状态其实够用,但折腾空间恰恰在这
第一次接触 org todo,默认配置是 TODO 和 DONE 两个状态,加上一个 C-c C-t 快捷键循环切换。如果只是记"今天要洗衣服""给客户回邮件"这类一次性任务,这套默认配置完全够用,没必要多折腾。
但实际用起来你会发现两个问题。第一,当你同时推进多个项目时,光有"已做/未做"根本没法区分哪些事正在阻塞中、哪些事在等待别人反馈。第二,切换状态的时候,你往往需要记录"什么时候切的""为什么切",这种信息在默认配置里是留不下来的。
我举个例子。我有段时间同时处理一个网站改版项目和一个在线课程的录制,两个项目加起来十几个任务。用默认的 TODO/DONE 管理了三天,我就崩溃了:有个任务标了 DONE,但其实是"做完了一版交给设计师去调",后面可能还要改;有个任务一直挂着 TODO,但实际上是"在等客户给参考资料",根本不需要我动手。
所以,状态流定制的核心思路是:把"任务状态"从简单的"做没做",变成"任务现在到底处于什么阶段、离完成还有多远"。
2.2 用 org-todo-keywords 定义多阶段状态流
org todo 的状态定义,核心就一个变量:org-todo-keywords。看名字平平无奇,但它支持的语法比大多数人想象的要灵活得多。
最基本的多状态流配置长这样:
elisp复制(setq org-todo-keywords
'((sequence "TODO(t)" "DOING(i@)" "BLOCKED(b@)" "DONE(d!)")
(sequence "WAITING(w@)" "DELEGATED(e@)" "DONE(d!)")))
这里有几个细节值得展开。
括号里的字母,比如 TODO(t),是 fast access 的快捷键。默认情况下,在任务标题上按 C-c C-t 会弹出一个选择列表,列出所有可选状态。但如果你的状态很多,每次都要看列表再选,效率太低。配置了这个字母之后,可以直接按 C-c C-t t 切到 TODO,按 C-c C-t i 切到 DOING,不用看列表盲操作。这个字母选择是有讲究的:尽量选状态英文名的首字母,且不要重复。比如 TODO 选 t,DOING 选 d,BLOCKED 选 b,WAITING 选 w。注意这里 DOING 选了 d 而不是 i,是因为 DONE 也需要 d——为了避免冲突,DOING 用了 d,DONE 就必须另选。我实际配置里 DONE 用 d,DOING 用 i,这样不会撞车。
状态名后面的 @ 和 ! 是状态切换时是否记录日志的开关。@ 表示切换到该状态时,要记录一个带时间戳的 note,提示你输入说明文字。! 则表示只记录时间戳,不弹输入框。比如 DOING(i@),从别的状态切到 DOING 时会弹出一个小 buffer 让你写"现在开始做这件事",写完回车确认,时间戳和说明都会记录到任务的 LOGBOOK 里。而 DONE(d!) 表示切到完成时只用 ! 记录时间戳,不用额外输入,因为"完成"这件事本身不需要解释,但"何时完成"很重要。
org-todo-keywords 还支持定义多个 sequence。比如我的配置里有两条序列,一条处理正常执行的任务流:TODO→DOING→BLOCKED→DONE;另一条处理需要协作的任务流:WAITING→DELEGATED→DONE。使用多条序列的好处是,不同性质的任务可以有不同的流转路径。比如"等客户回复"这种事,它根本不会经过 DOING,直接用 WAITING 来标记,语义更准确。不过要注意,同一时刻只有一个 sequence 是激活的,默认是第一组。想在第二组之间切换,需要手动制定当前的 sequence,这个后面在进阶部分细说。
2.3 状态流转的触发器和自动化
定义好状态只是第一步。真正让状态流"活"起来的,是 org-todo-state-tags-triggers 这个变量。它允许你指定:当任务切换到某个状态时,自动添加或移除某些 tag。
举个例子。我给任务定义了一个 @work tag 和一个 @home tag。我希望任务进入 DOING 状态时,自动加上当前所在位置的 tag,这样在 org-agenda 里就能按 tag 筛选"现在能做哪些事"。
elisp复制(setq org-todo-state-tags-triggers
'(("DOING" ("@work" . t) ("@home" . t))
("DONE" ("@work" . nil) ("@home" . nil))))
这段配置的意思是:任务进入 DOING 状态时,给任务加上 @work 和 @home 两个 tag;进入 DONE 状态时,移除这两个 tag。实际使用中,这个机制我主要是用来做上下文的自动管理。
另外一个特别实用的触发是"自动切换下一个任务"。比如我有这样一个习惯:每天早上打开 org-agenda,把今天要做的任务按优先级排好。做完第一个标 DONE 的时候,我希望 Emacs 自动提示"要不要开始下一个任务"。这个需求 org todo 原生不提供,但配合 org-after-todo-state-change-hook 可以实现。
elisp复制(defun my/org-todo-next-task-auto-start ()
(when (and (string= org-state "DONE")
(org-agenda-check-type t 'agenda))
(org-agenda-todo "DOING")))
(add-hook 'org-after-todo-state-change-hook #'my/org-todo-next-task-auto-start)
这段代码的思路是:在 org-after-todo-state-change-hook 里检查,如果当前状态变更为了 DONE,且我们在 org-agenda 的视图里,就自动把下一个任务置为 DOING。实际体验就是,在 agenda 里按 t 把任务标完成,光标下的下一个任务自动变成"进行中",非常顺滑。当然,这个逻辑如果你不想自动触发,也可以改成按某个快捷键手动触发,避免误操作。
2.4 状态优先级:让 org-agenda 的排序有意义
定义状态流的时候,还有一个经常被忽略的细节:org-todo-keywords 每种状态其实是有默认优先级的——注意,这里说的不是任务的优先级(priority),而是状态本身的权重。
默认情况下,TODO 状态的优先级高于 DONE。当你配置了多状态流之后,如果你不额外指定,Emacs 会按照状态在 sequence 里出现的顺序,越靠前的优先级越高。这个优先级会影响 org-agenda 里任务的排序:在同一个 deadline 下,TODO 的任务会排在 DOING 前面,DOING 会排在 WAITING 前面。
如果你觉得这个顺序不符合心意,可以在定义状态时用 (sequence "TODO(t)" "DOING(i@)" "BLOCKED(b@)" "DONE(d!)" :priorities "A C B") 这样的额外参数来指定。:priorities 后面的字符串长度要跟状态数量一致,每个字符对应一个状态的优先级,A 最高,C 最低。
我实际配置里给 DOING 设置了最高优先级,因为"正在做"的事应该永远排在"还没开始"的事前面。这个细节看起来不起眼,但当你同时面对二十个任务的时候,agenda 的排序直接就决定了你今天先干哪件事。
3. 状态切换时的日志记录——把"做了什么"变成可追溯的数据
3.1 LOGBOOK 里到底存了什么
配置了 @ 和 ! 之后,org todo 会自动在任务标题下面生成一个 :LOGBOOK: 抽屉,专门存状态切换的记录。用默认折叠视图看,这个抽屉是隐藏的,只有展开标题才能看到。但它在后台默默记录了很多关键信息。
比如我设置了一个任务 TODO 写方案,配了 DOING(i@) 和 DONE(d!)。实际操作是:早上花十分钟切换到 DOING 状态,并输入了"开始写初稿";晚上改完了,切到 DONE。这时候 LOGBOOK 里的内容长这样:
org复制* TODO 写方案
:LOGBOOK:
- State "DOING" from "TODO" [2024-06-11 二 09:15] \\
开始写初稿
- State "DONE" from "DOING" [2024-06-11 二 18:02]
:END:
注意看,DOING 那行末尾有 \\ 和一段文字,这是因为我配置了 @,切换状态时 Emacs 弹出了输入框,我写了一句话。DONE 那行就只有时间戳,因为 ! 只记录时间,不弹输入框。有了这个记录,我回看某个任务的时候,就能知道它什么时候被激活过、中间经历了什么、最终何时完成。
我自己的使用习惯是:每周末花十分钟过一遍这一周所有标 DONE 的任务,看 LOGBOOK 里的时间戳,算一算每个任务实际执行的时间段。刚开始可能觉得没必要,但坚持几周之后,你会对自己的工作效率有个非常直观的认知——"原来我下午三点到五点才是高产期"这种结论,不是靠感觉,是靠数据看出来的。
3.2 状态切换时自动补充 note 的几种方式
@ 弹出的输入框默认只让你输入一行文本,对于大多数场景够用了。但有些时候,一行文字说不清楚——比如你从 DOING 切到 BLOCKED,想详细说明卡在哪个环节,一行字根本不够。
org todo 处理这个问题的办法是:在输入框里输入多行文本。具体操作是,在输入框出现后,直接输入第一行文字,然后按 C-c C-c 提交。如果你想多行,可以在弹出输入框时,直接开始输入第一行,然后每行结尾按 Shift+Enter 换行,最后 C-c C-c 提交。不过说实话,这个交互有点微妙,我第一次用的时候按了 Ctrl+Enter,结果直接提交了,根本没机会写第二行。正确做法是:弹出输入框后,第一行写核心原因,然后按下 M-S-RET 换行继续写,最后 C-c C-c 提交。
如果你觉得 @ 弹出输入框的时机太频繁很烦——毕竟不是每个状态切换都需要写一堆字——可以改用 ! 只记时间戳,等任务整体完成之后,在标题下用 C-c C-z 打开 LOGBOOK,手动添加 note。C-c C-z 会直接跳到当前标题的 LOGBOOK 区域,让你手动补记。这样既能保留时间戳,又不打断工作流。
3.3 用 org-log-into-drawer 让正文区保持干净
LOGBOOK 默认在标题下直接展开显示。如果你的任务标题下面既有 LOGBOOK,又有正文说明,阅读体验会非常差,因为 LOGBOOK 的位置正好夹在标题和正文之间。解决办法是设置全局变量:
elisp复制(setq org-log-into-drawer t)
设置为 t 之后,所有状态切换记录都会自动收进 :LOGBOOK: 抽屉,并且在默认折叠状态下不可见。你按 TAB 展开标题,才会看到抽屉里的内容。这个设置我强烈推荐,因为 org 文件里标题下方的空间是留给正文的,不应该被系统生成的日志打扰。
另一个相关的变量是 org-log-state-notes-insert-after-drawers。它的作用是控制 state note 记录在 LOGBOOK 抽屉里的位置——是放在抽屉开头还是末尾。默认是放在末尾,我个人觉得放在开头更清晰,因为最新的记录应该在最上面。这样设置:
elisp复制(setq org-log-state-notes-insert-after-drawers nil)
nil 表示记录放在 LOGBOOK 抽屉的开头。注意这个变量只在 org-log-into-drawer 开启时才有意义。
3.4 为状态切换补充时钟记录(clock)
除了 LOGBOOK 里的文字日志,org todo 还有一个更硬核的追踪方式:与 org-clock 联动。说白了就是,你可以把一个任务从"开始做"到"做完"的时间段精确记录下来,形成时钟记录。这个功能配合 DOING 状态特别好用。
配置了一个习惯:每次切换到 DOING 状态时,自动启动时钟;切到 DONE 时,自动停止时钟。配置代码是:
elisp复制(defun my/org-todo-clock-in ()
(when (and (string= org-state "DOING")
(not (org-clocking-p)))
(org-clock-in)))
(defun my/org-todo-clock-out ()
(when (and (string= org-state "DONE")
(org-clocking-p))
(org-clock-out)))
(add-hook 'org-after-todo-state-change-hook #'my/org-todo-clock-in)
(add-hook 'org-after-todo-state-change-hook #'my/org-todo-clock-out)
实际效果是,任务切到 DOING 时,mode-line 会出现一个时钟标记,开始计时;切到 DONE 时自动停止。这比手动按 C-c C-x C-i / C-c C-x C-o 方便太多。配合 org-clock-in 的默认行为,每次启动时钟时还会在 LOGBOOK 里记录一行 CLOCK: [2024-06-11 二 09:15]--[2024-06-11 二 18:02] => 8:47 这样的数据,等于同时拿到了"文字记录"和"时间记录"两套数据。
要注意的是,这个自动 clock-in 的 hook 是全局生效的,如果同时打开了多个 org 文件,要小心时钟冲突。我踩过的坑是:在一个 org 文件里把任务 A 切到 DOING,没做完又去另一个 org 文件把任务 B 切到 DOING,结果两个文件各自启动了一个时钟,Emacs 会提示"时钟已经在运行中"。解决办法是使用 org-clock-in 前先检查 org-clocking-p——我在上面代码里已经加了——但更稳妥的办法是,如果你真的需要同时处理两件事,就别用自动 clock,手动控制更安全。
4. 重复任务和周期任务——org todo 最被低估的功能
4.1 在时间戳上做文章:REPEAT 和 SCHEDULED 的区别
很多任务管理器都有"重复任务"功能,但大多很死板:每周一重复、每月一号重复,完事。org todo 的重复任务则和任务状态绑定得很紧密,理解这一点,才算真正掌握周期任务的精髓。
先说基础语法。在一个任务的 SCHEDULED 时间戳后面加一个重复表达式,就能变成重复任务:
org复制* TODO 每周写周报
SCHEDULED: <2024-06-10 一 16:00 +1w>
这个 +1w 就是重复间隔,单位可以是 d(天)、w(周)、m(月)、y(年)。当这个任务被标记为 DONE 时,SCHEDULED 会自动顺延到下一个周期。比如上面的例子,6 月 10 日标记完成,SCHEDULED 会变成 6 月 17 日。
这里有个关键区别:SCHEDULED 和 DEADLINE 的重复语义不一样。SCHEDULED 的重复是"从这个周期开始工作",而 DEADLINE 的重复是"到这个时间点必须完成"。如果你要做的是"每周一发周报",用 SCHEDULED 更合适,因为它的重点是"这周的开始我该干这事了";如果要做的是"每月 30 号交报表",用 DEADLINE 更合适,因为重点是"这一天必须交"。
而当你需要在DEADLINE时间戳上也加重复时,写法是:
org复制* TODO 每月提交报销单
DEADLINE: <2024-06-30 日 +1m>
4.2 不同重复模式:+1w、.+1w 和 ++1w
+1w 只是最简单的一种。org todo 的重复表达式有三种变体,语义差别很大,我折腾了很久才彻底搞明白。
-
+1w:固定周期重复。无论你提前还是延后完成,下一次 SCHEDULED 都按"上次的计划日期"顺延。比如计划 6 月 10 日写周报,实际 6 月 12 日才写完,标记 DONE 后,下次还是 6 月 17 日,不会因为你晚交而顺延。 -
.+1w:从"实际完成日期"开始顺延。比如计划 6 月 10 日,实际 6 月 12 日完成,那么下次就变成 6 月 19 日。这个模式适合那些"做完之后才开始计算下一个周期"的任务,比如每隔两周做一次大扫除,你周六做了,下次就在两周后的周六。 -
++1w:从"任务被打开的那一天"开始顺延。这个模式最不常用,它把任务的启动时间作为计算基准。适用于"只要打开这个任务,就从那一天开始计两周"的场景。
我实际用得最多的是 +1w,因为它最符合周报这类固定节奏的任务。对于健身这种"练完才开始算下一练"的场景,用 .+1w 更合理。至于 ++1w,说实话我至今没找到特别合适的场景,但它确实是语法的一部分,知道总比不知道好。
4.3 重复任务的日志记录:org-todo-repeat-to-state
重复任务有个挺烦人的问题:如果你把"每周写周报"这个任务从 TODO 标成 DONE,它自动顺延到了下周,但此刻它应该是"下周要做的事"。然而 org todo 默认情况下,标记 DONE 后它立刻变成一个新的 TODO,而且这个 TODO 的下一次 SCHEDULED 是下周。如果你此时打开 org-agenda,今天这个任务已经标完成了,不会出现在今天的待办里。这个行为是没问题的。
但这里有个隐藏逻辑:重复任务在标 DONE 之后,会默认变成 TODO 状态。如果你不想让它标完 DONE 之后变成 TODO,而想直接变成"未开始"的感觉,可以设置 org-todo-repeat-to-state 变量。比如:
elisp复制(setq org-todo-repeat-to-state "TODO")
这样重复任务完成后,自动变回 TODO,而不是停留在 DONE 状态。这样做的好处是,在 org-agenda 里,这个任务永远是以 TODO 的身份出现的,不会因为变成 DONE 而混进已完成列表。但你要知道,这个变量设成 "TODO" 时,重复任务的 LOGBOOK 里也会多一条 "State 'TODO' from 'DONE'" 的记录。如果你想让它直接变成某个自定义状态,比如 "DOING",把变量值换掉就行。不过我的实际体验是:重复任务在标 DONE 后,还是保持 TODO 最省心,因为下一个周期到来时,它重新进入待办列表才是最自然的语义。
4.4 另一种思路:org-clone-subtree-with-time-shift 批量生成任务
有人不太喜欢 org repeat 的自动顺延机制,觉得"任务完成之后自动变出下一个"有点飘,更喜欢把每个周期的任务当成一个独立任务来管理。org todo 提供了另一个工具:org-clone-subtree-with-time-shift,默认快捷键是 C-c C-x c。
这个命令的作用是:复制当前 heading 下的一整棵子树(包括子任务、描述、tag 全带上),然后按时间偏移生成多个副本。比如我有一份"月报模板",包含三个子任务,本来只写了一个带 SCHEDULED: <2024-06-01 六> 的父任务。选中它执行 C-c C-x c,输入克隆数量 3,再输入偏移量 +1m,就会生成 6 月和 7 月的两份副本,时间戳依次顺延。
这个功能适合"固定几期但不想用重复机制"的任务。比如一个培训计划有 8 期课程,每期有两个前置任务。如果用 repeat,会一直在那循环;用 clone 一次性生成 8 份独立任务,每份都可以单独调整内容、单独记录进度,互不干扰。
相比 repeat,clone 的好处是每个实例独立,改一个不会影响其他实例;坏处是如果模板本身有改动,已经 clone 出来的不会同步。所以我的建议是:对于"所有周期完全一样"的,用 repeat;对于"每期任务都可能有变动"的,用 clone。这个判断标准,帮我避免了很多不必要的纠结。
5. 状态和时间戳组合拳——TODO 状态机与 SCHEDULED/DEADLINE 的联动
5.1 org-todo-keywords 里的状态标签:一个例子看清全局
上面讲了很多单点配置,这里用一套完整的、我实际在用的配置做个整合演示。我的个人任务管理文件里有两个 sequence,一个管"执行流",一个管"等待流"。每定义一个新状态,我都要仔细权衡它和 SCHEDULED/DEADLINE 的关系。
elisp复制(setq org-todo-keywords
'((sequence "TODO(t)" "DOING(i@)" "BLOCKED(b@)" "DONE(d!)")
(sequence "WAITING(w@)" "DELEGATED(e@)" "SOMEDAY(s)" "DONE(d!)")))
(setq org-todo-state-tags-triggers
'(("WAITING" ("@waiting" . t))
("DELEGATED" ("@delegated" . t))
("DONE" ("@waiting" . nil) ("@delegated" . nil))))
(setq org-todo-repeat-to-state "TODO")
(setq org-log-into-drawer t)
(setq org-log-state-notes-insert-after-drawers nil)
这套配置的核心逻辑是:
- 执行流:TODO → DOING → BLOCKED → DONE,适用于我手头正在推进的任务。
- 等待流:WAITING → DELEGATED → SOMEDAY → DONE,适用于不依赖我、或者要等别人给反馈的任务。
为什么把 WAITING 单独拉出来一条序列?因为在实际工作中,"等待别人"这个状态太常见了,它和"自己还没开始"有本质区别。WAITING 状态的任务不该出现在"今天该做什么"的视图里,但它必须在某个地方存在,以免被遗忘。而 DELEGATED 表示我已经把活派出去了,SOMEDAY 表示这个任务以后可能做,但现在不排期。
5.2 用 SCHEDULED 和 DEADLINE 把状态机变成时间线
状态机本身不管时间。真正让任务在时间线上有位置,靠的是 SCHEDULED 和 DEADLINE 这两个时间戳。我常用的组合方式是:
- 一个有 SCHEDULED 的任务,表示"到了这个时间点,任务应该出现在待办列表里开始处理"。
- 一个有 DEADLINE 的任务,表示"到了这个时间点,如果还没完成,就会在 org-agenda 里显示红色警告"。
- SCHEDULED 和 DEADLINE 都有的任务,是最标准的"开始时间 + 截止时间"范式。
举一个实际的任务例子:
org复制* TODO 完成网站改版方案
SCHEDULED: <2024-06-20 四>
DEADLINE: <2024-06-28 五>
这段时间内,该任务会一直出现在 org-agenda 的日程视图里,从 Scheduled 那天起,它是"今天该开始做的事";到了 Deadline 前三天,agenda 里会开始显示"DEADLINE 临近"的黄色警告;如果超期,会变红色警告。
这个组合非常好用,但有一个坑:如果你设置了带重复的 SCHEDULED 且该任务同时有 DEADLINE,状态切到 DONE 时,两个时间戳的重复行为可能不一致,容易造成 agenda 里出现奇怪的双重记录。我的经验是:重复任务尽量只在一个时间戳上加 repeat——一般加在 DEADLINE 上,因为它更贴近"何时必须完成"的语义。SCHEDULED 就让它保持固定日期,由重复的 DEADLINE 自动推动。
5.3 设置 org-agenda 的 TODO 视图,把状态机展示出来
折腾完状态机,当然要在 org-agenda 里看到效果。我配置了一个自定义的 agenda 视图,把 TODO 状态的任务单独拉出来,按优先级和时间排序。关键配置如下:
elisp复制(setq org-agenda-custom-commands
'(("n" "我的待办"
((agenda "")
(todo "TODO|DOING|BLOCKED"
((org-agenda-overriding-header "当前处理中")
(org-agenda-sorting-strategy '(priority-down category-up))))
(todo "WAITING|DELEGATED"
((org-agenda-overriding-header "等待/委派中")))))))
这个命令的效果是:按 C-c a n,能看到三块内容——今日日程、当前待处理任务(TODO/DOING/BLOCKED)、等待中的任务(WAITING/DELEGATED)。agenda 部分默认显示今天的时间格,todo 部分会列出所有匹配状态的任务,并按照优先级从高到低排序。对于 WAITING 状态的任务,我特意让它们单独成块,这样打开 agenda 不会觉得所有事都堆在一起,视觉压力小很多。
这一步对我来说是成体系的最后一块拼图。状态机让任务有了语义,时间戳让任务有了时间坐标,agenda 视图则把所有任务按状态和时间组织成一个可操作的界面。以后每天早上打开 Emacs 敲 C-c a n,一眼就知道今天该干什么、什么在等、什么被堵住了。
6. 常见问题速查与避坑经验——折腾 org todo 一定会遇到的坑
6.1 状态切换失灵:C-c C-t 没反应
这是新手最容易遇到的问题。按 C-c C-t 没有弹出状态选择列表,或者完全没反应。大概率是当前光标不在一个 org 标题行上,或者 org-mode 没有正确开启。检查方法:确认文件扩展名是 .org,并且 mode-line 上显示 Org。如果显示的是 Text 之类,执行 M-x org-mode 手动开启。还有一种情况是光标在代码块里,org 默认不会在代码块里执行 todo 操作,把光标移出代码块再试。
6.2 LOGBOOK 内容丢失
有段时间我发现自己 LOGBOOK 里的状态记录经常消失,排查了很久,发现原因是我在关闭 Emacs 前没有保存文件。org todo 的日志记录是实时写入 buffer 的,但如果你没有保存文件就退出,自然会丢失。更隐蔽的一个原因是:在 org-log-into-drawer 开启的情况下,如果 org 文件里的 LOGBOOK 抽屉格式被破坏——比如多了一个空行——后续状态记录可能跑到抽屉外面去。我遇到过把 LOGBOOK 抽屉的 :END: 行删掉后,所有日志直接平铺在标题下的情况。解决方法是每次编辑 LOGBOOK 抽屉时小心,不要动 :END: 行。
6.3 重复任务没有自动顺延
这个坑我一开始也踩过。设置 SCHEDULED: <2024-06-10 一 16:00 +1w> 之后,标 DONE 却发现 SCHEDULED 没有顺延。原因是:repeat 的顺延只在"从 TODO 状态直接切到 DONE"时生效。如果你先把任务从 TODO 切成 DOING,再从 DOING 切到 DONE,有些旧版本 Emacs 的 org 包不会触发 repeat 顺延。这个 bug 在较新版本里修掉了,但如果你还在用老版本,就要注意:重复任务直接 C-c C-t d 完成,不要绕弯。
另一个需要注意的点是:如果你用了 org-todo-repeat-to-state,把重复任务设成某个自定义状态而非 TODO,某些版本下 repeat 的计算会被 skip。如果发现不自动顺延,第一步先检查版本,第二步检查这个变量。
6.4 多文件任务管理时,org-agenda 里看不到任务
我折腾 org todo 的时候,任务散落在多个 org 文件里——一个工作项目一个文件,个人杂事一个文件,学习笔记一个文件。结果打开 org-agenda 只能看到部分文件的任务。原因很简单,我没有设置 org-agenda-files:
elisp复制(setq org-agenda-files '("~/notes/work.org" "~/notes/personal.org" "~/notes/study.org"))
把所有任务相关的 org 文件加入这个变量,agenda 才能统一扫描。更省心的做法是:
elisp复制(setq org-agenda-files (directory-files-recursively "~/notes" "\\.org$"))
这样 ~/notes 目录下所有 org 文件都会被包含进来。不过要注意,这个写法会扫描所有子目录,如果你有 org 文件只是用来做笔记、不想出现在 agenda 里,就得换一种方式,或者给不需要的文件加 ARCHIVE tag 或者放在特定子目录中再 exclude 掉。
6.5 状态太多导致切换繁琐
这个问题听起来有点反直觉——前面说了状态越多语义越清晰,但实际操作中,状态太多会让你在切换时反复看列表,反而降低效率。我的建议是:把状态数量控制在 5 个左右,并且每个状态都有自己的快捷键字母。如果你发现某个状态一周都用不上一次,删掉它,别犹豫。我有一段时期配置了一堆状态:TODO、DOING、WAITING、BLOCKED、DONE、CANCELLED、DEFERRED、SOMEDAY,实际用下来 CANCELLED 和 DEFERRED 基本没用过,后来全部删掉。
6.6 重复任务从上周遗留到本周,agenda 里堆了一堆 TODO
这个坑在每周复盘时特别明显。比如每周一写周报,上周一的周报没写,标成 DONE,周一过去没完成;到了这周一,agenda 里会出现两个"写周报",而且都是 TODO。原因在于:repeat 顺延是把旧日期后的 SCHEDULED 变成新的周次,但如果你上周没标 DONE,它依然留在 TODO 状态,继续出现在本周。
解决思路有两个。一是严格习惯:每周复盘时把所有该完成的重复任务标成 DONE,哪怕内容是"本周无进展",也要把它关闭,让 repeat 自动顺延到下一周。二是用 org-agenda-skip-deadline-if-done 等变量过滤掉部分已完成任务,但这不能治本。我更推荐的是保持"到期必勾"的习惯,让 repeat 机制自动工作。
7. 这套状态流还能往哪扩展——org todo 的进阶玩法
如果上面的内容你都消化了,org todo 的基本功算是踏实了。接下来可以试试几个和 org todo 搭配起来特别顺的功能。
7.1 org-habits:把重复任务变成习惯追踪
org todo 的 repeat 机制加上 org-habits 包,可以生成一个习惯追踪视图,显示每个习惯的完成历史和连续天数。org-habits 的配置很简单,在一个重复任务上加上 STYLE: habit 属性就行:
org复制* TODO 每天阅读 30 分钟
SCHEDULED: <2024-06-01 六 .+1d>
:PROPERTIES:
:STYLE: habit
:END:
在 org-agenda 的 agenda 视图里,这个习惯会以图形化的方式显示过去的完成情况:绿色方块代表完成,红色代表漏掉,还有一个百分比显示完成率。如果你坚持记录了几周,再看这个图形,那种成就感不是一般 todo 应用能比的。
7.2 org-pomodoro:和 DOING 状态串起来
我在任务切到 DOING 时配合 org-pomodoro 开始番茄钟。org-pomodoro 是一个独立的包,不算 org todo 核心,但配合起来体验很好。简单说,在任务标题下执行 M-x org-pomodoro,它会启动一个 25 分钟倒计时,结束之后自动在 LOGBOOK 里记一条 pomodoro 记录。这样你的任务不仅有状态切换日志,还有专注时长的记录。
org-pomodoro 的默认设置会要求你有一个 active clock,所以最好和 org-clock 配合使用。具体配置可以直接用包管理安装,唯一要注意的是 org-pomodoro-long-break-sound 这些声音文件,如果你不想被打扰,可以把音效关掉。
7.3 任务归档:org todo 的终点站
任务标 DONE 之后,如果一直留在当前文件里,org 文件会越来越臃肿。org todo 提供归档机制:C-c C-x C-a 可以把当前任务归档到指定的归档文件,或者 C-c C-x a 在 agenda 中批量归档。归档之后,任务会从当前文件移到 xxx.org_archive 文件里,彻底离开日常视图。
我习惯每周日归档一次本周完成的所有任务。归档前先跑一遍 agenda 的"已完成任务"视图,确认没有遗漏,然后批量归档。这样 org 文件常年保持精简,agenda 加载速度也不会随着时间变慢。
8. 关于 org todo 的一些个人体会
文章写到这里,技术上能讲的都讲了。最后聊点个人的真实感受。
我在这一路上,从最开始只用 TODO 和 DONE,到后来配齐了多状态、日志、重复、时钟、归档,最大的收获不是"用 Emacs 管理任务"这个行为本身,而是被迫想清楚了一个问题:我的工作流程到底长什么样?每种任务的不同阶段,我到底需要记录什么、关注什么?这个问题理清楚了,任务管理工具不过是顺手的表达方式。
如果你还在纠结要不要把任务管理搬到 org todo 里,我的建议是:先别急着配一大堆东西,用一个 .org 文件,开两个默认状态,用两周试试。等你在真实的工作节奏里遇到了"这个任务该标成什么""做完一个任务之后下一个怎么办"这类问题,再回来翻这篇博客,配置什么、为什么这么配置,你自然就有了答案。
毕竟折腾的意义,从来都是让自己用得更顺手,而不是把工具打磨成一个完美但没有灵魂的摆设。
