branch: externals/denote commit ad1537d22a5c6c2cc8967b890e2044c57ead08f9 Merge: 7c73cbbd52 f704b49df4 Author: Protesilaos Stavrou <i...@protesilaos.com> Commit: GitHub <nore...@github.com>
Merge pull request #624 from jeanphilippegg/custom-identifiers Custom identifier formats --- denote.el | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 92 insertions(+), 16 deletions(-) diff --git a/denote.el b/denote.el index e5fe6486e0..2132b4e1ea 100644 --- a/denote.el +++ b/denote.el @@ -2181,6 +2181,7 @@ Consult the `denote-file-types' for how this is used." :date-key-regexp "^#\\+date\\s-*:" :date-value-function denote-date-org-timestamp :date-value-reverse-function denote-extract-date-from-front-matter + :link-retrieval-format "[denote:%VALUE%]" :link denote-org-link-format :link-in-context-regexp denote-org-link-in-context-regexp) (markdown-yaml @@ -2201,6 +2202,7 @@ Consult the `denote-file-types' for how this is used." :date-key-regexp "^date\\s-*:" :date-value-function denote-date-rfc3339 :date-value-reverse-function denote-extract-date-from-front-matter + :link-retrieval-format "(denote:%VALUE%)" :link denote-md-link-format :link-in-context-regexp denote-md-link-in-context-regexp) (markdown-toml @@ -2221,6 +2223,7 @@ Consult the `denote-file-types' for how this is used." :date-key-regexp "^date\\s-*=" :date-value-function denote-date-rfc3339 :date-value-reverse-function denote-extract-date-from-front-matter + :link-retrieval-format "(denote:%VALUE%)" :link denote-md-link-format :link-in-context-regexp denote-md-link-in-context-regexp) (text @@ -2241,6 +2244,7 @@ Consult the `denote-file-types' for how this is used." :date-key-regexp "^date\\s-*:" :date-value-function denote-date-iso-8601 :date-value-reverse-function denote-extract-date-from-front-matter + :link-retrieval-format "[denote:%VALUE%]" :link denote-org-link-format :link-in-context-regexp denote-org-link-in-context-regexp)) "Alist of variable `denote-file-type' and their format properties. @@ -2289,6 +2293,9 @@ PROPERTY-LIST is a plist that consists of the following elements: retrieve the keywords' value from the front matter. It performs the reverse of the `:keywords-value-function'. +- `:link-retrieval-format' is a string, or variable holding a string, + that specifies the retrieval format of a link. + - `:link' is a string, or variable holding a string, that specifies the format of a link. See the variables `denote-org-link-format', `denote-md-link-format'. @@ -2410,6 +2417,15 @@ this list for new note creation. The default is `org'.") (alist-get file-type denote-file-types) :date-value-reverse-function)) +(defun denote--link-retrieval-format (file-type) + "Return link retrieval format based on FILE-TYPE." + (let ((prop (plist-get + (alist-get file-type denote-file-types) + :link-retrieval-format))) + (if (symbolp prop) + (symbol-value prop) + prop))) + (defun denote--link-format (file-type) "Return link format extension based on FILE-TYPE." (let ((prop (plist-get @@ -2727,6 +2743,66 @@ If FILES is not given, use all text files as returned by (mapcar (lambda (x) (assoc x data)) files-sorted) data))) +(defun denote--get-files-by-file-type (files) + "Return hash table of FILES by file type." + (let ((file-type-hash-table (make-hash-table)) + (file-types (denote--file-type-keys))) + (dolist (file-type file-types) + (puthash file-type '() file-type-hash-table)) + (dolist (file files) + (when-let* ((file-type (denote-file-type file))) + (push file (gethash file-type file-type-hash-table)))) + file-type-hash-table)) + +(defun denote--get-all-backlinks (files) + "Return hash table of all backlinks in FILES by identifier." + (let ((links-hash-table (make-hash-table :test 'equal)) + (file-types (denote--file-type-keys)) + (files-by-file-type (denote--get-files-by-file-type files))) + (dolist (file-type file-types) + (let* ((file-type-files (gethash file-type files-by-file-type)) + (regexp (denote--link-in-context-regexp file-type))) + (dolist (file file-type-files) + (let* ((file-identifiers + (with-temp-buffer + (insert-file-contents file) + (denote-link--collect-identifiers regexp)))) + (dolist (file-identifier file-identifiers) + (if-let* ((links (gethash file-identifier links-hash-table))) + (puthash file-identifier (push file links) links-hash-table) + (puthash file-identifier (list file) links-hash-table))))))) + links-hash-table)) + +(defun denote-retrieve-xref-alist-for-backlinks (identifier) + "Return xref alist of absolute file paths of matches for IDENTIFIER." + (let* ((files (denote-directory-files)) + (file-types (denote--file-type-keys)) + (xref-file-name-display 'abs) + (xref-matches '())) + (when-let* ((backlinks (gethash identifier (denote--get-all-backlinks files)))) + (let* ((backlinks-by-file-type (denote--get-files-by-file-type backlinks))) + (dolist (file-type file-types) + (when-let* ((current-backlinks (gethash file-type backlinks-by-file-type)) + (format-parts (string-split + (denote--link-retrieval-format file-type) + "%VALUE%")) ; Should give two parts + (query-simple (concat + (regexp-quote (nth 0 format-parts)) + (regexp-quote identifier) + (regexp-quote (nth 1 format-parts)))) + (query-org-link (concat + (regexp-quote (nth 0 format-parts)) + (regexp-quote identifier) + "::"))) + (setq xref-matches (append xref-matches (xref-matches-in-files query-simple current-backlinks))) + (setq xref-matches (append xref-matches (xref-matches-in-files query-org-link current-backlinks)))))) + (let ((data (xref--analyze xref-matches))) + (if-let* ((sort denote-query-sorting) + (files-matched (mapcar #'car data)) + (files-sorted (denote-sort-files files-matched sort))) + (mapcar (lambda (x) (assoc x data)) files-sorted) + data))))) + ;;;; New note ;;;;; Common helpers for new notes @@ -3274,10 +3350,6 @@ instead of that of the parameter." template (or (alist-get template denote-templates) ""))) (signature (or signature ""))) - ;; TODO: Remove this when ready to allow custom identifiers. - (unless (and (string-match-p denote-date-identifier-regexp identifier) - (date-to-time identifier)) - (user-error "The identifier must have the format of `denote-date-identifier-format'")) (list title keywords file-type directory date identifier template signature))) ;;;###autoload @@ -4159,10 +4231,6 @@ Respect `denote-rename-confirmations', `denote-save-buffers' and (t (funcall denote-get-identifier-function identifier nil)))) (new-name (denote-format-file-name directory identifier keywords title extension signature)) (max-mini-window-height denote-rename-max-mini-window-height)) - ;; TODO: Remove this when ready to allow custom identifiers. - (unless (and (string-match-p denote-date-identifier-regexp identifier) - (date-to-time identifier)) - (user-error "The identifier must have the format of `denote-date-identifier-format'")) (when (and (file-regular-p new-name) (not (string= (expand-file-name file) (expand-file-name new-name)))) (user-error "The destination file `%s' already exists" new-name)) @@ -5632,6 +5700,16 @@ alist, such as `denote-backlinks-display-buffer-action'." (denote--display-buffer-from-xref-alist xref-alist buffer-name display-buffer-action))))) (display-buffer buffer-name display-buffer-action))) +(defun denote-make-backlinks-buffer (identifier buffer-name display-buffer-action) + "Create links' buffer called BUFFER-NAME for IDENTIFIER. + +Optional DISPLAY-BUFFER-ACTION is a `display-buffer' action and +concomitant alist, such as `denote-backlinks-display-buffer-action'." + (let* ((xref-alist (denote-retrieve-xref-alist-for-backlinks identifier))) + (unless xref-alist + (error "No matches for identifier `%s'" identifier)) + (denote--display-buffer-from-xref-alist xref-alist buffer-name display-buffer-action))) + ;; NOTE 2025-03-24: The `&rest' is there because we used to have an ;; extra SHOW-CONTEXT parameter. This way we do not break anybody's ;; code, even if we slightly modify the behaviour. @@ -5925,19 +6003,17 @@ use the ID." (defun denote-backlinks () "Produce a buffer with backlinks to the current note. -Show the names of files linking to the current file. Include the -context of each link if the user option `denote-backlinks-show-context' -is non-nil. +Show the names of files linking to the current file. Place the buffer below the current window or wherever the user option `denote-backlinks-display-buffer-action' specifies." (interactive) (if-let* ((file buffer-file-name)) (if-let* ((identifier (denote-retrieve-filename-identifier file))) - (funcall denote-query-links-buffer-function - identifier nil - (denote--backlinks-get-buffer-name file identifier) - denote-backlinks-display-buffer-action) + (denote-make-backlinks-buffer + identifier + (denote--backlinks-get-buffer-name file identifier) + denote-backlinks-display-buffer-action) (user-error "The current file does not have a Denote identifier")) (user-error "Buffer `%s' is not associated with a file" (current-buffer)))) @@ -5955,7 +6031,7 @@ Also see `denote-get-links'." (when-let* ((current-file (or file (buffer-file-name))) (id (or (denote-retrieve-filename-identifier current-file) (user-error "The file does not have a Denote identifier")))) - (delete current-file (denote-retrieve-files-xref-query id)))) + (mapcar #'car (denote-retrieve-xref-alist-for-backlinks id)))) ;; TODO 2024-09-04: Instead of using `denote-get-backlinks' we ;; should have a function that does not try to find all backlinks but