refactor: improve speed of retrieving and formatting nodes

The original code uses macros that when expanded use a lot memory
and are relatively slow. This removes some of that processing:

- Add a constructor to org-roam-node that does uses parameters
  by position instead or by name

- Add the option of using a function to format nodes. The current
  code uses a string template. But this template is processes
  for every node. Using a function will further improve
  performance significantly.

Simplified expected return value user's template function.

As suggested by akashpal-21, the return value of the user's template
function can be simpler.

I have refactored the code such that the function only returns
a string (potentially propertized) with the formatted node.

I have also modified the documentation of
org-roam-node-display-template with an example of the user defined
function.

Acknowledgement: akashpal-21 for the suggestion for improvement

Close: #2511
This commit is contained in:
Daniel M German
2025-03-23 14:47:11 -07:00
committed by Dustin Farris
parent 2ff616fbd8
commit fed577f805

View File

@ -39,6 +39,16 @@
;;;; Completing-read ;;;; Completing-read
(defcustom org-roam-node-display-template "${title}" (defcustom org-roam-node-display-template "${title}"
"Configures display formatting for Org-roam node. "Configures display formatting for Org-roam node.
If it is a function, it will be called to format a node.
Its result is expected to be a string (potentially with
embedded properties).
If it is a string and it will be used as described in org-roam
(see org-roam-node-display-template)
When it is a string, the following processing is done:
Patterns of form \"${field-name:length}\" are interpolated based Patterns of form \"${field-name:length}\" are interpolated based
on the current node. on the current node.
@ -64,7 +74,21 @@ as many characters as possible and will be aligned accordingly.
A closure can also be assigned to this variable in which case the A closure can also be assigned to this variable in which case the
closure is evaluated and the return value is used as the closure is evaluated and the return value is used as the
template. The closure must evaluate to a valid template string." template. The closure must evaluate to a valid template string.
When org-roam-node-display-template is a function, the function is
expected to return a string, potentially propertized. For example, the
following function shows the title and base filename of the node:
\(defun my--org-roam-format (node)
\"formats the node\"
(format \"%-40s %s\"
(if (org-roam-node-title node)
(propertize (org-roam-node-title node) 'face 'org-todo)
\"\")
(file-name-nondirectory (org-roam-node-file node))))
\q(setq org-roam-node-display-template 'my--org-roam-format)"
:group 'org-roam :group 'org-roam
:type '(string function)) :type '(string function))
@ -151,6 +175,11 @@ This path is relative to `org-roam-directory'."
;;; Definition ;;; Definition
(cl-defstruct (org-roam-node (:constructor org-roam-node-create) (cl-defstruct (org-roam-node (:constructor org-roam-node-create)
(:constructor org-roam-node-create-from-db
(title aliases ; 2
id file file-title level todo ; 5
point priority scheduled deadline properties ;;5
olp file-atime file-mtime tags refs)) ;;5
(:copier nil)) (:copier nil))
"A heading or top level file with an assigned ID property." "A heading or top level file with an assigned ID property."
file file-title file-hash file-atime file-mtime file file-title file-hash file-atime file-mtime
@ -352,23 +381,27 @@ nodes."
(defun org-roam-node-list () (defun org-roam-node-list ()
"Return all nodes stored in the database as a list of `org-roam-node's." "Return all nodes stored in the database as a list of `org-roam-node's."
(let ((rows (org-roam-db-query (let ((rows (org-roam-db-query
"SELECT "
SELECT
title,
aliases,
id, id,
file, file,
filetitle, filetitle,
\"level\", \"level\",
todo, todo,
pos, pos,
priority , priority ,
scheduled , scheduled ,
deadline , deadline ,
title,
properties , properties ,
olp, olp,
atime, atime,
mtime, mtime,
'(' || group_concat(tags, ' ') || ')' as tags, '(' || group_concat(tags, ' ') || ')' as tags,
aliases,
refs refs
FROM FROM
( (
@ -417,32 +450,20 @@ FROM
LEFT JOIN refs ON refs.node_id = nodes.id LEFT JOIN refs ON refs.node_id = nodes.id
GROUP BY nodes.id, tags.tag, aliases.alias ) GROUP BY nodes.id, tags.tag, aliases.alias )
GROUP BY id, tags ) GROUP BY id, tags )
GROUP BY id"))) GROUP BY id
(cl-loop for row in rows ")))
append (pcase-let* ((`( (mapcan
,id ,file ,file-title ,level ,todo ,pos ,priority ,scheduled ,deadline (lambda (row)
,title ,properties ,olp ,atime ,mtime ,tags ,aliases ,refs) (let (
row) (all-titles (cons (car row) (nth 1 row)))
(all-titles (cons title aliases))) )
(mapcar (lambda (temp-title) (mapcar (lambda (temp-title)
(org-roam-node-create :id id (apply 'org-roam-node-create-from-db (cons temp-title (cdr row))))
:file file all-titles)
:file-title file-title ))
:file-atime atime rows)
:file-mtime mtime )
:level level )
:point pos
:todo todo
:priority priority
:scheduled scheduled
:deadline deadline
:title temp-title
:aliases aliases
:properties properties
:olp olp
:tags tags
:refs refs))
all-titles)))))
;;;; Finders ;;;; Finders
(defun org-roam-node-marker (node) (defun org-roam-node-marker (node)
@ -556,6 +577,25 @@ PROMPT is a string to show at the beginning of the mini-buffer, defaulting to \"
(or (cdr (assoc node nodes)) (or (cdr (assoc node nodes))
(org-roam-node-create :title node)))) (org-roam-node-create :title node))))
(defun org-roam--format-nodes-using-template (nodes)
"Formats NODES using org-roam template features.
Uses org-roam--node-display-template."
(let (
(wTemplate (org-roam-node--process-display-format org-roam-node-display-template))
)
(mapcar (lambda (node)
(org-roam-node-read--to-candidate node wTemplate)) nodes))
)
(defun org-roam--format-nodes-using-function (nodes)
"Formats NODES using the function org-roam-node-display-template."
(mapcar (lambda (node)
(cons
(propertize (funcall org-roam-node-display-template node) 'node node)
node))
nodes)
)
(defun org-roam-node-read--completions (&optional filter-fn sort-fn) (defun org-roam-node-read--completions (&optional filter-fn sort-fn)
"Return an alist for node completion. "Return an alist for node completion.
The car is the displayed title or alias for the node, and the cdr The car is the displayed title or alias for the node, and the cdr
@ -565,15 +605,17 @@ and when nil is returned the node will be filtered out.
SORT-FN is a function to sort nodes. See `org-roam-node-read-sort-by-file-mtime' SORT-FN is a function to sort nodes. See `org-roam-node-read-sort-by-file-mtime'
for an example sort function. for an example sort function.
The displayed title is formatted according to `org-roam-node-display-template'." The displayed title is formatted according to `org-roam-node-display-template'."
(let* ((template (org-roam-node--process-display-format org-roam-node-display-template)) (let* (
(nodes (org-roam-node-list)) (nodes (org-roam-node-list))
(nodes (if filter-fn (nodes (if filter-fn
(cl-remove-if-not (cl-remove-if-not
(lambda (n) (funcall filter-fn n)) (lambda (n) (funcall filter-fn n))
nodes) nodes)
nodes)) nodes))
(nodes (mapcar (lambda (node) (nodes (if (functionp org-roam-node-display-template)
(org-roam-node-read--to-candidate node template)) nodes)) (org-roam--format-nodes-using-function nodes)
(org-roam--format-nodes-using-template nodes)))
(sort-fn (or sort-fn (sort-fn (or sort-fn
(when org-roam-node-default-sort (when org-roam-node-default-sort
(intern (concat "org-roam-node-read-sort-by-" (intern (concat "org-roam-node-read-sort-by-"