(feat): support file-level IDs (#1163)

Additionally cache IDs at outline-level 0, now that property drawers are supported in Org 9.4.

Update org-roam--format-link to prefer ID links wherever possible. That is, when a file has an ID, use an id link instead of file link.
This commit is contained in:
Jethro Kuan
2020-10-05 16:57:54 +08:00
committed by GitHub
parent 6759bee56b
commit d973e8f6e0
7 changed files with 89 additions and 95 deletions

View File

@ -13,6 +13,7 @@ Org-roam also now does not resolve symlinks. This significantly speeds up cache
### Features
- [#1163](https://github.com/org-roam/org-roam/pull/1163) Support file-level IDs introduced in Org 9.4
- [#1093](https://github.com/org-roam/org-roam/pull/1093) Add `vanilla` org-roam-tag-source to extract buffer Org tags
- [#1079](https://github.com/org-roam/org-roam/pull/1079) Add `org-roam-tag-face` to customize appearance of tags in interactive commandsg
- [#1073](https://github.com/org-roam/org-roam/pull/1073) Rename file on title change, when `org-roam-rename-file-on-title-change` is non-nil.

View File

@ -53,7 +53,7 @@
(declare-function org-roam-backlinks-mode "org-roam")
(declare-function org-roam-mode "org-roam")
(declare-function org-roam--find-file "org-roam")
(declare-function org-roam--format-link "org-roam")
(declare-function org-roam-format-link "org-roam")
(declare-function org-roam-link-make-string "org-roam-compat")
(declare-function org-roam-link-get-path "org-roam-link")
@ -161,7 +161,7 @@ ORIG-PATH is the path where the CONTENT originated."
(let ((file-from (car group))
(bls (cdr group)))
(insert (format "** %s\n"
(org-roam--format-link file-from
(org-roam-format-link file-from
(org-roam--get-title-or-slug file-from)
"file")))
(dolist (backlink bls)
@ -189,7 +189,7 @@ ORIG-PATH is the path where the CONTENT originated."
(bls (mapcar (lambda (row)
(nth 2 row)) (cdr group))))
(insert (format "** %s\n"
(org-roam--format-link file-from
(org-roam-format-link file-from
(org-roam--get-title-or-slug file-from)
"file")))
;; Sort backlinks according to time of occurrence in buffer

View File

@ -45,7 +45,7 @@
(declare-function org-roam--get-ref-path-completions "org-roam")
(declare-function org-roam--file-path-from-id "org-roam")
(declare-function org-roam--find-file "org-roam")
(declare-function org-roam--format-link "org-roam")
(declare-function org-roam-format-link "org-roam")
(declare-function org-roam-mode "org-roam")
(declare-function org-roam-completion--completing-read "org-roam-completion")
@ -337,9 +337,9 @@ the capture)."
(type (org-roam-capture--get :link-type))
(desc (org-roam-capture--get :link-description)))
(if (eq (point) (marker-position mkr))
(insert (org-roam--format-link path desc type))
(insert (org-roam-format-link path desc type))
(org-with-point-at mkr
(insert (org-roam--format-link path desc type))))))))))
(insert (org-roam-format-link path desc type))))))))))
(when region
(set-marker beg nil)
(set-marker end nil))

View File

