Using org-roam-ql with consult for filtering nodes

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.

temp

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 :sweat_smile:

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.

2 Likes