5 个 Org Roam 技巧提高 Emacs 生产力

简介

Org Roam 是一种非常好的工具,可以编写和组织您的想法,但是当您利用 Org Roam 提供的更多功能时,您可以为常见任务创建高效的自定义工作流程。

在本视频中,我将向您展示 5 个在 Org Roam 和 Org Mode 特性的帮助下优化笔记记录和项目管理工作流程的技巧。

如果你觉得这个指南有帮助, 请考虑通过链接 帮助 页 支持系统工匠!

开始配置

开始之前,请确保您已经设置好 Org Roam。

您可以通过复制以下配置快速入手,但是如果首先观看 Org Roam 系列的上一集视频,您会学到更多内容:

(use-package org-roam
  :ensure t
  :init
  (setq org-roam-v2-ack t)
  :custom
  (org-roam-directory "~/RoamNotes")
  (org-roam-completion-everywhere t)
  :bind (("C-c n l" . org-roam-buffer-toggle)
         ("C-c n f" . org-roam-node-find)
         ("C-c n i" . org-roam-node-insert)
         :map org-mode-map
         ("C-M-i" . completion-at-point)
         :map org-roam-dailies-map
         ("Y" . org-roam-dailies-capture-yesterday)
         ("T" . org-roam-dailies-capture-tomorrow))
  :bind-keymap
  ("C-c n d" . org-roam-dailies-map)
  :config
  (require 'org-roam-dailies) ;; Ensure the keymap is available
  (org-roam-db-autosync-mode))

有很多代码要展示!

在本视频中,我将向您展示许多自定义代码,这些代码产生了这些工作流程改进。如果您目前还没有深入研究 Emacs Lisp,其中有些内容可能不完全清晰,但这没关系!

所有代码均可取并放入您的配置中,只要它正常工作,您无需理解其工作原理!

如果您确实想学习,我建议阅读代码并使用 M-x describe-functionM-x describe-variable 阅读文档字符串。如果您想要一个更完整的教程,也可以观看我的 学习 Emacs Lisp 系列!

请在评论中提出任何疑问,我将尽力澄清!

更快速地插入笔记以实现更流畅的写作

有时在写作时,您可能希望在 Org Roam 笔记中创建一个新节点而不中断写作流程!通常您将使用 org-roam-node-insert,但当您使用此命令创建新笔记时,该新笔记将在创建后打开。

我们可以定义一个函数,使您可以创建新笔记并在当前文档中插入链接,而不打开新笔记的缓冲区。

这将使您能够在写作时快速为您提到的主题创建新笔记,以便以后可以在这些笔记中填写更多细节!

;; 将此绑定为 C-c n I
(defun org-roam-node-insert-immediate (arg &rest args)
  (interactive "P")
  (let ((args (cons arg args))
        (org-roam-capture-templates (list (append (car org-roam-capture-templates)
                                                  '(:immediate-finish t)))))
    (apply #'org-roam-node-insert args)))

此函数采取 org-roam-capture-templates 中的第一个捕获模板(通常是“默认”模板),并向其添加 :immediate-finish t 捕获属性,以在捕获完成后防止加载笔记缓冲区:

感谢 Umar Ahmad 提供的代码片段!

从 Org Roam 笔记构建 Org 议程

Org Mode 最有用的功能之一是议程视图。实际上,您可以使用 Org Roam 笔记作为此视图的源!

通常,您不会希望拉入 所有 Org Roam 笔记,因此我们只使用具有特定标签(如 Project)的笔记。

下面是一个代码片段,它将找到具有特定标签的所有笔记,然后使用相应的笔记文件设置您的 org-agenda-list

;; 包含此代码的缓冲区必须将 lexical-binding 设置为 t!
;; 有关更多详细信息,请参阅最终配置。

 (defun my/org-roam-filter-by-tag (tag-name)
   (lambda (node)
     (member tag-name (org-roam-node-tags node))))

 (defun my/org-roam-list-notes-by-tag (tag-name)
   (mapcar #'org-roam-node-file
           (seq-filter
            (my/org-roam-filter-by-tag tag-name)
            (org-roam-node-list))))

 (defun my/org-roam-refresh-agenda-list ()
   (interactive)
   (setq org-agenda-files (my/org-roam-list-notes-by-tag "Project")))

 ;; 首次为会话生成议程列表
 (my/org-roam-refresh-agenda-list)

现在运行 M-x org-agenda 查看 org 议程,然后按 a 查看每日时间表或按 d 查看项目文件中的所有 TODO 列表。

为了获得最佳结果,请确保将所需的标签(在本例中为 Project)添加到作为捕获模板的一部分的新笔记文件。只记住在使用该标签创建新笔记后调用 my/org-roam-refresh-agenda-list 来刷新列表!

注意:我还没有找到可靠而有效的方法将日常事项拉入议程!一旦找到,我可能会制作另一个视频。

提示:改善议程视图中笔记的外观

您可能会注意到,来自您的 Org Roam 文件的议程行由于带时间戳的文件名而看起来有点难看。我们可以通过在项目文件之一的标题行添加 category 来修复此问题,如下所示:

#+title: Mesche
#+category: Mesche
#+filetags: Project

通常,您会希望类别包含与笔记相同的名称,以便我们可以更新来自 Org Roam 第2集 的 Project 模板以自动包含它:

("p" "project" plain "* Goals\n\n%?\n\n* Tasks\n\n** TODO Add initial tasks\n\n* Dates\n\n"
 :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+category: ${title}\n#+filetags: Project")
 :unnarrowed t)

这会将标题和类别设置为新笔记文件的名称,从而在议程视图中产生更清晰的条目。

从具有特定标签的笔记列表中选择

org-roam-node-find 函数给了我们过滤显示用于选择的笔记列表的能力。

我们可以定义自己的函数,为具有特定标签(如我们之前讨论过的 Project)的笔记显示选择列表。这对于设置快速选择特定笔记集的键绑定很有用!

一个额外的好处是我们可以覆盖创建新笔记时使用的捕获模板集。

这意味着如果笔记尚不存在,我们可以自动使用项目捕获模板创建新笔记!

(defun my/org-roam-project-finalize-hook ()
  "如果捕获未被中止,则将捕获的项目文件添加到`org-agenda-files'中。"
  ;; 由于临时添加了钩子,所以删除钩子
  (remove-hook 'org-capture-after-finalize-hook #'my/org-roam-project-finalize-hook)

  ;; 如果确认捕获,则将项目文件添加到议程列表中
  (unless org-note-abort
    (with-current-buffer (org-capture-get :buffer)
      (add-to-list 'org-agenda-files (buffer-file-name)))))

(defun my/org-roam-find-project ()
  (interactive)
  ;; 捕获完成后,将项目文件添加到议程中
  (add-hook 'org-capture-after-finalize-hook #'my/org-roam-project-finalize-hook)

  ;; 选择要打开的项目文件,如有必要则创建项目文件
  (org-roam-node-find
   nil
   nil
   (my/org-roam-filter-by-tag "Project")
   :templates
   '(("p" "project" plain "* Goals\n\n%?\n\n* Tasks\n\n** TODO Add initial tasks\n\n* Dates\n\n"
      :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+category: ${title}\n#+filetags: Project")
      :unnarrowed t))))  

(global-set-key (kbd "C-c n p") #'my/org-roam-find-project)

此代码片段的一个有用方面是 org-capture-after-finalize-hook 允许我们确保新的项目笔记通过调用我们之前定义的 my/org-roam-project-finalize-hook 函数自动添加到 Org 议程中!

简化自定义捕获任务和笔记

Org Roam 提供了一个名为 org-roam-capture- 的低级函数(是的,有连字符!),允许您以非常灵活的方式调用笔记捕获功能。更多信息可以在 Org Roam 手册中找到:扩展捕获系统

我们可以使用此函数来优化捕获工作流程的特定部分!

这里有一些您可能使用它的方式:

保留笔记和任务的收件箱

如果您想使用单个键绑定快速捕获新笔记和任务,以便稍后审查,我们可以使用 org-roam-capture- 将其捕获到单个特定文件,如 Inbox.org!

即使此文件不会有时间戳文件名,它也仍然会被视为 Org Roam 笔记中的节点。

(defun my/org-roam-capture-inbox ()
  (interactive)
  (org-roam-capture- :node (org-roam-node-create)
                     :templates '(("i" "inbox" plain "* %?"
                                  :if-new (file+head "Inbox.org" "#+title: Inbox\n")))))

(global-set-key (kbd "C-c n b") #'my/org-roam-capture-inbox)

直接捕获任务到特定项目

如果您设置了之前提到的项目笔记文件,您可以设置一个捕获模板,让您快速为任何项目捕获任务。

与前面的示例一样,我们可以选择已存在的项目,或者在项目笔记不存在时自动创建项目笔记!

(defun my/org-roam-capture-task ()
  (interactive)
  ;; 捕获完成后,将项目文件添加到议程中
  (add-hook 'org-capture-after-finalize-hook #'my/org-roam-project-finalize-hook)

  ;; 如有必要,捕获新任务并创建项目文件
  (org-roam-capture- :node (org-roam-node-read
                            nil
                            (my/org-roam-filter-by-tag "Project"))
                     :templates '(("p" "project" plain "* TODO %?"
                                   :if-new (file+head+olp "%<%Y%m%d%H%M%S>-${slug}.org"
                                                          "#+title: ${title}\n#+category: ${title}\n#+filetags: Project"
                                                          ("Tasks"))))))
(global-set-key (kbd "C-c n t") #'my/org-roam-capture-task)

要注意的一点是,我们在捕获模板中使用 file+head+olp,这样我们就可以将新任务条目放在“Tasks”标题下。

我们还使用之前定义的 my/org-roam-project-finalize-hook 函数,以便任何新项目都会被添加到 Org 议程中!

自动将完成的任务复制(或移动)到每日文件

每日文件一个有趣的用途是保留完成任务的记录。如果我们可以自动将任何 Org 模式文件中的完成任务复制到当天的每日文件,那会怎么样?

我们可以通过添加一些自定义代码来实现这一目的!

以下代码段为所有 Org 任务状态更改设置了一个钩子,然后将完成的(DONE)条目复制到当天的笔记文件:

(defun my/org-roam-copy-todo-to-today ()
  (interactive)
  (let ((org-refile-keep t) ;; 将此设置为 nil 以删除原件!
        (org-roam-dailies-capture-templates
          '(("t" "tasks" entry "%?"
             :if-new (file+head+olp "%<%Y-%m-%d>.org" "#+title: %<%Y-%m-%d>\n" ("Tasks")))))
        (org-after-refile-insert-hook #'save-buffer)
        today-file
        pos)
    (save-window-excursion
      (org-roam-dailies--capture (current-time) t)
      (setq today-file (buffer-file-name))
      (setq pos (point)))

    ;; 仅在目标文件与当前文件不同时重新归档
    (unless (equal (file-truename today-file)
                   (file-truename (buffer-file-name)))
      (org-refile nil nil (list "Tasks" today-file nil pos)))))

(add-to-list 'org-after-todo-state-change-hook
             (lambda ()
               (when (equal org-state "DONE")
                 (my/org-roam-copy-todo-to-today))))

如果要移动完成的任务而不是复制它,请在此代码中将 org-refile-keep 设置为 nil!

此代码较高级,因此请参阅下一节以了解其工作原理!

工作原理

为了在 TODO 项状态更改时收到通知,我们将 my/org-roam-copy-todo-to-today 函数添加到 org-after-todo-state-change-hook 列表中。

当用户完成一个任务时,此函数将设置一个“日常”临时捕获模板,它将跳转到当天日期文件中的“Tasks”标题下。这被包装在 save-window-excursion 调用中,以确保捕获作业不会更改您的窗口配置和当前缓冲区。

如果捕获目标文件不是当前日期的文件,我们调用 org-refile 以将项目复制(或在 org-refile-keepnil 时移动)到新位置!这避免了将完成的任务移动回它已经存在的文件中(这会引发错误!)。

最终配置

非常重要的注意事项! 确保使用此代码的配置文件顶部有以下行!

;; -*- lexical-binding: t; -*-

这可以确保在评估此配置之前加载 Org Roam,这对我们编写的自定义代码正常工作很重要。

此行启用词法绑定,可以确保 my/org-roam-filter-by-tag 函数正确工作。

(use-package org-roam
  :ensure t
  :demand t  ;; Ensure org-roam is loaded by default
  :init
  (setq org-roam-v2-ack t)
  :custom
  (org-roam-directory "~/RoamNotes")
  (org-roam-completion-everywhere t)
  :bind (("C-c n l" . org-roam-buffer-toggle)
         ("C-c n f" . org-roam-node-find)
         ("C-c n i" . org-roam-node-insert)
         ("C-c n I" . org-roam-node-insert-immediate)
         ("C-c n p" . my/org-roam-find-project)
         ("C-c n t" . my/org-roam-capture-task)
         ("C-c n b" . my/org-roam-capture-inbox)
         :map org-mode-map
         ("C-M-i" . completion-at-point)
         :map org-roam-dailies-map
         ("Y" . org-roam-dailies-capture-yesterday)
         ("T" . org-roam-dailies-capture-tomorrow))
  :bind-keymap
  ("C-c n d" . org-roam-dailies-map)
  :config
  (require 'org-roam-dailies) ;; Ensure the keymap is available
  (org-roam-db-autosync-mode))

(defun org-roam-node-insert-immediate (arg &rest args)
  (interactive "P")
  (let ((args (push arg args))
        (org-roam-capture-templates (list (append (car org-roam-capture-templates)
                                                  '(:immediate-finish t)))))
    (apply #'org-roam-node-insert args)))

(defun my/org-roam-filter-by-tag (tag-name)
  (lambda (node)
    (member tag-name (org-roam-node-tags node))))

(defun my/org-roam-list-notes-by-tag (tag-name)
  (mapcar #'org-roam-node-file
          (seq-filter
           (my/org-roam-filter-by-tag tag-name)
           (org-roam-node-list))))

(defun my/org-roam-refresh-agenda-list ()
  (interactive)
  (setq org-agenda-files (my/org-roam-list-notes-by-tag "Project")))

;; Build the agenda list the first time for the session
(my/org-roam-refresh-agenda-list)

(defun my/org-roam-project-finalize-hook ()
  "Adds the captured project file to `org-agenda-files' if the
capture was not aborted."
  ;; Remove the hook since it was added temporarily
  (remove-hook 'org-capture-after-finalize-hook #'my/org-roam-project-finalize-hook)

  ;; Add project file to the agenda list if the capture was confirmed
  (unless org-note-abort
    (with-current-buffer (org-capture-get :buffer)
      (add-to-list 'org-agenda-files (buffer-file-name)))))

(defun my/org-roam-find-project ()
  (interactive)
  ;; Add the project file to the agenda after capture is finished
  (add-hook 'org-capture-after-finalize-hook #'my/org-roam-project-finalize-hook)

  ;; Select a project file to open, creating it if necessary
  (org-roam-node-find
   nil
   nil
   (my/org-roam-filter-by-tag "Project")
   :templates
   '(("p" "project" plain "* Goals\n\n%?\n\n* Tasks\n\n** TODO Add initial tasks\n\n* Dates\n\n"
      :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+category: ${title}\n#+filetags: Project")
      :unnarrowed t))))

(defun my/org-roam-capture-inbox ()
  (interactive)
  (org-roam-capture- :node (org-roam-node-create)
                     :templates '(("i" "inbox" plain "* %?"
                                  :if-new (file+head "Inbox.org" "#+title: Inbox\n")))))

(defun my/org-roam-capture-task ()
  (interactive)
  ;; Add the project file to the agenda after capture is finished
  (add-hook 'org-capture-after-finalize-hook #'my/org-roam-project-finalize-hook)

  ;; Capture the new task, creating the project file if necessary
  (org-roam-capture- :node (org-roam-node-read
                            nil
                            (my/org-roam-filter-by-tag "Project"))
                     :templates '(("p" "project" plain "** TODO %?"
                                   :if-new (file+head+olp "%<%Y%m%d%H%M%S>-${slug}.org"
                                                          "#+title: ${title}\n#+category: ${title}\n#+filetags: Project"
                                                          ("Tasks"))))))

(defun my/org-roam-copy-todo-to-today ()
  (interactive)
  (let ((org-refile-keep t) ;; Set this to nil to delete the original!
        (org-roam-dailies-capture-templates
          '(("t" "tasks" entry "%?"
             :if-new (file+head+olp "%<%Y-%m-%d>.org" "#+title: %<%Y-%m-%d>\n" ("Tasks")))))
        (org-after-refile-insert-hook #'save-buffer)
        today-file
        pos)
    (save-window-excursion
      (org-roam-dailies--capture (current-time) t)
      (setq today-file (buffer-file-name))
      (setq pos (point)))

    ;; Only refile if the target file is different than the current file
    (unless (equal (file-truename today-file)
                   (file-truename (buffer-file-name)))
      (org-refile nil nil (list "Tasks" today-file nil pos)))))

(add-to-list 'org-after-todo-state-change-hook
             (lambda ()
               (when (equal org-state "DONE")
                 (my/org-roam-copy-todo-to-today))))
订阅系统工匠通讯!
与最新的系统工匠新闻和更新保持同步! 阅读 Newsletter 浏览更多信息。
名称 (optional)
邮箱