@ -47,7 +47,7 @@
(declare-function org-roam--extract-titles "org-roam")
(declare-function org-roam--extract-ref "org-roam")
(declare-function org-roam--extract-tags "org-roam")
(declare-function org-roam--extract-headlines "org-roam")
(declare-function org-roam--extract-ids "org-roam")
(declare-function org-roam--extract-links "org-roam")
(declare-function org-roam--list-all-files "org-roam")
(declare-function org-roam--path-to-slug "org-roam")
@ -79,7 +79,7 @@ value like `most-positive-fixnum'."
:type 'int
:group 'org-roam)
(defconst org-roam-db--version 8)
(defconst org-roam-db--version 9)
(defvar org-roam-db--connection (make-hash-table :test #'equal)
"Database connection to Org-roam database.")
@ -140,9 +140,10 @@ SQL can be either the emacsql vector representation, or a string."
(hash :not-null)
(meta :not-null)])
(headlines
(ids
[(id :unique :primary-key)
(file :not-null)])
(file :not-null)
(level :not-null)])
(links
[(from :not-null)
@ -249,25 +250,25 @@ This is equivalent to removing the node from the graph."
(mapcar (lambda (title)
(vector file title)) titles)))
(defun org-roam-db--insert-headlines (headlines)
"Insert HEADLINES into the Org-roam cache.
(defun org-roam-db--insert-ids (ids)
"Insert IDS into the Org-roam cache.
Returns t if the insertion was successful, nil otherwise.
Insertions can fail when there is an ID conflict."
(condition-case nil
(progn
(org-roam-db-query
[:insert :into headlines
[:insert :into ids
:values $v1]
headlines)
ids)
t)
(error
(unless (listp headlines)
(setq headlines (list headlines)))
(unless (listp ids)
(setq ids (list ids)))
(lwarn '(org-roam) :error
(format "Duplicate IDs in %s, one of:\n\n%s\n\nskipping..."
(aref (car headlines) 1)
(aref (car ids) 1)
(string-join (mapcar (lambda (hl)
(aref hl 0)) headlines) "\n")))
(aref hl 0)) ids) "\n")))
nil)))
(defun org-roam-db--insert-tags (file tags)
@ -441,14 +442,14 @@ connections, nil is returned."
(when-let ((links (org-roam--extract-links)))
(org-roam-db--insert-links links))))
(defun org-roam-db--update-headlines ()
"Update the file headlines of the current buffer into the cache."
(defun org-roam-db--update-ids ()
"Update the ids of the current buffer into the cache."
(let* ((file (or org-roam-file-name (buffer-file-name))))
(org-roam-db-query [:delete :from headlines
(org-roam-db-query [:delete :from ids
:where (= file $s1)]
file)
(when-let ((headlines (org-roam--extract-headlines file)))
(org-roam-db--insert-headlines headlines))))
(when-let ((ids (org-roam--extract-ids file)))
(org-roam-db--insert-ids ids))))
(defun org-roam-db--update-file (&optional file-path)
"Update Org-roam cache for FILE-PATH.
@ -469,7 +470,7 @@ If the file exists, update the cache with information."
(org-roam-db--update-titles)
(org-roam-db--update-refs)
(when org-roam-enable-headline-linking
(org-roam-db--update-headlines))
(org-roam-db--update-ids))
(org-roam-db--update-links)))))
(defun org-roam-db-build-cache (&optional force)
@ -484,16 +485,18 @@ If FORCE, force a rebuild of the cache from scratch."
(org-roam-files (org-roam--list-all-files))
(current-files (org-roam-db--get-current-files))
(file-count 0)
(headline-count 0)
(id-count 0)
(link-count 0)
(tag-count 0)
(title-count 0)
(ref-count 0)
(deleted-count 0))
(deleted-count 0)
(processed-count 0))
(emacsql-with-transaction (org-roam-db)
;; Two-step building
;; First step: Rebuild files and headlines
;; First step: Rebuild files and ids
(dolist (file org-roam-files)
(org-roam-message "Processed %s/%s files..." processed-count (length org-roam-files))
(let* ((attr (file-attributes file))
(atime (file-attribute-access-time attr))
(mtime (file-attribute-modification-time attr)))
@ -509,47 +512,42 @@ If FORCE, force a rebuild of the cache from scratch."
(vector file contents-hash (list :atime atime :mtime mtime)))
(setq file-count (1+ file-count))
(when org-roam-enable-headline-linking
(when-let ((headlines (org-roam--extract-headlines file)))
(when (org-roam-db--insert-headlines headlines)
(setq headline-count (1+ headline-count))))))
(when-let ((ids (org-roam--extract-ids file)))
(when (org-roam-db--insert-ids ids)
(setq id-count (+ id-count (length ids))))))
(when-let (links (org-roam--extract-links file))
(org-roam-db-query
[:insert :into links
:values $v1]
links)
(setq link-count (1+ link-count)))
(when-let (tags (org-roam--extract-tags file))
(org-roam-db-query
[:insert :into tags
:values $v1]
(vector file tags))
(setq tag-count (1+ tag-count)))
(let ((titles (or (org-roam--extract-titles)
(list (org-roam--path-to-slug file)))))
(org-roam-db--insert-titles file titles)
(setq title-count (+ title-count (length titles))))
(when-let* ((ref (org-roam--extract-ref)))
(when (org-roam-db--insert-ref file ref)
(setq ref-count (1+ ref-count)))))
(file-error
(setq org-roam-files (remove file org-roam-files))
(org-roam-db--clear-file file)
(lwarn '(org-roam) :warning
"Skipping unreadable file while building cache: %s" file)))))))
;; Second step: Rebuild the rest
(dolist (file org-roam-files)
(let ((contents-hash (org-roam-db--file-hash file)))
(unless (string= (gethash file current-files)
contents-hash)
(org-roam--with-temp-buffer file
(when-let (links (org-roam--extract-links file))
(org-roam-db-query
[:insert :into links
:values $v1]
links)
(setq link-count (1+ link-count)))
(when-let (tags (org-roam--extract-tags file))
(org-roam-db-query
[:insert :into tags
:values $v1]
(vector file tags))
(setq tag-count (1+ tag-count)))
(let ((titles (or (org-roam--extract-titles)
(list (org-roam--path-to-slug file)))))
(org-roam-db--insert-titles file titles)
(setq title-count (+ title-count (length titles))))
(when-let* ((ref (org-roam--extract-ref)))
(when (org-roam-db--insert-ref file ref)
(setq ref-count (1+ ref-count))))))
(remhash file current-files)))
"Skipping unreadable file while building cache: %s" file))))
(remhash file current-files)
(setq processed-count (+ processed-count 1)))))
(dolist (file (hash-table-keys current-files))
;; These files are no longer around, remove from cache...
(org-roam-db--clear-file file)
(setq deleted-count (1+ deleted-count))))
(org-roam-message "files: Δ%s, headlines: Δ%s, links: Δ%s, tags: Δ%s, titles: Δ%s, refs: Δ%s, deleted: Δ%s"
(org-roam-message "files: Δ%s, ids: Δ%s, links: Δ%s, tags: Δ%s, titles: Δ%s, refs: Δ%s, deleted: Δ%s"
file-count
headline-count
id-count
link-count
tag-count
title-count

View File

@ -41,7 +41,7 @@
(defvar org-roam-directory)
(declare-function org-roam--find-file "org-roam")
(declare-function org-roam-find-file "org-roam")
(declare-function org-roam-format-link "org-roam")
(defcustom org-roam-link-auto-replace t
"When non-nil, replace Org-roam's roam links with file or id links whenever possible."
@ -236,9 +236,7 @@ DESC is the link description."
(unless (org-in-regexp org-link-bracket-re 1)
(user-error "No link at point"))
(replace-match "")
(when (string-equal link-type "file")
(setq loc (org-roam-link-get-path loc)))
(insert (org-roam-link-make-string (concat link-type ":" loc) desc)))))
(insert (org-roam-format-link loc desc link-type)))))
(defun org-roam-link-replace-all ()
"Replace all roam links in the current buffer."

View File

@ -380,15 +380,6 @@ If FILE is not specified, use the current buffer's file-path."
(string-match-p org-roam-file-exclude-regexp path)))
(f-descendant-of-p path (expand-file-name org-roam-directory))))))
(defun org-roam--org-roam-headline-p (&optional id)
"Return t if ID is part of Org-roam system, nil otherwise.
If ID is not specified, use the ID of the entry at point."
(if-let ((id (or id
(org-id-get))))
(org-roam-db-query [:select [file] :from headlines
:where (= id $s1)]
id)))
(defun org-roam--shell-command-files (cmd)
"Run CMD in the shell and return a list of files. If no files are found, an empty list is returned."
(--> cmd
@ -625,20 +616,21 @@ it as FILE-PATH."
(push (vector file-path name type properties) links))))))
links)))
(defun org-roam--extract-headlines (&optional file-path)
"Extract all headlines with IDs within the current buffer.
(defun org-roam--extract-ids (&optional file-path)
"Extract all IDs within the current buffer.
If FILE-PATH is nil, use the current file."
(let ((file-path (or file-path
(buffer-file-name))))
;; Use `org-map-region' instead of `org-map-entries' as the latter
;; would require another step to remove all nil values.
(let ((result nil))
(setq file-path (or file-path org-roam-file-name (buffer-file-name)))
(let (result)
;; We need to handle the special case of the file property drawer (at outline level 0)
(org-with-point-at (point-min)
(when-let ((id (org-entry-get nil "ID")))
(push (vector id file-path (org-outline-level)) result)))
(org-map-region
(lambda ()
(when-let ((id (org-entry-get nil "ID")))
(push (vector id file-path) result)))
(push (vector id file-path (org-outline-level)) result)))
(point-min) (point-max))
result)))
result))
(defun org-roam--extract-titles-title ()
"Return title from \"#+title\" of the current buffer."
@ -817,10 +809,18 @@ If `org-roam-link-title-format title' is defined, use it with TYPE."
(funcall org-roam-link-title-format title type)
(format org-roam-link-title-format title)))
(defun org-roam--format-link (target &optional description type)
(defun org-roam-format-link (target &optional description type)
"Formats an org link for a given file TARGET, link DESCRIPTION and link TYPE.
TYPE defaults to \"file\"."
TYPE defaults to \"file\".
Here, we also check if there is an ID for the file."
(setq type (or type "file"))
(when-let ((id (and (string-equal type "file")
(caar (org-roam-db-query [:select [id] :from ids
:where (= file $s1)
:and (= level 0)
:limit 1]
target)))))
(setq type "id" target id))
(when (string-equal type "file")
(setq target (org-roam-link-get-path target)))
(org-roam-link-make-string (concat type ":" target) description))
@ -1035,14 +1035,14 @@ citation key, for Org-ref cite links."
"Return the file if ID exists in the Org-roam database.
Return nil otherwise."
(caar (org-roam-db-query [:select [file]
:from headlines
:from ids
:where (= id $s1)
:limit 1]
id)))
(defun org-roam-id-find (id &optional markerp strict keep-buffer-p)
"Return the location of the entry with the id ID.
When MARKERP is non-nil, return a marker pointing to theheadline.
When MARKERP is non-nil, return a marker pointing to the headline.
Otherwise, return a cons formatted as \(file . pos).
When STRICT is non-nil, only consider Org-roams database.
When KEEP-BUFFER-P is non-nil, keep the buffers navigated by Org-roam open."
@ -1296,10 +1296,7 @@ update with NEW-DESC."
(new-label (if (string-equal label old-desc)
new-desc
label)))
(replace-match (org-roam-link-make-string
(concat type ":"
(file-relative-name new-path (file-name-directory (buffer-file-name))))
new-label)))))))))
(replace-match (org-roam-format-link new-path new-label type)))))))))
(defun org-roam--fix-relative-links (old-path)
"Fix file-relative links in current buffer.
@ -1629,7 +1626,7 @@ If DESCRIPTION is provided, use this as the link label. See
(delete-region beg end)
(set-marker beg nil)
(set-marker end nil))
(insert (org-roam--format-link target-file-path link-description link-type)))
(insert (org-roam-format-link target-file-path link-description link-type)))
(t
(let ((org-roam-capture--info `((title . ,title-with-tags)
(slug . ,(funcall org-roam-title-to-slug-function title-with-tags))))

View File

@ -239,7 +239,7 @@
:to-equal
'("t1" "t2 with space" "t3" "tags"))))))
(describe "Headline extraction"
(describe "ID extraction"
(before-all
(test-org-roam--init))
@ -252,12 +252,12 @@
(buf (find-file-noselect fname)))
(with-current-buffer buf
(funcall fn fname)))))
(it "extracts headlines"
(expect (test #'org-roam--extract-headlines
(it "extracts ids"
(expect (test #'org-roam--extract-ids
"headlines/headline.org")
:to-have-same-items-as
`(["e84d0630-efad-4017-9059-5ef917908823" ,(test-org-roam--abs-path "headlines/headline.org")]
["801b58eb-97e2-435f-a33e-ff59a2f0c213" ,(test-org-roam--abs-path "headlines/headline.org")])))))
`(["e84d0630-efad-4017-9059-5ef917908823" ,(test-org-roam--abs-path "headlines/headline.org") 1]
["801b58eb-97e2-435f-a33e-ff59a2f0c213" ,(test-org-roam--abs-path "headlines/headline.org") 1])))))
(describe "Test roam links"
(it ""