Org-roam buffers and non-org-roam buffers

Sometimes I find myself having a bunch of org-roam files open while also working on non-org-roam files. So I end up with a list of buffers (what I see when calling switch-to-buffer) that’s not very helpful, largely because there are too many buffers on the list whose name is not very informative.

So I’m looking for a way to separate org-roam buffers from regular buffers. For example, I could make use of some way of filtering out org-roam buffers from my list of buffers and another way of accessing my org-roam buffers that does something like org-roam-node-read but which filters out nodes not corresponding to any open file.

(I should add I want my org-roam files to be named using timestamps only and that I still use a v1 system with a single node per file and have no plans to stop doing that.)

I’m working on adapting something like this bit from the consult wiki to accomplish the first task: i.e., for filtering out org-roam buffers (in my case, all and only buffers in ~/org/notebook correspond to org-roam node), basically by adding a new buffer source like so. I still need to figure out the best way to filter those out, but I’m sort of clear on how to do that.

If anyone has accomplished something similar and is willing to share their solution, I’d be most grateful.

As for the second task, before v2 there used to be a function called org-roam-switch-to-buffer that did just that. I’m trying to adapt it to work with v2, but I’m quickly running into the limits of my elisp/org-roam knowledge. In particular, I do not know how to extract the title of an org-roam node given a file containing that node. If anyone has some suggestions, I’d really appreciate them.

Thoughts on alternative ways of making my buffer list more user-friendly would also be welcome (perhaps using something like marginalia? I can’t wrap my head around how to write an annotator yet, but maybe that’s a way to go?).

One suggestion is to look into built-in Ibuffer. One easy way is to use its filter by “full filename”. It can take regex so you can use the org-roam-directory etc. as a good filter.

Once you are happy with your filter, you can create a filter group for easy navigation.

Ibuffer also has a macro to define a filter function like this:

(with-eval-after-load 'ibuf-ext
  (define-ibuffer-filter org-roam
      "Limit current view to Org-roam files."
    (:description "Org-roam files"
     :reader nil)
    (when (buffer-file-name buf)
      (org-roam-file-p (buffer-file-name buf)))))

This creates a command named ibuffer-filter-by-org-roam that you can call in Ibuffer to apply the filter to show only Org-roam buffers.

1 Like

Very nice suggestion, thanks!

I’ve been mostly relying on consult-buffer for this, but from the looks of it I should definitely explore Ibuffer. For now, I’ve sort of managed to do a bare bones version of what I wanted with the following snippet:

(add-to-list 'consult-buffer-filter "^[0-9]+\.org" 'append)

