I have been playing around with integrating org-roam-ql with consult to replace the org-roam find functions. I am using consult’s dynamic functions feature to filter the results when searching. Here’s a quick video of what I have working now. I’ve been using this with my personal db (3k+ nodes) for a while now.
To provide context:
- I have an org-roam-ql saved-query (
fam
- in the image above) which allows me to quickly filter with queries I use often. - This also has the same completion-at-point function in org-roam-ql (org-roam-ql/images/demo6.gif at master · ahmed-shariff/org-roam-ql · GitHub) configured, which is handy.
- I don’t use the org-roam’ templating for completion candidates. Instead, I have a custom marginalia annotator. I use orderless’s annotation search when I want to further query on the annotations.
- I prefer “;” as the default value for consult’s perl split style
The consult-org-roam-ql function:
(defvar consult-org-roam-ql--history nil)
;; TODO: filter-fn and sort-fn does notthing now!
(defun consult-org-roam-ql (&optional initial-input filter-fn sort-fn
require-match prompt)
"Consult with org-roam-ql for searching/narrowing."
(interactive)
(minibuffer-with-setup-hook
(lambda ()
;; KLUDGE: No idea why this is here!
(set-syntax-table emacs-lisp-mode-syntax-table)
(add-hook 'completion-at-point-functions
#'org-roam-ql--completion-at-point nil t))
(let* ((style (consult--async-split-style))
(fn (plist-get style :function))
(corfu-auto nil)
split-pos
mb-str
;; Override how the empty string is handled!
;; When empty async-str should return default candidates
(split (lambda (str)
(pcase-let* ((res (funcall fn str style))
(`(,async-str ,pos ,force . ,_) res))
;; This gets called at severaal places. We only want the data when it is
;; called with the force value!
(when force
(setq split-pos pos
mb-str str))
(when (and force (equal "" async-str))
(setf (car res) "-"))
res)))
;; Default candidates
(nodes (mapcar (lambda (node)
(cons (propertize (org-roam-node-title node) 'node node) node))
(org-roam-node-list)))
;; The sink is what holds the candidates and feed it back to all-completions
(sink (consult--async-sink))
(overriden-keymap (make-sparse-keymap))
(delete-minibuffer-override
(lambda ()
(interactive)
(when (and mb-str split-pos)
(delete-minibuffer-contents)
(insert (substring mb-str 0 split-pos))))))
(define-key overriden-keymap "\M-d" delete-minibuffer-override)
(define-key overriden-keymap (kbd "C-,") (lambda ()
(interactive)
(when (minibufferp)
(embark-select)
(funcall delete-minibuffer-override))))
(when (not (featurep 'org-roam-ql))
(require 'org-roam-ql))
(set-keymap-parent overriden-keymap org-roam-ql--read-query-map)
;; Feeding initial set of candidates to sink
(funcall sink nodes)
(-->
(consult--dynamic-compute
sink
(lambda (input)
(if (and (> (length input) 0) (not (equal input "-")))
;; TODO: can I update the state/indicator somehow?
(condition-case err
(mapcar
(lambda (node)
(cons (propertize (org-roam-node-title node) 'node node) node))
(org-roam-ql-nodes (read input)))
(user-error
(minibuffer-message (propertize (cadr err) 'face 'consult-async-failed))
nodes))
nodes)))
(consult--async-throttle it)
(consult--async-split it split)
(consult--read
it
:prompt (or prompt "Node: ")
:initial (if initial-input initial-input ";")
:keymap overriden-keymap
:category 'org-roam-node
:sort nil ;; TODO
:require-match require-match
:state (consult-org-roam--node-preview)
:history 'consult-org-roam-ql--history
;; Taken from consult-org-roam
;; Uses the DEFAULT argument of alist-get to return input in case the input is not found as key.
:lookup (lambda (selected candidates input narrow) (alist-get selected candidates input nil #'equal)))
(if (org-roam-node-p it)
it
(org-roam-node-create :title it))))))
(advice-add #'consult-org-roam-node-read :override #'consult-org-roam-ql)
I am still constantly improving this, any thoughts and feedback is most welcome.