(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 ### 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 - [#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 - [#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. - [#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-backlinks-mode "org-roam")
(declare-function org-roam-mode "org-roam") (declare-function org-roam-mode "org-roam")
(declare-function org-roam--find-file "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-make-string "org-roam-compat")
(declare-function org-roam-link-get-path "org-roam-link") (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)) (let ((file-from (car group))
(bls (cdr group))) (bls (cdr group)))
(insert (format "** %s\n" (insert (format "** %s\n"
(org-roam--format-link file-from (org-roam-format-link file-from
(org-roam--get-title-or-slug file-from) (org-roam--get-title-or-slug file-from)
"file"))) "file")))
(dolist (backlink bls) (dolist (backlink bls)
@ -189,7 +189,7 @@ ORIG-PATH is the path where the CONTENT originated."
(bls (mapcar (lambda (row) (bls (mapcar (lambda (row)
(nth 2 row)) (cdr group)))) (nth 2 row)) (cdr group))))
(insert (format "** %s\n" (insert (format "** %s\n"
(org-roam--format-link file-from (org-roam-format-link file-from
(org-roam--get-title-or-slug file-from) (org-roam--get-title-or-slug file-from)
"file"))) "file")))
;; Sort backlinks according to time of occurrence in buffer ;; 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--get-ref-path-completions "org-roam")
(declare-function org-roam--file-path-from-id "org-roam") (declare-function org-roam--file-path-from-id "org-roam")
(declare-function org-roam--find-file "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-mode "org-roam")
(declare-function org-roam-completion--completing-read "org-roam-completion") (declare-function org-roam-completion--completing-read "org-roam-completion")
@ -337,9 +337,9 @@ the capture)."
(type (org-roam-capture--get :link-type)) (type (org-roam-capture--get :link-type))
(desc (org-roam-capture--get :link-description))) (desc (org-roam-capture--get :link-description)))
(if (eq (point) (marker-position mkr)) (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 (org-with-point-at mkr
(insert (org-roam--format-link path desc type)))))))))) (insert (org-roam-format-link path desc type))))))))))
(when region (when region
(set-marker beg nil) (set-marker beg nil)
(set-marker end nil)) (set-marker end nil))

View File

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

View File

@ -41,7 +41,7 @@
(defvar org-roam-directory) (defvar org-roam-directory)
(declare-function org-roam--find-file "org-roam") (declare-function org-roam--find-file "org-roam")
(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 (defcustom org-roam-link-auto-replace t
"When non-nil, replace Org-roam's roam links with file or id links whenever possible." "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) (unless (org-in-regexp org-link-bracket-re 1)
(user-error "No link at point")) (user-error "No link at point"))
(replace-match "") (replace-match "")
(when (string-equal link-type "file") (insert (org-roam-format-link loc desc link-type)))))
(setq loc (org-roam-link-get-path loc)))
(insert (org-roam-link-make-string (concat link-type ":" loc) desc)))))
(defun org-roam-link-replace-all () (defun org-roam-link-replace-all ()
"Replace all roam links in the current buffer." "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))) (string-match-p org-roam-file-exclude-regexp path)))
(f-descendant-of-p path (expand-file-name org-roam-directory)))))) (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) (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." "Run CMD in the shell and return a list of files. If no files are found, an empty list is returned."
(--> cmd (--> cmd
@ -625,20 +616,21 @@ it as FILE-PATH."
(push (vector file-path name type properties) links)))))) (push (vector file-path name type properties) links))))))
links))) links)))
(defun org-roam--extract-headlines (&optional file-path) (defun org-roam--extract-ids (&optional file-path)
"Extract all headlines with IDs within the current buffer. "Extract all IDs within the current buffer.
If FILE-PATH is nil, use the current file." If FILE-PATH is nil, use the current file."
(let ((file-path (or file-path (setq file-path (or file-path org-roam-file-name (buffer-file-name)))
(buffer-file-name)))) (let (result)
;; Use `org-map-region' instead of `org-map-entries' as the latter ;; We need to handle the special case of the file property drawer (at outline level 0)
;; would require another step to remove all nil values. (org-with-point-at (point-min)
(let ((result nil)) (when-let ((id (org-entry-get nil "ID")))
(push (vector id file-path (org-outline-level)) result)))
(org-map-region (org-map-region
(lambda () (lambda ()
(when-let ((id (org-entry-get nil "ID"))) (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)) (point-min) (point-max))
result))) result))
(defun org-roam--extract-titles-title () (defun org-roam--extract-titles-title ()
"Return title from \"#+title\" of the current buffer." "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) (funcall org-roam-link-title-format title type)
(format org-roam-link-title-format title))) (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. "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")) (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") (when (string-equal type "file")
(setq target (org-roam-link-get-path target))) (setq target (org-roam-link-get-path target)))
(org-roam-link-make-string (concat type ":" target) description)) (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 the file if ID exists in the Org-roam database.
Return nil otherwise." Return nil otherwise."
(caar (org-roam-db-query [:select [file] (caar (org-roam-db-query [:select [file]
:from headlines :from ids
:where (= id $s1) :where (= id $s1)
:limit 1] :limit 1]
id))) id)))
(defun org-roam-id-find (id &optional markerp strict keep-buffer-p) (defun org-roam-id-find (id &optional markerp strict keep-buffer-p)
"Return the location of the entry with the id ID. "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). Otherwise, return a cons formatted as \(file . pos).
When STRICT is non-nil, only consider Org-roams database. 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." 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-label (if (string-equal label old-desc)
new-desc new-desc
label))) label)))
(replace-match (org-roam-link-make-string (replace-match (org-roam-format-link new-path new-label type)))))))))
(concat type ":"
(file-relative-name new-path (file-name-directory (buffer-file-name))))
new-label)))))))))
(defun org-roam--fix-relative-links (old-path) (defun org-roam--fix-relative-links (old-path)
"Fix file-relative links in current buffer. "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) (delete-region beg end)
(set-marker beg nil) (set-marker beg nil)
(set-marker end 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 (t
(let ((org-roam-capture--info `((title . ,title-with-tags) (let ((org-roam-capture--info `((title . ,title-with-tags)
(slug . ,(funcall org-roam-title-to-slug-function title-with-tags)))) (slug . ,(funcall org-roam-title-to-slug-function title-with-tags))))

View File

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