Org-roam(v2) 以及 org-roam-ui 的使用姿势(已支持Emacs 29 内置的 sqlite)

文件尽量不改名,如果需要改名在文件头部声明。

使用 id 链接有什么劣势吗?

有个功能可以隐藏properties这些,看不习惯你可以操作一下。我在org-roam 的论坛看到的。

Org-roam 已经支持在 Emacs 29 中使用内置的 sqlite,需要安装 emacsql-sqlite-builtin ,这个包也可以通过 melpa安装 并设置:

(use-package org-roam
  :custom
  (org-roam-database-connector 'sqlite-builtin)
)

使用内置 sqlite 的好处:启动 org-roam 速度更快了,首次加载都是秒开的感觉,使用过程也更加顺滑了😄。

这是我的个人配置

也可以参考这里:

8 个赞

这次更新 org-roam 配置的时候才发现,(setq org-roam-v2-ack t) 这个配置也不需要了。

3 个赞

不用emacs 29, 在以前的版本有办法加速一开始的sqlite的加载吗?

我是在windows上用的, 而且还是org-roam v1.

求教!

不用 Emacs 29 没法用这个选项,因为 sqlite 在 29 才内置支持的。

建议你先升级到 V2,有升级向导。安装好 org-roam 最新版本后,执行 M-x, org-roam-migrate-wizard 会引导你进行数据迁移(建议迁移前先备份好 V1 的数据)。

那还是算了, 我完全不需要v2中的特性, 而且已经习惯v1了.

不过org-roam的内核本来就不大, 我准备自己维护了.

1 个赞

V1 用的习惯的话,确实没必要折腾了。用的舒服才是最重要的

之前v1的时候用过一段时间,但基本都是记流水帐了。现在就只用text mode记,然后grep一顿搜,简单粗暴。我觉得真正想在pieces间建立联系只有写成essay这一条路。。好工具不如烂笔头

1 个赞

其实Zettelkasten方法的核心是写而不是记,建议阅读介绍Zettelkasten方法的书 How to Take Smart Notes

那你可以试试之光的 xeft,我也弃用 roam 了

org-super-links 试一下,直接引用块,也比较方便。但我确实是直接写 essay 来积累知识。因为比较显性、可结构化的,只需要做索引和查资料就行,不需要专门记录和分析。所以我看大部分用 Notion 自建站的博客,都觉得…

嗯 其实很多类似的包我都试过了,包括zk,deft,orgsuperlinks,但用一个包有些时候就不得不得按它的逻辑来适配自己的工作流,最后发现其实很多需求对于干活而言都是虚假的。所以我决定从最朴素的搜索逻辑开始,写一些自己的小函数满足需求,这样才能慢慢地真正掌握emacs,而不是被一些fancy的功能牵着鼻子走。当然我觉得这些包都是极好的学习资源,用一用然后抄几个自己真正用的到的函数。包的强大引入的复杂性和找到自己需要的理解的操作,这是tradeoff得权衡。emacs本身最大的魅力就是帮助你真正理解和操控自己使用的工具

9 个赞

之光的xeft感觉不错啊,谢谢推荐,我试试看。我之前感觉org roam太重,加上自己还 hack了太多东西,一直想换个轻量级的,试用了zk等一堆包,都有一些局限性不够灵活还是没换掉

zk 的优缺点如何? org-super-links 貌似太简陋了,不成熟。

我感觉super-link更成熟一些,zk最大的优点是desk以及检索方便,但因为过度依赖动态的搜索,无法自动更新文件里面的链接,所以有时候反而误导用户以为当前的文件只有一个链接。我个人很喜欢zk这种轻量级的方法,最终放弃的另一个原因是zk对于org的融合有点割裂,虽然能用org-id作为链接,但zk的做法跟org-id没啥关系,我get-id后还是得手动处理。私以为zk其实是希望统一所有的文本格式都能处理,因此直接放弃了org mode的许多特性转而换用自己的方法

同时,zk这种纯搜索的方法在大量文件的情况下也不如roam或者xeft之类的数据库方法

谢谢分享zk使用经验。zk不能自动更新链接,且不能很好与orgmode融合的话,使用的意义就不是很大了。

org-super-link 我只看了简介。

目前我用org-roam的唯一理由就是双链,以及未来的可扩展性。它的缺点就是总是有点小毛病,虽然作者更新很快,但总给人一种不稳定的感觉。

另,能分享一下你的方法,或者代码吗?谢谢 :grinning:

关于org-roam的代码我大概分为4个部分

  1. 一些基础设置
  2. 对于标题链接的显示
  3. org-roam 和 org-agenda 的结合
  4. citar 文献操作的结合

其中2是从doom emacs 拿出来的, 3是org- roam论坛一位大佬的方法,4是org-roam作者分享他记录文献的流程,我从中学了不少我能用的方法

我很粗略的写了一点代码是干啥的,因为大部分都是我重组或者抄的,如果你感兴趣,建议去找找原代码出处,我有空可能会写一篇详细的工作流例如录一些动图来介绍这些方法。

1 个赞
  1. 首先是基础的设置,这里感谢论坛大佬的提醒,我也使用了built-in 的sqlite
     (setq org-roam-database-connector 'sqlite-builtin
     org-roam-mode-section-functions (list #'org-roam-backlinks-section
                                           #'org-roam-reflinks-section
                                           ;; #'org-roam-unlinked-references-section
                                           )
     org-roam-directory "~/Documents/emacs/orgmode/roam/"
     org-roam-dailies-directory "~/Documents/emacs/orgmode/roam"

     org-roam-db-gc-threshold most-positive-fixnum)

;; 使用侧边栏而不是完整buffer
      (add-to-list 'display-buffer-alist
                   '("\\*org-roam\\*"
                     (display-buffer-in-side-window)
                     (side . right)
                     (slot . 0)
                     (window-width . 0.25)
                     (window-parameters . ((no-other-window . t)
                                           (no-delete-other-windows . t)))))

  1. 标题链接,org-roam默认会把所有的标题链接和文件链接视为同一级,这样有时会因此重复索引或者不直观的特点,我从doom里面抄了一堆代码, 效果如第四个node那样(2021-0815是文件一级,而具体的标题一级是后面的链接)

