目录
orgmode 虽然是纯文本,但结构清晰、功能丰富,对于记笔记来说,Org-mode 提供了一个近乎完美的解决方案。
用 orgmode 做笔记是一个非常宏大的话题,不同的人有不同的理解,也就带来了不同的用法。但一般来说,都会用到这几个包:bibtex-completion, helm-bibtex, org-ref, org-noter, org-download。
其中,bibtex-completion, helm-bibtex, org-ref 这三个包提供的功能是围绕 bib 文件展开的。bibtex-completion 是其他两个包的基础,主要是读取并分析 bib 文件所包含的条目;helm-bibtex 展示 bibtex-completion 的分析结果,并提供搜索能力;org-ref 则提供在适当位置插入交叉引用的功能。
org-noter 利用了 emacs 读取 pdf 的能力,在 emacs 中阅读 pdf 文档的同时,可以在org 文档中做笔记,并保持 pdf 文档和笔记的同步滚动。但要注意,笔记并不是做在 pdf 文档中的。个人并不喜欢这样的笔记方式,太累了,除非是特别经典的书,需要做大量笔记。
除了上述包,还有一个 org-roam 包。org-roam 是一个方法论工具,其背后的理论是“Zettelkasten 笔记法”,可参考“Emacs 系列——Zettelkasten 笔记法”。
1 Org-mode 记笔记有什么优势?
- org 文档是纯文本。任何机器都能打开,易于版本控制(如 Git),方便备份和同步(如坚果云等)。
- 结构层次清晰。使用星号
*定义标题,天然形成树状结构。 - 功能一体化。笔记不仅支持 LaTex 公式,还支持待办事项、代码执行、链接、导出其他格式。
所以,org 文档记下的不只是“笔记”这么简单,它可以呈现是非常丰富的功能。但纯文本有个极大的劣势,就是不能嵌入图片等多媒体文件,处理起来有诸多问题。
2 捕获 - 快速创建笔记
记录笔记、想法要尽可能地快,否则很容易遗忘。记笔记也不应该操心笔记应该怎么存放,而是先记下来,回头再整理。orgmode 提供的“捕获”(capture)功能完全能够满足这些需求,不过先要配置一下 capture。
在 orgmode 的基础配置 里,我们已经配置了一个 memo.org 文件临时存放各种记录,类似便笺本,不仅是任务、日程安排,随手记录的笔记也存放在那里,所以把它设置为默认的笔记本,如果“捕获”模板里没有指定存放位置,捕获内容就会存放到这个默认位置。
(org-default-notes-file (expand-file-name "memos.org" org-directory))
除此之外,我们还需要添加几个捕获功能,先添加一个函数:
(defun my/create-captured-file (&optional filename)
"If filename is given, create the capture file in org-directory,
with the filename; else, use the title as the filename when putting it
in the posts directory."
(interactive)
(let* ((title (read-string "Title: "))
(file-path (if filename
(expand-file-name
(format "%s.org" (my/slug filename))
org-directory)
(expand-file-name
(format "%s.org" (my/slug title))
(expand-file-name "posts/" org-directory)))))
(setq my-template-title title)
file-path))
这个函数里用到了一个全局自定义变量 my-template-title ,这个变量会把输入的标题名称同时转换为文件名(如果需要)和文档的标题:
(defvar my-template-title ""
"模板的标题,用于填充模板里的 TITLE 关键字。")
用于平时写博文、讲义、幻灯片之类的,每种用途都编写一个捕获模板。
("p" "blog-post" plain
(file (lambda () (my/create-captured-file)))
(file "~/.emacs.d/org-templates/post.orgcaptmpl"))
("b" "Templates for teaching purpose")
("bt" "讲义" plain
(file (lambda () (my/create-captured-file "讲义")))
(file "~/.emacs.d/org-templates/讲义.orgcaptmpl"))
("bb" "beamer" plain
(file (lambda () (my/create-captured-file "课件")))
(file "~/.emacs.d/org-templates/beamer.orgcaptmpl"))
("bi" "教学设计" plain
(file (lambda () (my/create-captured-file "教学设计")))
(file "~/.emacs.d/org-templates/教学设计.orgcaptmpl"))
比如讲义的捕获模板是这个样子:
# -*- org -*-
#+SETUPFILE: ~/.emacs.d/org-templates/讲义.setup
#+INCLUDE: ~/.emacs.d/org-templates/common.special
#+TITLE: %(eval my-template-title)
#+DATE:
#+ESTABLISHED: %<%Y-%m-%d %a %H:%M %Z>
#+DESCRIPTION:
#+KEYWORDS:
#
#
#
%?
模板里的 #+DATE: 行的时间是空的,设置为由 emacs 在文件保存时填入:
(time-stamp-pattern "^#\\+DATE: %Y-%m-%d \%a %H:%M %Z$") ;自动设置时间戳的模板
要让 org 文件在保存时填入时间戳,需要向 before-save-hook 添加 time-stamp 函数:
(before-save . (lambda ()
(when (derived-mode-p 'org-mode)
(time-stamp))))
3 bibtex-completion
bibtex-completion 模块是利用 bibtex 文件进行文献引用管理的基础包,我们基本上不会直接与 bibtex-completion 进行交互,而是通过它的前端(如 helm-bibtex, bib-ref 等)来间接使用它。引入 bibtex-completion 包需要设置 3 个最重要的参数: bibtex-completion-bibliography (bib 文件路径), bibtex-completion-library-path (pdf文档所在目录), bibtex-completion-notes-path (笔记所在目录)。另外, bibtex-completion-display-formats 参数设置各类文献如何在查找界面显示, bibtex-completion-notes-template-multiple-files 和 bibtex-completion-notes-template-one-file 参数设置笔记的模板。除此之外,还有一些其他参数的设置,可参考包自带的文档。
(use-package bibtex-completion ;它是 bibtex 各种操作的基础包
:defer t
:ensure t
:custom
(bibtex-completion-bibliography (directory-files-recursively "~/tex/paper/" "^.*\\.bib$" nil))
(bibtex-completion-library-path (directory-files-recursively "~/tex/paper/" "^literature$" t))
(bibtex-completion-notes-path (expand-file-name "roam/literature/" org-directory))
(bibtex-completion-additional-search-fields '(keywords))
(bibtex-completion-find-additional-pdfs t) ;文献有多个pdf时会提示打开哪个
(bibtex-completion-notes-template-one-file
(concat
"\n"
"* TODO ${author-or-editor} (${year}): ${title}\n"
" :PROPERTIES:\n"
" :ID: %(org-id-uuid)\n"
" :Journal: ${journal}\n"
" :ROAM_REFS: [[cite:&${=key=}]]\n"
" :END:\n\n"))
(bibtex-completion-notes-template-multiple-files
(concat
":PROPERTIES:\n"
":ID: %(org-id-uuid)\n"
":Journal: ${journal}\n"
":ROAM_REFS: [[cite:&${=key=}]]\n"
":END:\n"
"#+Title: Notes on: ${author-or-editor} (${year}): ${title}\n\n"))
(bibtex-completion-display-formats
'((article . "${=has-pdf=:1}${=has-note=:1} ${=type=:3} ${year:4} ${author:20} ${title:40} ${journal:20}")
(inbook . "${=has-pdf=:1}${=has-note=:1} ${=type=:3} ${year:4} ${author:20} ${title:40} Chapter ${chapter:20}")
(incollection . "${=has-pdf=:1}${=has-note=:1} ${=type=:3} ${year:4} ${author:20} ${title:40} ${booktitle:20}")
(inproceedings . "${=has-pdf=:1}${=has-note=:1} ${=type=:3} ${year:4} ${author:20} ${title:40} ${booktitle:20")
(t . "${=has-pdf=:1}${=has-note=:1} ${=type=:3} ${author:20} ${year:4} ${=type=:7} ${title:90}")))
(bibtex-completion-open-any 'org-ref-open-bibtex-pdf)
;; (bibtex-completion-pdf-open-function 'xah-open-in-external-app)
(bibtex-completion-pdf-open-function 'find-file)
(bibtex-completion-pdf-field "file")
(bibtex-completion-pdf-symbol "⌘")
(bibtex-completion-notes-symbol "✎"))
当 bibtex-completion 指向的文献目录内容有变时,需要执行一下 bibtex-completion-clear-cache 刷新缓存,确保其与源数据保持一致。
4 bibtex-completion 用户交互界面
bibtex-completion 是一个后端组件,它不提供与用户交互的界面,与用户交互的界面通常由 helm-bibtex 或 ivy-bibtex 提供。它们的配置已经写在 elpa.el 文件里了。
5 org-ref
org-ref 包提供 org-ref-insert-link 命令(自己指定了快捷键 C-c ]),用于向 org 文件中插入引用。它也是通过获取 bibtex-completion 提供的 bib 条目列表,打开一个选择界面,在安装了 helm 的情况下,调用 helm 的能力来完成条目选择,并插入链接。
org-ref-insert-link 是一个 generic 程序,它会调用变量 org-ref-insert-cite-function 定义的函数。 org-ref-insert-cite-function 默认值是 org-ref-insert-cite-link ,该函数不搜索 bib 条目的关键词,所以换成 org-ref-cite-insert-helm 。
(use-package org-ref ;交叉引用
:ensure t
:bind (:map org-mode-map
("C-c ]" . org-ref-insert-link))
:config
(setq org-ref-insert-cite-function 'org-ref-cite-insert-helm))
6 org-noter:批注式笔记
org-noter 其实是另一种做笔记的模式,它基于 pdf 的每一页,相当于在纸页上写下批注笔记。org-noter 建议在 org 文档中打开,这时会查找并打开对应的 pdf 文档,如果没有,则会提示输入 pdf 文档的路径。
所以,可以将 bibtex-compilation 的笔记功能和 org-noter 提供的笔记功能结合起来,既做大纲式笔记,也针对文档做批注式笔记。如果要将两者结合,最好是在 org 文档中新建一个一级标题,然后将 org-noter 的笔记都放在这个一级标题下面,这样可以避免两种不同功能混淆。
org-noter 会创建一个新 frame,这个 frame 被分为两个 window,一个 window 叫做 doc window,另一个叫做 note window。所以 org-noter 的很多变量、命令以及键绑定都是区分 doc 和 note 的。
| Key | Description | Where? |
|---|---|---|
| i | Insert note | Document buffer |
| M-i | 插入精确注释 | Document buffer |
| q | Kill session | Document buffer |
| M-p | 同步上一页/章节 | 文档和注释缓冲区 |
| M-. | 同步当前页面/章节 | 文档和注释缓冲区 |
| M-n | 同步下一页/章节 | 文档和注释缓冲区 |
| C-M-p | 同步以前的笔记 | 文档和注释缓冲区 |
| C-M-. | 同步所选笔记 | 文档和注释缓冲区 |
| C-M-n | 同步下一个笔记 | 文档和注释缓冲区 |
(use-package org-noter ;给pdf文件作标注
:ensure t
:bind (("C-c n n" . org-noter)
(:map org-noter-doc-mode-map
("e" . org-noter-insert-precise-note)))
:preface
(setq org-noter-notes-search-path `(,(concat org-directory "roam/literature")))
:config
(setq org-noter-default-heading-title "第 $p$ 页的笔记")
(setq org-noter-auto-save-last-location t) ;自动保存上次阅读位置
(advice-add 'org-noter--set-notes-scroll :override (lambda (&rest args)))
)
如果启用了 org-indent-mode , org-noter--set-notes-scroll 函数会横向卷动(scroll)缓冲区。而 emacs 在横向卷动缓冲区时,就会激活行截断。所以,只能禁止 org-noter--set-notes-scroll 干活。详见这个 issue 。
注意:org-noter 使用时有一个问题,如果使用 C-k 关闭 org-noter 的 buffer,那么再使用 org-noter 时就会出错:“apply: Wrong type argument: stringp, nil”,必须要 revert-buffer 才能正常使用。正确的关闭方式是使用 org-noter-kill-session (在 doc window 可以按 q ,在 note window 只能输入命令),或者也可以 delete-frame 。
一点小问题:和 pdf-tools 一起使用的时候,会弹出一个错误:“Error running timer ‘org-noter–show-arrow’: (invalid-function pdf-view-current-overlay)”,这个问题也被人提在这个 issue 里,但没有任何回复。本以为是编译 epdfserver 的问题,后来直接安装 debian 仓库里的包也是同样的问题,看来这个问题比较麻烦,不然早解决了。
7 org-pdf-tools:pdf 工具
org-pdf-tools 是一个强大的 pdf 工具,它可以在正式记笔记之前先运行 org-noter-create-skeleton 命令抽取 pdf 文档里的大纲、批注、链接等信息放入 org 文档,可以省掉很多的工作量,当然有些 pdf 文档可能没有大纲信息,那就没有办法了。
如果在 windows 里使用,需要 msys2 环境安装 mingw-w64-x8664-emacs-pdf-tools-server:
pacman -S mingw-w64-x86_64-emacs-pdf-tools-server
文档中说,要激活这个包,就要把 (pdf-tools-install) 放到启动配置文件里,我们让它在 pdf-tools 加载后运行:
(use-package pdf-tools
:ensure t
:defer t
:config
(pdf-tools-install)
)
8 org-download:下载图片小工具
org-download 是一个小工具,用于从系统剪贴板(clipboard)或网络下载图片,并插入到 org 文档中。
(use-package org-download ;从本地或远程下载图片
:ensure t
:hook ((dired-mode . org-download-enable)
(org-mode . org-download-enable))
:bind ((:map org-mode-map
("C-M-y" . org-download-clipboard)
("C-c n d" . org-download-image)))
:config
(setq-default org-download-heading-lvl 1) ;; 用一级标题给截图文件命名
(setq-default org-download-image-dir "./media")) ;截图文件放置目录
9 附:最终生成代码
9.1 init-bibtex.el
;;; init-bibtex.el --- init for bibtex utilities. -*- lexical-binding: t -*-
;;; Commentary:
;;; Code:
(eval-when-compile
(require 'use-package))
(use-package bibtex-completion ;它是 bibtex 各种操作的基础包
:defer t
:ensure t
:custom
(bibtex-completion-bibliography (directory-files-recursively "~/tex/paper/" "^.*\\.bib$" nil))
(bibtex-completion-library-path (directory-files-recursively "~/tex/paper/" "^literature$" t))
(bibtex-completion-notes-path (expand-file-name "roam/literature/" org-directory))
(bibtex-completion-additional-search-fields '(keywords))
(bibtex-completion-find-additional-pdfs t) ;文献有多个pdf时会提示打开哪个
(bibtex-completion-notes-template-one-file
(concat
"\n"
"* TODO ${author-or-editor} (${year}): ${title}\n"
" :PROPERTIES:\n"
" :ID: %(org-id-uuid)\n"
" :Journal: ${journal}\n"
" :ROAM_REFS: [[cite:&${=key=}]]\n"
" :END:\n\n"))
(bibtex-completion-notes-template-multiple-files
(concat
":PROPERTIES:\n"
":ID: %(org-id-uuid)\n"
":Journal: ${journal}\n"
":ROAM_REFS: [[cite:&${=key=}]]\n"
":END:\n"
"#+Title: Notes on: ${author-or-editor} (${year}): ${title}\n\n"))
(bibtex-completion-display-formats
'((article . "${=has-pdf=:1}${=has-note=:1} ${=type=:3} ${year:4} ${author:20} ${title:40} ${journal:20}")
(inbook . "${=has-pdf=:1}${=has-note=:1} ${=type=:3} ${year:4} ${author:20} ${title:40} Chapter ${chapter:20}")
(incollection . "${=has-pdf=:1}${=has-note=:1} ${=type=:3} ${year:4} ${author:20} ${title:40} ${booktitle:20}")
(inproceedings . "${=has-pdf=:1}${=has-note=:1} ${=type=:3} ${year:4} ${author:20} ${title:40} ${booktitle:20")
(t . "${=has-pdf=:1}${=has-note=:1} ${=type=:3} ${author:20} ${year:4} ${=type=:7} ${title:90}")))
(bibtex-completion-open-any 'org-ref-open-bibtex-pdf)
;; (bibtex-completion-pdf-open-function 'xah-open-in-external-app)
(bibtex-completion-pdf-open-function 'find-file)
(bibtex-completion-pdf-field "file")
(bibtex-completion-pdf-symbol "⌘")
(bibtex-completion-notes-symbol "✎"))
(use-package org-ref ;交叉引用
:ensure t
:bind (:map org-mode-map
("C-c ]" . org-ref-insert-link))
:config
(setq org-ref-insert-cite-function 'org-ref-cite-insert-helm))
(use-package org-download ;从本地或远程下载图片
:ensure t
:hook ((dired-mode . org-download-enable)
(org-mode . org-download-enable))
:bind ((:map org-mode-map
("C-M-y" . org-download-clipboard)
("C-c n d" . org-download-image)))
:config
(setq-default org-download-heading-lvl 1) ;; 用一级标题给截图文件命名
(setq-default org-download-image-dir "./media")) ;截图文件放置目录
(use-package org-noter ;给pdf文件作标注
:ensure t
:bind (("C-c n n" . org-noter)
(:map org-noter-doc-mode-map
("e" . org-noter-insert-precise-note)))
:preface
(setq org-noter-notes-search-path `(,(concat org-directory "roam/literature")))
:config
(setq org-noter-default-heading-title "第 $p$ 页的笔记")
(setq org-noter-auto-save-last-location t) ;自动保存上次阅读位置
(advice-add 'org-noter--set-notes-scroll :override (lambda (&rest args)))
)
(use-package pdf-tools
:ensure t
:defer t
:config
(pdf-tools-install)
)
(provide 'init-bibtex)
;;; init-bibtex ends here
1万+

被折叠的 条评论
为什么被折叠?



