“实战Elisp”系列旨在讲述我使用Elisp定制Emacs的经验,抛砖引玉,还请广大Emacs同好不吝赐教——若是真的有广大Emacs用户的话,哈哈哈。
在以前的文章中,我提到本身用org-mode管理工做日午饭和晚餐要看的动画及其进度。简单来讲,就是为每部动画建立同名的二级条目(它们有一个共同的一级条目叫“动画”),每当准备看新一集时,就在对应二级条目下建立形如“观看第X话”的三级条目并设为TODO。html
按这种方式,在《钢炼FA》下最终会建立出64个三级条目——而可怜的Emacs甚至一屏只能显示50行!这些“阅后即焚”的三级条目浪费了其它(或许)更有价值的内容的展现空间,所以最好将每个切换至 DONE
状态的三级条目藏起来。git
org-mode能够“internal archive”一个条目,但这样仍没法节省它们占据的纵向空间。后来,我想到了org-mode的drawer特性。github
基于drawer的方案很直接:在org-after-todo-state-change-hook
中新增一个钩子。每当一个条目切换至DONE
状态、并知足一些条件(好比条目的heading符合“观看第X话”这个模式)时,就将heading的文本与当前时间一块儿追加到父级条目下、名为“观看进度”的drawer的末端。less
org-mode没有内置的函数能够往某个条目的某个drawer中追加内容,须要自行利用“移动光标”和“字符串搜索”来实现。最终的成品以下ide
(require 'cl) (defun lt-org-move-episode-to-drawer () "往上级heading的drawer中插入一个内容,并删除当前heading。" ;; 用cl-block来实现nonlocal-exit (cl-block lt-org-move-episode-to-drawer (let ((state org-state)) (unless (string= state "DONE") (cl-return-from lt-org-move-episode-to-drawer))) (let ((tags (org-get-tags-at)) (text (nth 4 (org-heading-components)))) ;; 只处理内容以“观看”开头、带有“动画”标签的heading (unless (and (or (string-prefix-p "观看" text) (string-prefix-p "继续阅读" text)) (or (member "动画" tags) (member "阅读" tags))) (cl-return-from lt-org-move-episode-to-drawer)) (save-excursion (let (current-position) ;; 记下当前的位置,以后搜索的时候到这里为止 (setq current-position (point)) ;; 往上走一级,以便寻找一个名字叫作“观看进度”的drawer (widen) (outline-up-heading 1) (unless (search-forward ":观看进度:" current-position t) (message "请自行建立“观看进度”的drawer") (cl-return-from lt-org-move-episode-to-drawer)) ;; 继续往前找到:END:的标记 (unless (search-forward ":END:" current-position t) (message "请确保有完整的“观看进度”的drawer") (cl-return-from lt-org-move-episode-to-drawer)) ;; 往左走五步 (backward-char 5) ;; 开辟一行新的,而后把刚刚完成的任务的内容和时间戳放进来 (org-open-line 1) (insert (format "“%s” DONE at %s" text (current-time-string))))) (org-cut-subtree))))
别忘了加入到钩子中函数
(add-hook 'org-after-todo-state-change-hook 'lt-org-move-episode-to-drawer t)
lt-org-move-episode-to-drawer
的缺点在于它会删掉切换至DONE
状态的条目,所以这个观看记录在*Org Agenda*
中会彻底消失——这一点尚能接受。动画
此外,上面的defun
彻底能够修改成cl-defun
,并移除cl-block
。ui
阅读原文spa