代码


      ;; Codes blow are used to general a hierachy for title nodes that under a file
      (cl-defmethod org-roam-node-doom-filetitle ((node org-roam-node))
        "Return the value of \"#+title:\" (if any) from file that NODE resides in.
      If there's no file-level title in the file, return empty string."
        (or (if (= (org-roam-node-level node) 0)
                (org-roam-node-title node)
              (org-roam-get-keyword "TITLE" (org-roam-node-file node)))
            ""))
      (cl-defmethod org-roam-node-doom-hierarchy ((node org-roam-node))
        "Return hierarchy for NODE, constructed of its file title, OLP and direct title.
        If some elements are missing, they will be stripped out."
        (let ((title     (org-roam-node-title node))
              (olp       (org-roam-node-olp   node))
              (level     (org-roam-node-level node))
              (filetitle (org-roam-node-doom-filetitle node))
              (separator (propertize " > " 'face 'shadow)))
          (cl-case level
            ;; node is a top-level file
            (0 filetitle)
            ;; node is a level 1 heading
            (1 (concat (propertize filetitle 'face '(shadow italic))
                       separator title))
            ;; node is a heading with an arbitrary outline path
            (t (concat (propertize filetitle 'face '(shadow italic))
                       separator (propertize (string-join olp " > ") 'face '(shadow italic))
                       separator title)))))

     (setq org-roam-node-display-template (concat "${type:15} ${doom-hierarchy:80} " (propertize "${tags:*}" 'face 'org-tag)))

6 个赞
  1. org-roam agenda结合 这些代码的效果是,会对所有有TODO的文件动态添加到agenda列表。因为是用了org-roam的数据库,所以速度很快。我没细看代码,大概的原理是,当用户添加TODO的时候,会自动给文件打一个project的tag,如果改文件所有TODO都没有了,则益处project的tag,然后会用数据库读取包含project tag的文件添加到agenda文件列表

