Skip to content

Instantly share code, notes, and snippets.

@dmgerman
Last active May 28, 2024 21:07
Show Gist options
  • Save dmgerman/fbf8e657918f6bd76d0bda2a396616c1 to your computer and use it in GitHub Desktop.
Save dmgerman/fbf8e657918f6bd76d0bda2a396616c1 to your computer and use it in GitHub Desktop.
Retrieve Fields of a node for a template

formatting fields for presentation

  • The way the nodes are processed for the template is very slow.
  • To test how slow, I replaced the code and returned the value of the field without any extra manipulation

    See below

  • With these changes, the take home:
    • for my database
      • org-roam-node-list takes 1/3 of the time,
      • 2/3 is the formatting. most inside org-roam-format-template–replace (see below)

if we would want to make this code, most of this postprocessing should be done by the DBMS (faster, and no emacs gc)

Version one, refactored , same functionality

  • removed large lambdas, I suspect they generate a lot of garbage
  • this already has some improvement
  • it is easier to profile this code, since now the lambda has a name
(defun org-roam-node--retrieve-field (node field _default-val)
       (pcase-let* ((`(,field-name ,field-width) (split-string field ":"))
                    (getter (intern (concat "org-roam-node-" field-name)))
                    (field-value (funcall getter node)))
         ;; if the field-name is file, replace it with its
         ;; relative name
         (when (and (equal field-name "file")
                    field-value)
           (setq field-value (file-relative-name field-value org-roam-directory)))
         ;; if field name is olp
         ;; append " > "
         (when (and (equal field-name "olp")
                    field-value)
           (setq field-value (string-join field-value " > ")))
         ;; if field value is not a list, make it one
         (when (and field-value (not (listp field-value)))
           (setq field-value (list field-value)))
         ;; ????
         (setq field-value (mapconcat
                            (lambda (v)
                              (concat (or (cdr (assoc field-name org-roam-node-template-prefixes))
                                          "")
                                      v))
                            field-value " "))
         ;; set the width of the field
         (setq field-width (cond
                            ((not field-width)
                             field-width)
                            ((string-equal field-width "*")
                             (if width
                                 (- width tmpl-width)
                               tmpl-width))
                            ((>= (string-to-number field-width) 0)
                             (string-to-number field-width))))
         (when field-width
           (let* ((truncated (truncate-string-to-width field-value field-width 0 ?\s))
                  (tlen (length truncated))
                  (len (length field-value)))
             (if (< tlen len)
                 ;; Make the truncated part of the string invisible. If strings
                 ;; are pre-propertized with display or invisible properties, the
                 ;; formatting may get messed up. Ideally, truncated strings are
                 ;; not preformatted with these properties. Face properties are
                 ;; allowed without restriction.
                 (put-text-property tlen len 'invisible t field-value)
               ;; If the string wasn't truncated, but padded, use this string instead.
               (setq field-value truncated))))
         field-value))


(defun org-roam-node--format-entry (template node &optional width)
  "Formats NODE for display in the results list.
WIDTH is the width of the results list.
TEMPLATE is the processed template used to format the entry."
  (pcase-let ((`(,tmpl . ,tmpl-width) template))
    (org-roam-format-template
     tmpl
     (lambda (field _default-val)
       (org-roam-node--retrieve-field node field _default-val)
       )
     )
    )
  )

(defun org-roam-format-template--replace (replacer md var replacer-match-data)
  (let (;(var (match-string 1 md))
        ;(replacer-match-data (match-data))
        default-val)
    (when (string-match "\\(.+\\)=\\(.+\\)" var)
      (setq default-val (match-string 2 var)
            var (match-string 1 var)))
    (unwind-protect
        (let ((v (progn
                   (set-match-data saved-match-data)
                   (funcall replacer var default-val))))
          (if v
              (format (apply #'propertize "%s" (text-properties-at 0 var)) v)
            (signal 'org-roam-format-resolve md)))
      (set-match-data replacer-match-data)))
  )


(defun org-roam-format-template (template replacer)
  "Format TEMPLATE with the function REPLACER.
The templates are of form ${foo} for variable foo, and
${foo=default} for variable foo with default value \"default\".
REPLACER takes an argument of the format variable and the default
value (possibly nil). Adapted from `s-format'."
;  (message (format "org-roam-format-template [%S][%S]" template replacer))
  (let ((saved-match-data (match-data)))
    (unwind-protect
        (replace-regexp-in-string
         "\\${\\([^}]+\\)}"
         (lambda (md)
           (org-roam-format-template--replace replacer md (match-string 1 md) (match-data)))
         (if (functionp template)
             (funcall template)
           template)
         ;; Need literal to make sure it works
         t t)
      (set-match-data saved-match-data))))


org-roam-format-template
(cl-loop
 for i from 1 to 10
 collect (benchmark-run 1
            (org-roam-node-read--completions)
           ))
0.41206100.0
0.39788700.0
0.6490810.2558129999999892
0.3927940000000000300.0
0.3973750000000000300.0
0.39678700.0
0.64872410.25631900000001906
0.39380200.0
0.3952710000000000400.0
0.39823600.0

Version two, refactor, very simple replacement

  • remove pcase-let (they are slow)
  • simply retrieve the field, without any formatting
(defun org-roam-node--retrieve-field (node field _default-val)
  (let* (
         (getter (intern (concat "org-roam-node-" field)))
         (field-value (funcall getter node))
         )
    (if (listp field-value)
        (mapconcat 'identity field-value " ")
      field-value)
    )
  )

(defun org-roam-node--format-entry (template node &optional width)
  "Formats NODE for display in the results list.
WIDTH is the width of the results list.
TEMPLATE is the processed template used to format the entry."
    (org-roam-format-template
     (car template)
     (lambda (field _default-val)
       (org-roam-node--retrieve-field node field _default-val)
       )
     )
  )

(defun org-roam-format-template--replace (replacer md var replacer-match-data)
  (let (;(var (match-string 1 md))
        ;(replacer-match-data (match-data))
        default-val)
    (when (string-match "\\(.+\\)=\\(.+\\)" var)
      (setq default-val (match-string 2 var)
            var (match-string 1 var)))
    (unwind-protect
        (let ((v (progn
                   (set-match-data saved-match-data)
                   (funcall replacer var default-val))))
          (if v
              (format (apply #'propertize "%s" (text-properties-at 0 var)) v)
            (signal 'org-roam-format-resolve md)))
      (set-match-data replacer-match-data)))
  )


(defun org-roam-format-template (template replacer)
  "Format TEMPLATE with the function REPLACER.
The templates are of form ${foo} for variable foo, and
${foo=default} for variable foo with default value \"default\".
REPLACER takes an argument of the format variable and the default
value (possibly nil). Adapted from `s-format'."
;  (message (format "org-roam-format-template [%S][%S]" template replacer))
  (let ((saved-match-data (match-data)))
    (unwind-protect
        (replace-regexp-in-string
         "\\${\\([^}]+\\)}"
         (lambda (md)
           (org-roam-format-template--replace replacer md (match-string 1 md) (match-data)))
         (if (functionp template)
             (funcall template)
           template)
         ;; Need literal to make sure it works
         t t)
      (set-match-data saved-match-data))))



org-roam-format-template
(cl-loop
 for i from 1 to 10
 collect (benchmark-run 1
            (org-roam-node-read--completions)
           ))
0.19571900.0
0.17651100.0
0.1897339999999999900.0
0.4308680000000000310.25443200000000843
0.17517600.0
0.17487100.0
0.17922100.0
0.17734600.0
0.17942200.0
0.43454510.25984799999997676
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment