Wanted to share something I've been working on: a way to combine the filters generated by the `ibuffer-projectile-generate-filter-groups` function in `ibuffer-projectile`!
Basically, I wanted ibuffer & projectile to generate a list of projects on startup that I could combine with my own list of filters so that stuff inside a project gets grouped together, and all the stuff not in a project is grouped in ways that's useful to me instead of all just ending up under **Default**.
Oh, and before I share the code I should say that I am by no means a lisp or elisp expert; this is just the result of an afternoon's worth of hacking on lisp to try and get it to do what I want.
Alrighty, so the first thing I did was create my own version of `ibuffer-projectile-generate-filter-groups` so that it takes an optional argument: the list of extra filters to add to the list of project filter groups.
(defun sh/ibuffer-projectile-generate-filter-groups
(&optional additional)
"Create a set of ibuffer filter groups based on the projectile root dirs of buffers."
(let ((roots (ibuffer-remove-duplicates
(delq nil (mapcar 'ibuffer-projectile-root (buffer-list))))))
(let ((new-groups
(mapcar (lambda (root)
(cons (funcall ibuffer-projectile-group-name-function (car root) (cdr root))
`((projectile-root . ,root))))
roots)))
(if additional
(append new-groups additional)
new-groups))))
Then I use it in my configuration like so:
(setq my-ibuffer-filter
'(("dired" (mode . dired-mode))
("chat" (mode . erc-mode))
("sql" (or (mode . sql-mode)
(mode . sql-interactive-mode)))
("shell" (or (mode . eshell-mode)
(mode . shell-mode)
(mode . ssh-mode)))
("org" (or (mode . org-mode)
(name . "^\\*Org Agenda\\*$")
(derived-mode . org-mode)))
("lsp" (or (name . "^\\*lsp-log\\*$")
(name . "^\\*gopls")
(name . "^\\*ruby-ls")
(name . "^\\*ts-ls")))
("magit" (or (mode . magit-mode)
(derived-mode . magit-mode)))
("help" (or (name . "^\\*Help\\*$")
(name . "^\\*helpful")))
("emacs" (or (mode . debugger-mode)
(name . "^\\*scratch\\*$")
(name . "^\\*Messages\\*$")
(name . "^\\*dashboard\\*$")
(name . "^\\*Async-native-compile-log\\*$")
(name . "^\\*Flymake log\\*$")
(name . "^\\*flycheck")
(name . "^\\*straight-process\\*$"))))
ibuffer-saved-filter-groups (list (append (list "default") (sh/ibuffer-projectile-generate-filter-groups my-ibuffer-filter))))
And blamo, I've got projectile groups & my own custom groups without having to switch back and forth!
I know I could just have separate filter groups, or maintain a list of projectile projects by hand. That's actually what I used to do, with lines like this:
("Prj:keyboard" (projectile-root "keyboard" . "~/Code/C++/keyboard"))
But keeping that list up-to-date was annoying. I had to remember to update the list of ibuffer filters when pruning old projects from projectile. Now ibuffer gets updated automagically when I update my list of projectile projects!
I know there's definitely room for improvement; `(list (append (list "default")` is ugly but I wasn't sure of the best way to marshal the output of the function so that it's what ibuffer is expecting to find in `ibuffer-saved-filter-groups`.