Filter org-roam-node-{find, insert} using tags and folders

I wanted to filter out daily nodes and refs nodes so I wrote the following in my configuration. Hope it will help someone else out there.

(Btw I use Doom.)

(cl-defun my/org-roam-node--filter-by-tags (node &optional included-tags excluded-tags)
  "Filter org-roam-node by tags."
  (let* ((tags (org-roam-node-tags node))
         (file-path (org-roam-node-file node))
         (rel-file-path (f-relative file-path org-roam-directory))
         (parent-directories (butlast (f-split rel-file-path)))
         (tags (cl-union tags parent-directories)))
    (if (or
         ;; (and included-tags (cl-notevery (lambda (x) (cl-member x tags :test #'string=)) included-tags))
         (and included-tags (not (cl-intersection included-tags tags :test #'string=)))
         (and excluded-tags (cl-intersection excluded-tags tags :test #'string=))
         ) nil t)))

(cl-defun my/org-roam-node-find (included-tags excluded-tags)
  "Modded org-roam-node-find which filters nodes using tags."
  (interactive)
  (org-roam-node-find nil nil
                      (lambda (node) (my/org-roam-node--filter-by-tags node included-tags excluded-tags))))

(cl-defun my/org-roam-node-insert (included-tags excluded-tags)
  "Modded org-roam-node-insert which filters nodes using tags."
  (interactive)
  (org-roam-node-insert
   (lambda (node) (my/org-roam-node--filter-by-tags node included-tags excluded-tags))))

(map!
 :leader
 (:prefix ("r" . "org-roam")
  "f" #'(lambda () (interactive) (my/org-roam-node-find nil '("daily" "captures")))
  "i" #'(lambda () (interactive) (my/org-roam-node-insert nil '("daily" "captures")))
  "F" #'(lambda () (interactive) (my/org-roam-node-find '("daily" "captures") nil))
  "I" #'(lambda () (interactive) (my/org-roam-node-insert '("daily" "captures") nil)))
2 Likes

You can customize the node inclusion/exclusion by implementing the function org-node-node-inclusion-function, as explained in the What to cache section in the org-roam manual. So there is no need to override the functions.

But your code snippet inspired me to achieve the same, because I liked the directory-based tagging in org-roam v1. In my example, I would like to exclude all nodes in the daily directory:

  (setq my/org-roam-excluded-dirs '("daily"))
  
  (setq org-roam-db-node-include-function (lambda ()
                                            (let* ((file-path (buffer-file-name (buffer-base-buffer)))
                                                   (rel-file-path (f-relative file-path org-roam-directory))
                                                   (parent-directories (butlast (f-split rel-file-path))))
                                              (if (cl-intersection my/org-roam-excluded-dirs parent-directories :test #'string=) nil t))))

(thanks to Jethro for the nudge in the right direction)

2 Likes

Thanks for the information about org-node-node-inclusion-function!
However, I think our cases are slightly different. I wanted to filter out certain tags on demand while still keeping them in database so that I would still be able to access them whenever I need to. (eg. “nsfw” tag)

I’d love to your Aur3l14no’s function, too, but I use vanilla emacs. I don’t have enough lisp knowledge to rewrite it, buy I’m sure it’s not hard. Any1 out there?

I can’t test this but by a quick glance it appears that you don’t need doom for the functions. Make sure you have the f package. The map! is doom specific but you can use the built-in define-key for the keybindings.

I rewrote things and simplified things, I tried to replace all the common lisp expressions with vanilla elisp and added documentation, users may use this as a separate package. Use (setq org-roam-filter-by-entries '("list" "of" "items")) to set the entries to filter by

  1. entries may be tags or names of directories
  2. without C-u prefix it calls the standard functions
  3. with C-u 1 shows candidates excluding those specified in the entries
  4. C-u 2 shows only candidates matching those entries
  5. C-u 3 lets either choose from the pre defined list or specify something totally different on the fly
;; org-roam-filter-entries.el

(require 'org-roam)

;;; 0. Variables
;;  --------------------------------------------------
(defcustom org-roam-filter-by-entries '("journal" "notes")
  "Entries (tags or directories) to be excluded for Org-roam node filtering."
  :type '(repeat string)
  :group 'org-roam)
;;  ---

;;; 1. Helper Functions
;;  --------------------------------------------------

;; Helper function to filter org-roam-node by entries (tags or directories).
(defun custom/org-roam-node--filter-by-entries (node &optional included-entries excluded-entries)
  "Filter org-roam-node by entries (tags or directories)."
  (let* ((entries (append (org-roam-node-tags node)
                          (butlast (f-split (f-relative (org-roam-node-file node) org-roam-directory)))))
         (included (and included-entries (not (seq-some (lambda (entry) (member entry entries)) included-entries))))
         (excluded (and excluded-entries (seq-some (lambda (entry) (member entry entries)) excluded-entries))))
    (if (or included excluded)
        nil t)))

;; Helper function to filter org-roam-node to show only nodes with entries in `org-roam-filter-by-entries`.
(defun custom/org-roam-node--filter-excluded (node)
  "Filter org-roam-node to show only nodes with entries in `org-roam-filter-by-entries`."
  (custom/org-roam-node--filter-by-entries node nil org-roam-filter-by-entries))
;;  ---

;;; 2. Find Functions
;;  --------------------------------------------------

;; Modded org-roam-node-find which filters nodes using entries (tags or directories).
(defun custom/org-roam-node-find ()
  "Show Org-roam nodes, filtering by entries (tags or directories)."
  (interactive)
  (org-roam-node-find nil nil
                      (lambda (node) (custom/org-roam-node--filter-by-entries node nil org-roam-filter-by-entries))))

;; Show only Org-roam nodes that match entries in `org-roam-filter-by-entries`.
(defun custom/org-roam-node-find-only ()
  "Show only Org-roam nodes that match entries in `org-roam-filter-by-entries`."
  (interactive)
  (org-roam-node-find nil nil (lambda (node) (not (custom/org-roam-node--filter-excluded node)))))

(defun custom/org-roam-node-find-entry (&optional new-entries)
  "Show only Org-roam nodes matching the specified entries interactively.
If NEW-ENTRIES are provided, use them as the entries to match by (seperate entries by ,).
Otherwise, prompt the user to select from existing entries."
  (interactive)
  (if new-entries
      (let ((org-roam-filter-by-entries new-entries))
        (custom/org-roam-node-find-only))
    (let ((selected-entries (completing-read-multiple "Select entries to filter by (seperate by ,): " org-roam-filter-by-entries)))
      (let ((org-roam-filter-by-entries selected-entries))
        (custom/org-roam-node-find-only)))))

;; Perform find actions on Org-roam nodes based on the prefix argument.
(defun custom/org-roam-node-find-actions (arg)
  "Perform find actions on Org-roam nodes.

With no prefix argument, this function invokes `org-roam-node-find`,
to find nodes from the unfiltered list

With a prefix argument of C-u 1, it calls `custom/org-roam-node-find`,
to find Org-roam nodes from a filtered list excluding entries specified in `org-roam-filter-by-entries`

With a prefix argument of C-u 2, it calls `custom/org-roam-node-find-only`,
to find only nodes that match entries specified in `org-roam-filter-by-entries`.

With a prefix argument of C-u 3, it calls `custom/org-roam-node-find-entry`
allowing the user to interactively choose entries or specify on-the-fly new ones (seperated by ,) and to find nodes matching the entries.
"
  (interactive "P")
  (let ((numeric-arg (prefix-numeric-value arg)))
    (cond
     ((null arg) (org-roam-node-find))
     ((= numeric-arg 1) (custom/org-roam-node-find))
     ((= numeric-arg 2) (custom/org-roam-node-find-only))
     ((= numeric-arg 3) (custom/org-roam-node-find-entry))
     (t (custom/org-roam-node-find)))))
;;  ---

;;; 3. Input Functions
;;  --------------------------------------------------

;; Modded org-roam-node-insert which filters nodes using entries (tags or directories).
(defun custom/org-roam-node-insert ()
  "Insert Org-roam node, filtering by entries (tags or directories)."
  (interactive)
  (org-roam-node-insert
   (lambda (node) (custom/org-roam-node--filter-by-entries node nil org-roam-filter-by-entries))))

;; Show only Org-roam nodes that have entries in `org-roam-filter-by-entries`.
(defun custom/org-roam-node-insert-only ()
  "Insert Org-roam node, showing only nodes that match entries in `org-roam-filter-by-entries`."
  (interactive)
  (org-roam-node-insert (lambda (node) (not (custom/org-roam-node--filter-excluded node)))))

(defun custom/org-roam-node-insert-entry (&optional new-entries)
  "Insert Org-roam node, showing only nodes matching the specified entries interactively.
If NEW-ENTRIES are provided, use them as the entries to match by (seperate entries by ,).
Otherwise, prompt the user to select from existing entries."
  (interactive)
  (if new-entries
      (let ((org-roam-filter-by-entries new-entries))
        (custom/org-roam-node-insert-only))
    (let ((selected-entries (completing-read-multiple "Select entries to filter by (seperate by ,): " org-roam-filter-by-entries)))
      (let ((org-roam-filter-by-entries selected-entries))
        (custom/org-roam-node-insert-only)))))

;; Perform actions on Org-roam nodes during insertion based on the prefix argument.
(defun custom/org-roam-node-insert-actions (arg)
  "Perform insert actions on Org-roam nodes.

With no prefix argument, this function invokes `org-roam-node-insert`,
to insert nodes from the unfiltered list

With a prefix argument of C-u 1, it calls `custom/org-roam-node-insert`,
to insert Org-roam nodes from a filtered list excluding entries specified in `org-roam-filter-by-entries`

With a prefix argument of C-u 2, it calls `custom/org-roam-node-insert-only`,
to insert only nodes that match entries specified in `org-roam-filter-by-entries`.

With a prefix argument of C-u 3, it calls `custom/org-roam-node-insert-entry`
allowing the user to interactively choose entries or specify on-the-fly new ones (seperated by ,) and to insert from nodes matching the entries.
"
  (interactive "P")
  (let ((numeric-arg (prefix-numeric-value arg)))
    (cond
     ((null arg) (org-roam-node-insert))
     ((= numeric-arg 1) (custom/org-roam-node-insert))
     ((= numeric-arg 2) (custom/org-roam-node-insert-only))
     ((= numeric-arg 3) (custom/org-roam-node-insert-entry)) 
     (t (custom/org-roam-node-insert)))))
;;  ---

;; Key bindings
(global-set-key (kbd "C-c f f") #'custom/org-roam-node-find-actions)
(global-set-key (kbd "C-c f i") #'custom/org-roam-node-insert-actions)

(provide 'org-roam-filter-entries)
;;; 


Please set your keybindings that you use for org-roam-node-find and org-roam-node-insert , without prefix they are the same and will continue to work as same.

Awesome. Is this similar in sprit and application to the one I did to filter by subdirectories? I put mine in the wiki. Yours looks to have much more features including tags, etc.

PS I haven’t forgotten about letting you know my tag usage yet :slight_smile:

1 Like

yes sir,i think its similar only incorporating tags also, sorry for the late reply, I have this weird condition where once I publish something I suddenly notice multiple things wrong with it, there were mistakes in documentations that I updated, I also checked your code and decided to use completing-read-multiple so its possible to introduce multiple “entries” seperated by comma while using the C-u 3 prefix. I think I wont need to fix something again. But until a cure for my OCD is found, I will always find numerous mistakes (only after i publish it)

Please post in your time sir, no hurry.

1 Like