如果你打算使用这个功能,建议去关注org-roam论坛中该代码的作者,他在长期维护(从org-roam v1 一直维护到现在的v2)

    (defun vulpea-project-p ()
    "Return non-nil if current buffer has any todo entry.
  TODO entries marked as done are ignored, meaning the this
  function returns nil if current buffer contains only completed
  tasks."
    (seq-find                                 ; (3)
     (lambda (type)
       (eq type 'todo))
     (org-element-map                         ; (2)
         (org-element-parse-buffer 'headline) ; (1)
         'headline
       (lambda (h)
         (org-element-property :todo-type h)))))

  (defun vulpea-project-update-tag ()
      "Update PROJECT tag in the current buffer."
      (when (and (not (active-minibuffer-window))
                 (vulpea-buffer-p))
        (save-excursion
          (goto-char (point-min))
          (let* ((tags (vulpea-buffer-tags-get))
                 (original-tags tags))
            (if (vulpea-project-p)
                (setq tags (cons "project" tags))
              (setq tags (remove "project" tags)))

            ;; cleanup duplicates
            (setq tags (seq-uniq tags))

            ;; update tags if changed
            (when (or (seq-difference tags original-tags)
                      (seq-difference original-tags tags))
              (apply #'vulpea-buffer-tags-set tags))))))

  (defun vulpea-buffer-p ()
    "Return non-nil if the currently visited buffer is a note."
    (and buffer-file-name
         (string-prefix-p
          (expand-file-name (file-name-as-directory org-roam-directory))
          (file-name-directory buffer-file-name))))

  (defun vulpea-project-files ()
      "Return a list of note files containing 'project' tag." ;
      (seq-uniq
       (seq-map
        #'car
        (org-roam-db-query
         [:select [nodes:file]
          :from tags
          :left-join nodes
          :on (= tags:node-id nodes:id)
          :where (like tag (quote "%\"project\"%"))]))))

  (defun vulpea-agenda-files-update (&rest _)
    "Update the value of `org-agenda-files'."
    (setq org-agenda-files (vulpea-project-files)))

  (add-hook 'find-file-hook #'vulpea-project-update-tag)
  (add-hook 'before-save-hook #'vulpea-project-update-tag)

  (advice-add 'org-agenda :before #'vulpea-agenda-files-update)
  (advice-add 'org-todo-list :before #'vulpea-agenda-files-update)

  ;; functions borrowed from `vulpea' library
  ;; https://github.com/d12frosted/vulpea/blob/6a735c34f1f64e1f70da77989e9ce8da7864e5ff/vulpea-buffer.el

  (defun vulpea-buffer-tags-get ()
    "Return filetags value in current buffer."
    (vulpea-buffer-prop-get-list "filetags" "[ :]"))

  (defun vulpea-buffer-tags-set (&rest tags)
    "Set TAGS in current buffer.
  If filetags value is already set, replace it."
    (if tags
        (vulpea-buffer-prop-set
         "filetags" (concat ":" (string-join tags ":") ":"))
      (vulpea-buffer-prop-remove "filetags")))

  (defun vulpea-buffer-tags-add (tag)
    "Add a TAG to filetags in current buffer."
    (let* ((tags (vulpea-buffer-tags-get))
           (tags (append tags (list tag))))
      (apply #'vulpea-buffer-tags-set tags)))

  (defun vulpea-buffer-tags-remove (tag)
    "Remove a TAG from filetags in current buffer."
    (let* ((tags (vulpea-buffer-tags-get))l
           (tags (delete tag tags)))
      (apply #'vulpea-buffer-tags-set tags)))

  (defun vulpea-buffer-prop-set (name value)
    "Set a file property called NAME to VALUE in buffer file.
  If the property is already set, replace its value."
    (setq name (downcase name))
    (org-with-point-at 1
      (let ((case-fold-search t))
        (if (re-search-forward (concat "^#\\+" name ":\\(.*\\)")
                               (point-max) t)
            (replace-match (concat "#+" name ": " value) 'fixedcase)
          (while (and (not (eobp))
                      (looking-at "^[#:]"))
            (if (save-excursion (end-of-line) (eobp))
                (progn
                  (end-of-line)
                  (insert "\n"))
              (forward-line)
              (beginning-of-line)))
          (insert "#+" name ": " value "\n")))))

  (defun vulpea-buffer-prop-set-list (name values &optional separators)
    "Set a file property called NAME to VALUES in current buffer.
  VALUES are quoted and combined into single string using
  `combine-and-quote-strings'.
  If SEPARATORS is non-nil, it should be a regular expression
  matching text that separates, but is not part of, the substrings.
  If nil it defaults to `split-string-default-separators', normally
  \"[ \f\t\n\r\v]+\", and OMIT-NULLS is forced to t.
  If the property is already set, replace its value."
    (vulpea-buffer-prop-set
     name (combine-and-quote-strings values separators)))

  (defun vulpea-buffer-prop-get (name)
    "Get a buffer property called NAME as a string."
    (org-with-point-at 1
      (when (re-search-forward (concat "^#\\+" name ": \\(.*\\)")
                               (point-max) t)
        (buffer-substring-no-properties
         (match-beginning 1)
         (match-end 1)))))

  (defun vulpea-buffer-prop-get-list (name &optional separators)
    "Get a buffer property NAME as a list using SEPARATORS.
  If SEPARATORS is non-nil, it should be a regular expression
  matching text that separates, but is not part of, the substrings.
  If nil it defaults to `split-string-default-separators', normally
  \"[ \f\t\n\r\v]+\", and OMIT-NULLS is forced to t."
    (let ((value (vulpea-buffer-prop-get name)))
      (when (and value (not (string-empty-p value)))
        (split-string-and-unquote value separators))))

  (defun vulpea-buffer-prop-remove (name)
    "Remove a buffer property called NAME."
    (org-with-point-at 1
      (when (re-search-forward (concat "\\(^#\\+" name ":.*\n?\\)")
                               (point-max) t)
        (replace-match ""))))
  1. 对于 citar文献的融合

鉴于这里是一种方法而不是形式,我建议你搜一下 org-roam作者写的那一篇关于他如何记录笔记和科研的方法,理解思路然后再阅读代码

首先是 citar部分,可以从bib文件一键生成node和索引,包括文献名和文献文件的链接。

  (setup citar
    (:option org-cite-global-bibliography '("~/Documents/emacs/orgmode/bibliography/better_zotero_bib.bib")
             org-cite-insert-processor 'citar
             org-cite-follow-processor 'citar
             org-cite-activate-processor 'citar
             citar-bibliography org-cite-global-bibliography))

  ;; borrowed from https://jethrokuan.github.io/org-roam-guide/ as a method for insert notes for reference
  (defun lewis/org-roam-node-from-cite (keys-entries)
    (interactive (list (citar-select-ref :multiple nil :rebuild-cache t)))
    (let ((title (citar--format-entry-no-widths (cdr keys-entries)
                                                "${author editor}::${title}")))
      (org-roam-capture- :templates
                         '(("r" "reference" plain "%?" :if-new
                            (file+head "reference/${citekey}.org"
                                       ":PROPERTIES:
  :ROAM_REFS: [cite:@${citekey}]
  :END:
  ,#+title: ${title}\n* Action notes\n* Idea notes\n* Sealed notes")
                            :unnarrowed t))
                         :info (list :citekey (car keys-entries))
                         :node (org-roam-node-create :title title)
                         :props '(:finalize find-file))))

然后是添加了新的一列,你可以简单理解为独立的一组tag,这组tag是根据文件路径显示的

      (cl-defmethod org-roam-node-type ((node org-roam-node))
        "Return the TYPE of NODE."
        (condition-case nil
            (file-name-nondirectory
             (directory-file-name
              (file-name-directory
               (file-relative-name (org-roam-node-file node) org-roam-directory))))
          (error "")))

效果就是前面的reference或者article分类。

上面的代码可能看起来有点乱,我今晚也在忙别的写的仓促。你可以在我的配置里 GitHub - nowislewis/nowisemacs: A full-blown emacs configuration framework with easy abstraction 找到更为系统的组织形式

我是用org-mode管理所有的配置,都在里面的一个 init.org里。说实话因为转了borg管理包,你不一定能在init.org看到所有的包,有疑问欢迎提出,我入门的时受过很多人的帮助,也很乐意分享任何我能有的一点知识

8 个赞