Close all refile targets in ibuffer after refiling

Hi everyone :wave:

Okay, so I have this problem where I always had a few hundred buffers open in ibuffer, and I couldn’t figure out what was doing it - since YEARS. Well, I finally figured it out the other day! Org refiling was the culprit. See, I didn’t know this but whenever you refile, when it says getting all targets, it’s opening them up (at least it did in my ibuffer behind the scenes).

To counter this, I asked an AI to help me, and it did. It wrote the code but it couldn’t get the last part right about not opening ibuffer to kill everything. I fixed that up myself. So this is a collaboration between an AI and me, but the code works.

The only “issue” now is that if you have a lot of targets, it will take a few seconds each time to do the gathering part. For me, that’s not a big deal, but maybe it is for you. And if you’re on here, there is a high likelihood of you being able to fix it so this becomes even better and faster. So here is my code. Please use it as you want. And if you can make it better, or faster somehow for repeated steps, then let me know (the closing part isn’t an issue - that takes a second on my machine, or less).

(Note that the comment says that it’s bound to C-z o r but that isn’t shown in the code. It’s at the bottom of my config in custom key binds).

;; This function uses C-z o r (listed at the end of config) to refile. It opens all the refile targets, and then closes them after refiling, so it keeps ibuffer clean.
  (defun my/org-refile-and-close-buffers ()
    "Refile using org-refile and close related buffers in ibuffer."
    (interactive)
    ;; Get the list of open buffers before the refile
    (let ((before-buffers (buffer-list))
	  (org-refile-use-outline-path nil)) ; Disable outline path
      (call-interactively 'org-refile) ; Call org-refile interactively

      ;; Get the list of open buffers after the refile
      (let ((after-buffers (buffer-list)))
	;; Close only the buffers opened during the refile
	(dolist (buffer before-buffers)
	  (setq after-buffers (delete buffer after-buffers)))
	(mapc 'kill-buffer after-buffers))))

I hope this helps! Now you can keep your ibuffer clean when you refile!

Cool, nice work! Something tells me there may be a more efficient way to accomplish this, but it’s too late in the evening for my brain to figure it out :slight_smile:

Copying my comment from Reddit:

A few clarifications FYI:

  1. Ibuffer is just a way to list buffers that are open in Emacs. There’s also the list-buffers command, my own bufler, etc.
  2. org-refile works by gathering a list of all headings in certain files, according to how you’ve configured it. In order to do that, it must have all of those files open in Emacs, so if any of them aren’t already, they will be opened–and the initialization of opening an Org file tends to be slow. As well, if there are a lot of headings in your configuration, yeah, it’s going to be slow, because it makes a list of, e.g. thousands and thousands of headings, just to throw away that list when you pick one.
  3. This is why I strongly recommend using my org-ql-refile command to choose a refile target. Of course, it must also have access to the files to find targets in them, but the act of finding them doesn’t build a list of every heading first; it uses the Org QL search query to find ones that match, which is generally orders of magnitude faster, and produces much less garbage.
  4. Whether you want to kill the Org buffers that weren’t open before you refiled the entry is another matter. Generally there’s no need to do so; Emacs can handle many buffers being open at once, and by leaving them open, you won’t have to wait the next time you want to refile something.

Other than that, the code you have should work, but you could replace the dolist loop with, e.g. (mapc #'kill-buffer (seq-difference after-buffers before-buffers)).