开篇先聊一个观察:我经常看到有人搜“idea 如何查看项目中的 todo”,说明很多人的任务管理启蒙其实是从Code里那几行TODO注释开始的。但当你在IDE里敲下// TODO: 修复XXX的时候,它只是一个僵死的标记,不会提醒你,不会过期,也不会在你完成它的时候欢呼一声。而Emacs里的org todo,是两个完全不同的物种——它不是“给代码做记号”,而是一整套任务状态机。你把一条待办从TODO推到DOING再推到DONE,每个状态切换都有时间戳,有跳过条件,有循环周期,还能自动汇总进日程。这套东西我折腾了几年,今天把最核心的部分拆开讲清楚。
刚开始接触org todo的人通常会犯一个错误:把它当成一个花哨的复选框列表。实际上,org todo的价值在于它把任务从“静态文本”变成了“动态流程”。下面我按自己从入门到踩坑的过程,把整套玩法展开。
1. 从IDE的TODO注释说起:任务标记和任务管理的本质差别
1.1 为什么IDE的TODO面板救不了你
IDE里查看项目TODO(IDEA里是View | Tool Windows | TODO,或者直接按Alt+6)本质上是正则扫描代码注释,把TODO、FIXME、XXX这些关键字拎出来列个面板。它能告诉你代码里还有哪些坑没填,但仅此而已。
问题在于:它不承担语境。一条TODO (johndoe): 重构此处接口,你根本不知道它是上周写下的还是去年写下的,有没有截止时间,卡在哪个环节,优先级多高。很多时候,项目里的TODO注释是越积越多的,因为没有人对它的状态负责。
在Emacs里,我同样可以用rg或grep在工程里扫一遍TODO注释,但这显然不是org todo的定位。org todo管理的不是代码,而是你的任务本身。它的核心是一套可配置的状态机:一条任务可以从TODO变成DOING,再变成WAITING,或者被取消,最后进入DONE。每一次流转都可以留下时间戳、备注和上下文。
1.2 org todo是一条完整的管理链路
我认为org todo真正让人上瘾的,是它把“记录一件事”到“完成一件事”中间的整个链条打通了:
- 捕获:遇到事情随手记(
org-capture),不打断当前工作流。 - 整理:给任务设置状态、优先级、截止时间、标签。
- 执行:从agenda视图中把今天该干的事情捞出来,一条条解决掉。
- 复盘:状态切换时间戳、日志记录、归档历史,全留着。
也就是说,IDE的TODO是“贴便利贴”,org todo是“项目管理系统”。这两件事在体验上的差距是颠覆性的。如果你只是想在代码里做标记,那直接用// TODO挺好;但如果你想让任务自动排期、自动提醒、自动循环,那我建议你把任务从代码注释里搬出来,放到org文件里管理。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 三分钟搭起第一套todo工作流:配置、快捷键与第一条待办
2.1 最简配置:先让状态转起来
org-mode内置的todo功能已经够用,但默认状态下只有TODO和DONE两个状态,有一点简陋。我建议第一步就自定义状态关键字。编辑配置:
elisp复制(setq org-todo-keywords
'((sequence "TODO(t)" "DOING(i!)" "WAITING(w@)" "|" "DONE(d!)" "CANCELLED(c@)")))
解释一下这一行干了什么:
sequence表示这是一组有序状态,按顺序循环。|把未完成状态和完成状态分隔开。左边是TODO、DOING、WAITING,右边是DONE、CANCELLED。- 括号里的字母是快捷键,在光标停在标题上按
C-c C-t后,输入对应字母就能快速切换状态。 !表示进入该状态时自动记录时间戳。DOING和DONE加了!,意味着你开始做和完成时,Emacs会分别写入一行CLOCK或CLOSED时间戳。@表示切换到这个状态时,光标会跳到标题末尾,方便你输入备注。WAITING和CANCELLED用@很合理,因为通常你要记录“在等什么”或者“为什么取消”。
完成这步配置后,重启Emacs或者M-x eval-buffer,org todo的第一步就通了。
2.2 建一个收件箱文件
我建议在初始阶段就建立一个~/org/gtd.org,专门用来放任务。这比把全Emacs的笔记都塞在一个.org里要好维护。给这个文件加一个标题:
code复制#+TITLE: 任务收件箱
然后在文件里敲一行:
code复制* TODO 给博客补一篇org todo教程
光标停留在这行上面,按C-c C-t,你会看到状态在循环切换。切到DOING后,再按C-c C-t切回TODO,它会问你“是不是真的退回未完成状态”,这是为了防止误操作。把这个任务切到DONE,标题上会多出一个CLOSED: [2025-01-12 Sun 10:24]时间戳——这条任务的完成时间就被记录下来了。
2.3 常用快捷键清单
org todo相关的按键,我整理了自己每天都在用的一张表:
| 按键 | 功能 | 备注 |
|---|---|---|
C-c C-t |
循环切换TODO状态 | 最核心的按键 |
S-LEFT / S-RIGHT |
在状态序列中前后切换 | 适合连续调整 |
C-c / t |
稀疏树:只显示含TODO的标题 | 快速过滤任务 |
C-c C-s |
设置任务计划日期(scheduled) | 决定“什么时候开始” |
C-c C-d |
设置截止日期(deadline) | 决定“什么时候必须完成” |
C-c [ ] |
把当前org文件加入agenda列表 | 让任务进入日程 |
C-c C-a |
打开org-agenda | 汇总所有任务 |
试想一下这个流程:早上打开Emacs,C-c a a调出周视图,今天要做的任务全部列在前面,状态标签、优先级、截止日期一目了然。中午推进一条任务,C-c C-t切到DONE,顺手记录一句“完成”。晚上复盘时翻一下CLOSED时间戳,就能看到今天真正做完了哪些事。这套体验是IDE的TODO面板完全给不了的。
2.4 为什么我强烈建议给状态加上时间戳
!和@的区别,是我使用中觉得最值得读文档的一个细节。!不只是写一个时间戳,它还会在org-log-into-drawer配置生效时,把日志写入LOGBOOK抽屉,避免状态记录污染标题区域。
elisp复制(setq org-log-into-drawer t)
开启之后,你在状态切换时产生的时间戳会全部收进:LOGBOOK:块里,正文区域依然清爽。这个配置建议一开始就加上,不然时间戳会直接堆在标题下,多了以后连自己都嫌乱。
3. 把任务流转做成真正的工作流:状态集合、顺序约束与循环任务
3.1 多套状态集合:区分任务和项目
实际用起来,个人的日常任务和团队项目对状态的要求完全不一样。日常任务有“待办、进行、等待、完成”就够;项目则可能需要“规划、启动、暂停、完成、归档”。org todo允许在一个配置文件里定义多套状态集合,并给每套取一个名字。
elisp复制(setq org-todo-keywords
'((sequence "TODO(t)" "DOING(i!)" "BLOCKED(b@)" "|" "DONE(d!)" "CANCELLED(c@)")
(sequence "PROJ(p)" "ACTIVE(a!)" "HOLD(h@)" "|" "COMPLETE(C!)" "ARCHIVE(A@)")))
然后在不同org文件里用#+TODO:声明使用哪套状态:
code复制#+TODO: TODO(t) DOING(i!) | DONE(d!) CANCELLED(c@)
这样gtd.org用一套个人任务状态,project.org用另一套项目状态。切换时按S-LEFT/S-RIGHT是在当前集合内循环,不会相互干扰。
3.2 给状态切换加上顺序约束
在组织里跑过协同的人都懂,有些状态是不能乱跳的。比如一条任务还没进入DOING,就不应该直接DONE。org todo提供了触发规则,可以在状态切换时自动做条件判断:
elisp复制(setq org-todo-state-tags-triggers
'((done ("nowork"))
("WAITING" ("next" . t))
("BLOCKED" ("next" . t))))
这里的意思是:任务标记为done时自动移除nowork标签;切到WAITING或BLOCKED时自动添加next标签。这样在agenda里可以按标签快速过滤出“现在该做的事”,而状态不合法的任务会直接被规则拦截。
3.3 循环任务的正确打开方式
如果说哪一个功能让我彻底离不开org todo,那就是重复任务。比如每周一写周报,直接写成:
code复制* TODO 写周报
SCHEDULED: <2025-01-13 Mon +1w>
+1w表示每周重复。完成任务后标记DONE,下一次自动变成<2025-01-20 Mon>。这个机制不是简单地复制粘贴,它会根据你的实际完成日期计算下一次的日期,所以周一没做完拖到了周三,下一轮会从周三顺延,而不是死板地落到下周一。
循环任务在多档频率下同样生效:+1d每天、+2d隔天、+1m每月、+1y每年,以及+1w等。更精细的场景,比如“每个月最后一个工作日”,用org-diary的sexp表达式也能实现,不过那属于进阶玩法,初期把握+n语法就够了。
3.4 优先级标记:重要的不是全部,是少数
优先级在org todo里用[#A]、[#B]、[#C]表示。光标在标题上按C-c ,即可设置。默认只有三档,但可以这样扩展为五档:
elisp复制(setq org-priority-highest ?A)
(setq org-priority-lowest ?E)
(setq org-priority-default ?C)
注意,org-priority-default最好保持中间档,否则所有新建任务都会自动带一个优先级,agenda里全是A,反而拖累了判断力。我个人很少用到A档——优先级之所以有用,正是因为它是少数派。如果每天有超过两条A级任务,那很可能不是任务出问题,是规划出问题了。
4. 让todo进入agenda:把清单变成每天的执行计划
4.1 scheduled和deadline,区别比你想的大
很多教程把C-c C-s和C-c C-d混着讲,但语义差得很远:
- scheduled是“计划开始时间”,表示你打算某天开始做这件事。它就像一个缓冲垫,让你在agenda上提前看到“这件事要来了”。
- deadline是“截止时间”,临近时会触发警告,提醒你“再不做就来不及了”。
举一个生活化的例子:你要在下周五之前提交季度汇报。那么scheduled应该设置在周一,因为周一就要动笔;deadline设置在下周五,因为周五必须交。如果你只设置deadline,agenda会在截止前一周就开始预警;如果你只设置scheduled,那它只是一个排期,过完就悄悄溜走,没有紧迫感。
elisp复制(setq org-deadline-warning-days 7)
这个配置表示deadline前7天开始亮黄灯预警。具体天数看个人习惯,我建议至少提前3天,会议性的任务甚至可以提到1天,否则agenda上会常年飘着一堆黄色警告。
4.2 配置agenda,让任务自动汇总
必须先把org文件纳入agenda文件列表,否则任务写在哪都不会出现在日程里。
elisp复制(setq org-agenda-files '("~/org"))
(global-set-key (kbd "C-c a") 'org-agenda)
这两行配置的意思是把~/org目录下所有.org文件作为agenda来源,并把C-c a绑定为打开agenda的快捷键。启动后按C-c a a进入周视图,你会看到带排期和截止时间的任务自动出现在对应日期下面。这比自己翻文件找任务高效得多,因为所有分散在多个org文件里的任务会被聚合成一张时间表。
4.3 定制属于自己的执行视图
默认的agenda视图是“按日期列出所有任务”,但每个人执行习惯不一样。我常年用自定义命令整出两个视图:
elisp复制(global-set-key (kbd "C-c a d") 'org-agenda-day-view) ; 只看今天
(global-set-key (kbd "C-c a w") 'org-agenda-week-view) ; 看一周
更进阶的玩法是用org-agenda-custom-commands定义过滤规则,比如只显示某个标签下的任务:
elisp复制(setq org-agenda-custom-commands
'(("n" "Next Actions"
tags-todo "next/!-DONE"
((org-agenda-overriding-header "下一步行动")
(org-agenda-sorting-strategy
'((agenda habit-down time-up priority-down tag-up)))))))
这个命令的作用是:在agenda里只展示带next标签且未完成的任务,按时间、优先级排序。这样一来,“我今天该干什么”不再需要靠脑子去翻文件,一条命令就筛出来了。
4.4 关于积压任务的个人建议
用agenda的初期,你可能会发现任务越堆越多。这不是org todo的问题,是执行层面的问题。我的做法是:每周五下午花十分钟做一次清点,长期不动的任务要么标CANCELLED,要么把scheduled推到下周。agenda本身只是镜子,但只有经常擦镜子,才能照出真实的进度。
5. 在代码仓库里跟踪TODO:org-capture与项目内清单的配合
5.1 从IDE迁移过来的核心思路
回到开头提到的问题:如何查看项目里的TODO。不同人适合不同方案:
- 如果只是想在代码里标注,那继续用
// TODO或-- TODO,再用rg TODO扫描,完全没问题。 - 如果你想跟踪和管理这些TODO,那最好的做法是在项目根目录建一个
TODO.org,把发现的问题记录成org任务,而不是疯狂地在代码里塞注释。
我自己在项目里维护一个TODO.org,结构类似:
code复制* 待办
** TODO 修复用户登录态过期失效问题
** DOING 重构订单查询接口
* 进行中
* 已完成
这个文件直接放在仓库里,团队其他人用任何编辑器打开都能看到纯文本,不会受限于工具。内容不多时,也方便在Code Review时贴链接引用。
5.2 org-capture:不打断当前的记录方式
在Emacs里写代码时,如果突然接到一个任务,最忌讳的是切换到别的窗口开新文件记录——那会打断所有心流。org-capture就是为此设计的。
elisp复制(setq org-capture-templates
'(("t" "Todo" entry (file+headline "~/org/inbox.org" "Inbox")
"* TODO %?\n %U\n %i\n")
("c" "Code TODO" entry (file+headline "~/org/projects.org" "Coding")
"* TODO %? :code:\n %U\n %a\n")))
第一行模板的语义:按C-c c t,在~/org/inbox.org的Inbox标题下插入一条新任务,任务内容是当前光标处输入的文本,%U自动记录创建时间。
第二行模板有意思的地方在于%a——它会自动记录你当前在Emacs中所在的位置(比如当前打开的文件和行号)。这样你捕获一条任务时保留了完整的上下文链接,回头C-c C-o就能跳回原来正在看的代码位置。
这就是org todo相比IDE注释的一大优势:被捕获的任务自带“锚点”,当你终于有时间处理它时,不会因为找不到上下文而白干一遍。
5.3 和magit配合:在提交前看到遗留TODO
如果你用magit管理Git,强烈推荐安装magit-todos插件。它会在magit status界面下方列出当前改动涉及的文件里还存在的TODO/FIXME注释,等于在提交代码前帮你做一道自查:是不是还有没清掉的标记?这个插件不直接属于org todo,但它把代码注释和任务管理串起来了,很符合我们这行的真实工作流。
6. 折腾多年后留下的配置和几条避坑经验
6.1 我现在实际在用的核心配置
上面讲了很多,最后把真正常年生效的配置收敛一下,供你直接抄。没有炫技,都是实测稳定、不折腾的:
elisp复制(use-package org
:ensure t
:hook (org-mode . visual-line-mode)
:custom
(org-todo-keywords
'((sequence "TODO(t)" "DOING(i!)" "WAITING(w@)" "|" "DONE(d!)" "CANCELLED(c@)"))
'((sequence "PROJ(p)" "ACTIVE(a!)" "HOLD(h@)" "|" "COMPLETE(C!)" "ARCHIVE(A@)")))
(org-log-into-drawer t)
(org-deadline-warning-days 7)
(org-priority-highest ?A)
(org-priority-lowest ?C)
(org-priority-default ?C)
(org-agenda-files '("~/org"))
(org-capture-templates
'(("t" "Todo" entry (file+headline "~/org/inbox.org" "Inbox")
"* TODO %?\n %U\n %i\n"))))
(global-set-key (kbd "C-c a") 'org-agenda)
(global-set-key (kbd "C-c c") 'org-capture)
6.2 踩坑记录一:状态关键字不要中途乱改
状态关键字一旦定义好,尽量保持稳定。org todo的状态时间戳和日志记录都依赖关键字名称。我曾经为了“显得更专业”把DOING改成IN_PROGRESS,结果历史任务的状态记录里新旧关键字混杂,agenda筛选时出现了半天查不到的情况。改配置之前,先想清楚:这次改动对已有数据有没有影响?
6.3 踩坑记录二:@和!带来的弹窗与日志
WAITING、CANCELLED这类带@的状态,切过去时光标会自动跳到标题末尾,等待你输入备注。有些场景下这个行为很贴心,但短任务频繁切换时会觉得烦。如果你在一条任务上快速执行TODO → DOING → DONE,那么DOING和DONE各自写入日志没有问题;而WAITING弹出的备注输入如果打断了节奏,把@去掉即可。这不算bug,只是交互习惯差异,根据场景灵活配。
6.4 踩坑记录三:重复任务一多,agenda会被淹没
+1d这种每日重复任务,一旦建了超过三四个,agenda就会显出原形:每天都有一长串循环任务,真正的临时任务反而被挤得看不见。我目前最多只在agenda里保留两类每日重复任务,其他都改成每周或每周多次的频率,并且配合org-habit插件把规律性用图表展示出来。工具是辅助决策的,不要让它变成心理负担的源头。
6.5 踩坑记录四:org文件会越长越胖,定期归档
任务完成并不代表文件里的内容会自动消失。DONE状态的任务会一直躺在那里,时间久了,org文件会膨胀得厉害,打开和搜索都变慢。解决办法是定期对已完成的任务执行org-archive-subtree(默认快捷键C-c C-x C-a),把旧任务挪到*.org_archive文件里。归档后的任务不在主文件里显示,但需要查历史时依然可以翻出来。注意,归档会丢失一部分跨文件链接,所以重要链接请在归档前手动固化到文档里。
折腾到今天,org todo最打动我的不是某个酷炫的界面,而是它允许我用自己的方式管理任务流转。IDE的TODO面板会告诉你“代码里还有多少坑”,org todo则让我知道“我今天应该集中精力做哪一件事”。这两个问题的层级完全不同。如果你正在被一堆TODO注释和毫无头绪的待办困扰,不妨试一下把任务从代码注释里搬出来,用一个.org文件,两行配置,和一次诚实的清单整理开始。
