Skip to content
Ahmed Shariff edited this page Apr 25, 2025 · 5 revisions

Welcome to the org-roam-ql wiki! Recording a few predicates/expansion functions that can be used.

Ripgrep search content

  (defun ripgrep (pattern directory type)
    (-non-nil
     (--map
      (pcase-let ((`(,file ,line ,match) (s-split ":" (f-relative it directory))))
        `(,(expand-file-name file directory), (string-to-number line) ,match))
      (--filter
       (< 0 (length it))
       (s-split "\n"
                (with-temp-buffer
                  (call-process "rg" nil t "--null"
                                "--line-buffered"
                                "--color=never"
                                "--max-columns=10000"
                                "--max-columns-preview"
                                "--path-separator" "/"
                                "--smart-case"
                                "--no-heading"
                                "--with-filename"
                                "--line-number"
                                "--search-zip"
                                "-j5"
                                (format "-t%s" type)
                                pattern
                                directory)
                  (buffer-string)))))))

The expansion function

  (org-roam-ql-defexpansion 'regexp-rg
                            "Regex on all org files."
                            (lambda (regexp)
                              (-map
                               #'org-roam-node-from-id
                               (-non-nil (--map
                                          ;; on windows the path has a colon, hence making it relative then expanding again.
                                          (pcase-let ((`(,file ,line ,match) it))
                                            (org-roam-with-file file nil
                                              (goto-line line)
                                              (or (org-id-get-closest)
                                                  (progn
                                                    (goto-char (point-min))
                                                    (org-id-get)))))
                                          (ripgrep regexp org-roam-directory "org"))))))

Using org-roam-ql with minibuffer completion of org-roam-nodes

For example, with org-roam-node-find, one could use org-roam-ql to narrow down the nodes showing up with the following:

image

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 (see demo) 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* (split-pos mb-str
             (consult-async-split-styles-alist
              `((org-roam-ql
                 ;; :initial ?\;  ;; this was interacting with the embark
                 :function
                 ;; Override how the empty string is handled!
                 ;; When empty async-str should return default candidates
                 ,(lambda (str style)
                    (pcase-let* ((res (consult--split-perl str style))
                                 (`(,async-str ,pos ,start-highlight . ,end-highlight) res)
                                 (force (or (get-text-property 0 'consult--force async-str)
                                            (and (not (null start-highlight)) (not (null end-highlight))))))
                      ;; 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) (propertize "-" 'consult--force end-highlight)))
                      res)))))
             (corfu-auto nil)
             (consult-async-input-debounce 1)
             ;; 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 "\M-D" #'delete-minibuffer-contents)
        (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--async-pipeline
          (consult--async-split 'org-roam-ql)
          (consult--dynamic-collection
              (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-indicator)
          (consult--async-refresh))
         (funcall it sink)
         (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
          :async-wrap nil
          :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))

Note that the above also uses consult-org-roam--node-preview from consult-org-roam to preview state. You can have the :state in consult--read if you don't want previews.

Originally posted in: https://org-roam.discourse.group

Preview in org-roam buffer with org-roam-ql

From #19

org-roam-ql uses the org-roam implementation to visualize nodes. When visualizing backlinks, which is what org-roam buffer does by default, it picks the point in the buffer where the backlink is. org-roam-ql on the other hand, visualizes the node, hence, the point is where the node is. The default preview function tries to get only the content between two headings, irrespective of the heading levels. Consider the following two nodes:

* A heading
  :PROPERTIES:
  :ID: 123
  :END:

* Another heading with backlink to "A Heading"  (point 1)
  :PROPERTIES:
  :ID: 456
  :END:
** A subheading
  - content that will not get visualized in org-roam buffer
*** Another subheading
     - content referring to [[id:123][A Heading]] (point 2)
*** Yet another subheading

In the org-roam buffer for the node A Heading, it will try and visualize (point 2), as that is where the backlink is. So you have the outline Another heading with backlink to "A Heading" > A subheading > Another subheading and the content between Another subheading and Yet another subheading will be seen.

But when you are visualizing the node Another heading with backlink to "A Heading", then you are visualizing (point 1). The default preview function will only get the content between the headings Another heading with backlink to "A Heading" and A subheading. Since it is the top of the node itself, you also don't see the outline.

To visualize the whole node and include the whole subtree/file, you could use something like this as your org-roam-preview-function:

(defun my/org-roam-node-subtree-aware-preview ()
  (let* ((full-file (< (point) 5)) ;; means we are trying to display the whole file, I think!
         ;; Even when displaying full file, we skip the initial meta data
         (beg (progn (if (org-id-get)
                         (org-roam-end-of-meta-data t)
                       (org-beginning-of-line))
                     (point)))
         (end (if full-file
                  (point-max)
                (progn (when (org-id-get)
                         (org-previous-visible-heading 1)
                         (org-beginning-of-line))
                       (org-end-of-subtree)
                       (point)))))
    (buffer-substring-no-properties beg end)))
Clone this wiki locally