之光的xeft感觉不错啊,谢谢推荐,我试试看。我之前感觉org roam太重,加上自己还 hack了太多东西,一直想换个轻量级的,试用了zk等一堆包,都有一些局限性不够灵活还是没换掉
我感觉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的唯一理由就是双链,以及未来的可扩展性。它的缺点就是总是有点小毛病,虽然作者更新很快,但总给人一种不稳定的感觉。
另,能分享一下你的方法,或者代码吗?谢谢
关于org-roam的代码我大概分为4个部分
- 一些基础设置
- 对于标题链接的显示
- org-roam 和 org-agenda 的结合
- citar 文献操作的结合
其中2是从doom emacs 拿出来的, 3是org- roam论坛一位大佬的方法,4是org-roam作者分享他记录文献的流程,我从中学了不少我能用的方法
我很粗略的写了一点代码是干啥的,因为大部分都是我重组或者抄的,如果你感兴趣,建议去找找原代码出处,我有空可能会写一篇详细的工作流例如录一些动图来介绍这些方法。
- 首先是基础的设置,这里感谢论坛大佬的提醒,我也使用了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)))))
- 标题链接,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)))
- 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 ""))))
- 对于 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看到所有的包,有疑问欢迎提出,我入门的时受过很多人的帮助,也很乐意分享任何我能有的一点知识
非常感谢!
重新定义 org-roam-node 的展示很有用,我在 org-roam 作者的配置里面也找到了,很好。
与org-agenda 结合的好处还是没看得太明白。我是直接把一个 task.org 丢到 roam 文件夹里面。
org-roam 作者 Jethro 关于记笔记的方法确实很值得一看,我也从中学习了很多方法。
哈哈,我也是从别人那里学到的。agenda 感兴趣的话可以看这篇博客:Boris Buliga - Task management with org-roam Vol. 5: Dynamic and fast agenda
好处大概就是,我在任何一个node里都可以添加TODO,比如某一个node是读书笔记,书没读完,或者是工作进行一半,或者是笔记写一半等等,这些node都会被动态的添加到agenda里,如果TODO完成了,则会自动移除。如果把所有的node都添加到agenda里,速度会非常慢,而这种动态的方法几乎没有速度影响
好的。感谢!明白那段代码的意思了。
配置(cl-defmethod org-roam-node-type ((node org-roam-node))之后,启动一直出现 error: Unknown specializer org-roam-node,这种情况怎么解决哇?看网上说 ;; This needs to be after the org-roam’ declaration as it is dependent on the structures of
org-roam’. 但是不太懂什么意思
原因可能是需要在load org roam之后再执行这段代码,加个with eval after load 试试
十分感谢,我用这个成功了。还有一个问题想请教一下,我现在用的是 org-roam v2, 有没有什么比较方便的预览org的方式啊
consult-org-roam可以做到在浏览node列表的时候同步预览org文件的内容,我一般用这个,非常方便
可以分享一下你的配置和使用 consult-org-roam 的 workflow 吗,我按照官方给的配置都不太有效,比如 consult-org-roam-search 永远都是 no matched
(use-package consult-org-roam
:ensure t
:init
(require 'consult-org-roam)
;; Activate the minor-mode
(consult-org-roam-mode 1)
:custom
(consult-org-roam-grep-func #'consult-ripgrep)
:config
;; Eventually suppress previewing for certain functions
(consult-customize
consult-org-roam-forward-links
:preview-key (kbd "M-."))
:bind
("C-c n e" . consult-org-roam-file-find)
("C-c n b" . consult-org-roam-backlinks)
("C-c n r" . consult-org-roam-search))
我的配置就一行,简单的打开consult-org-roam-mode,它就会自动激活预览,别的也没配置什么……
请问这句话的含义是 Emacs 29 已经不需要依赖系统的 sqlite 了吗?
我在安装 emacsql-sqlite-builtin
发现它似乎找不到内置的 sqlite, 依然需要手动安装. 比如:
(use-package emacsql-sqlite-builtin
:init
(require 'emacsql)
(require 'emacsql-sqlite))
;; Error (use-package): emacsql-sqlite-builtin/:init: Cannot open load file: No such file or directory, emacsql
依然需要手动安装 emacs-sql
与 emacsql-sqlite
二者, 如
(use-package emacsql-sqlite-builtin
:init
(use-package emacsql)
(use-package emacsql-sqlite))
就没有任何报错. 但这样似乎显得多余.
P.S.
emacs-version:
GNU Emacs 29.0.50 (build 1, aarch64-apple-darwin22.1.0, NS appkit-2282.14 Version 13.0 (Build 22A5342f)) of 2022-09-20
OS: macOS; 安装方式:
brew install emacs-plus@29 --with-native-comp
我的理解是 Emacs 29 内置了对 sqlite3的绑定,不需要通过动态模块来实现对 sqlite3的支持。 你系统中还是要安装 sqlite3 这个程序的。
按理说你只要安装好 emacsql-sqlite-builtin以及他的相关依赖就可以了,你是用过什么方式安装包?(比如:package.el, straight.el, Borg)
用 package.el 通过 melpa 安装的话,这样应该就可以了:
(use-package emacsql-sqlite-builtin :ensure t)
你是用过什么方式安装包?
使用的是 straight.el. 非常感谢, 这个提醒了我, 目前问题已经解决. 原因大致是 emacsql 和 emacsql-sqlite 这两个包应该隶属于 org-roam, 而按照 straight.el 的逻辑, 这两个包在加载 org-roam 前都不能被调用. 于是把 (use-package emacsql-sqlite-builtin)
这一行放在 (use-package org-roam)
后面加载就没有出错了.
在 emacs 29 刚安装了 emacsql-sqlite-builtin
然后设置 (setq org-roam-database-connector 'sqlite-builtin)
,load org-roam 报错如下
Lisp error: (invalid-slot-type emacsql-sqlite-builtin-connection process process #<sqlite db=0x... name=/path/to/roam-db.db>)
单独使用 (emacsql-sqlite-builtin "/path/to/roam-db.db")
试了一下报同样的错误。emacsql
emacsql-sqlite
emacsql-sqlite-builtin
都安装的melpa最新的版本。
网上搜了一下没有搜到,有碰到同样问题的吗?