(autoload 'org-roam-buffer-list "org-roam")

(defvar org-roam-buffer-source
  `(:name     "Org-roam"
              :hidden   t
              :narrow   ?r
              :category buffer
              :state    ,#'consult--buffer-state
              :items    ,(lambda () (mapcar #'buffer-name (org-roam-buffer-list)))))

(add-to-list 'consult-buffer-sources 'org-roam-buffer-source 'append)

Works largely because my org-roam files are the only files following the timestamp-without-hyphens + extension naming convention. Having something smarter that looks at the directory etc. seems wisest…

I use consult-buffer, too, but I have never looked into customizing it. Your way looks interesting. Would you mind sharing what your configuration does?

Specifically…

  • What does consult-buffer-filter do?
  • Why do you set :hidden t with the buffer source?

I tried your snippet but didn’t seem to have done anything until I removed the :hidden property… Got curious :slight_smile:

Sure thing!

consult-buffer-filter is a list of regexps that is used to exclude some buffers from the list of buffers you see by default when calling consult-buffer. The default setting is to have regexps that match the names of “ephemeral” buffers (those you see when you narrow to the “Hidden Buffers” source, by simply pressing SPC after calling consult-buffer). In my case, by adding the regexp above to consult-buffer-filter, the org-roam buffers get to be included in the “Hidden Buffer” source:

In setting :hidden to t I ensure that buffers from that source do not show up on the list of sources you get by default when calling consult-buffer—buffers, recent files (if recentf-mode is enabled), and bookmarks. Thus, while I have an open org-roam buffer right now, I don’t see it if I just call consult-buffer:

In order to see it, I need to press r (the key assigned as the value of :narrow) followed by space:

There’s a few other built-in sources that are hidden. The “Hidden Buffers” source I mentioned above, the “Projects” source, and some others (all the sources are listed in the value of consult-buffer-sources)

1 Like

I came across ibuffer recently too, which can be set up to do this.

2 Likes

Awesome! No wonder I could not see any difference :wink: Great tips. Thank you.

1 Like

Thanks!

Now, if only I could find a way to have the org-roam buffers be listed with their titles…

(I asked this question at the Consult repo, so maybe I’ll get help with that over there.)

Just to add, you might be able to use this some way to check if any buffer is visiting an Org-roam file or not – for your filter, for example. This may be superfluous as your regex is perfectly suited for your naming convention anyway. FYI.

1 Like

Nice idea, though I would have to modify the value consult--source-buffer to make use of that: as it is, consult-source-filter has to be a list of regexps, alas.

I do this below to rename buffer names with their title. My file names are all yyyy-mm-ddThhmmss with no title, I guess I used to have the same issue as you.

Note, though, it may not be perfect. The function is called in every single find-file call; I believe I have localized it enough so I no longer see any hiccups, but I have had this failed silently more than a couple of times – and had to scratch my head and do edebug to refine the function.

(add-hook 'find-file-hook #'my/rename-buffer)

;;;###autoload
(defun my/rename-buffer ()
  "Rename buffer to title when file is in Org-roam."
  (when (and (org-roam-file-p) (org-id-get))
    (let ((title (org-roam-node-title (org-roam-node-at-point))))
      (when title (rename-buffer (org-roam-node-title (org-roam-node-at-point)))))))
1 Like

In case this is of interest to anyone else here, I’ve ended up with something that does basically what I want by relying on a combination of consult and marginalia. If anyone tries it and runs into issues or has suggestions for making it better, don’t hesitate to let me know!

;; -*- lexical-binding: t; -*-

(defun org-roam-buffer-get-title (buffer)
  "Get title for BUFFER when it corresponds to an org-roam buffer"
  (when (org-roam-buffer-p buffer)
        (with-current-buffer buffer (org-roam-db--file-title))))

(defun org-roam-buffer-add-title (buffer)
  "Build a cons consisting of the BUFFER title and the BUFFER name"
  (cons (org-roam-buffer-get-title buffer) buffer))

(defun org-roam-update-open-buffer-list ()
  "Generate a list of pairs of the form `(TITLE BUF)’, where TITLE is
the title of an open org-roam BUFfer"
  (setq org-roam-open-buffer-list (mapcar #'org-roam-buffer-add-title (org-roam-buffer-list))))

(defun org-roam-buffer-with-title (title)
  "Find buffer name with TITLE from among the list of open org-roam
buffers"
  (org-roam-update-open-buffer-list)
  (cdr (assoc title org-roam-open-buffer-list)))


;; Here I'm manually doing what consult does with its macro
;; `consult--define-state`. There may be a more elegant way of
;; accomplishing this but I couldn't figure it out.
(defun org-roam-buffer-state nil
  (let
      ((preview
        (consult--buffer-preview)))
    (lambda
      (action cand)
      (funcall preview action (org-roam-buffer-with-title cand))
      (when
          (and cand
               (eq action 'return))
        (consult--buffer-action (org-roam-buffer-with-title cand))))))


;; This will exclude org-roam buffers in my system from the
;; consult---source-buffer. Beware it's a simple regexp match, so if
;; you use slugs etc. this may not work.
(add-to-list 'consult-buffer-filter "^[0-9]+\.org" 'append)

(autoload 'org-roam-buffer-list "org-roam")

;; This is the source for consult-buffer that will include all and
;; only org-roam buffers
(defvar org-roam-buffer-source
  `(:name     "Org-roam"
              :hidden   t
              :narrow   ?r
              :category org-roam-buffer
              :state    ,#'org-roam-buffer-state
              :items    ,(lambda () (consult--buffer-query :sort 'visibility :as #'org-roam-buffer-get-title :filter nil :predicate (lambda (buf) (org-roam-buffer-p buf))))))

(add-to-list 'consult-buffer-sources 'org-roam-buffer-source 'append)

(defun marginalia-annotate-org-roam-buffer (cand)
  "Annotate buffer CAND with modification status, file name and major mode."
  (when-let (buffer (get-buffer (org-roam-buffer-with-title cand)))
    (marginalia--fields
     ((marginalia--buffer-status buffer))
     ((marginalia--buffer-file buffer)
      :truncate -0.5 :face 'marginalia-file-name))))

(add-to-list 'marginalia-annotator-registry
             '(org-roam-buffer marginalia-annotate-org-roam-buffer builtin none))

The result will look like so:

1 Like

PS: I’ve created an issue at the consult-org-roam repo to see if something like this can be added to it. The proposal over there is a slightly improved version of the one I posted here. I’m marking this as ‘solved’ here.