Configure a Fast Org Backlog Function in Doom Emacs
My projects.org
file tends to grow, but I’m pretty happy with its overall outline structure,
so I use this function to quickly refile the current subtree into the someday.org
backlog file.
Doing it this way is faster than running a standard org-refile
operation,
and it automatically copies ancestor headings as needed to create the same outline path in the destination.
That way, if I ever go through the backlog file and find something to work on,
running the same command will bounce it back into the projects file in its original location.
In Doom, I have it mapped to SPC m r b
.
(setq my/org-refile-backlog-destinations
(list (file-name-concat org-directory "projects.org")
(file-name-concat org-directory "someday.org")))
(defun my/org-refile-backlog (&optional destinations)
"Refile the current subtree to another file with a copy of its ancestor path.
The user will select the destination file from the list of filename strings
defined by DESTINATIONS or by those in my/org-refile-backlog-destinations"
(interactive)
(save-excursion
(save-restriction
(org-save-outline-visibility t
(widen)
(outline-show-all)
(unless (derived-mode-p 'org-mode)
(error "Buffer must be Org mode"))
(unless (org-at-heading-p)
(ignore-errors (org-back-to-heading t)))
(unless (org-at-heading-p)
(error "Point must be on a heading"))
(let* ((src-buffer (current-buffer))
;; collect the point and heading for each ancestor
(src-outline-lines (progn
(save-excursion
(let* ((result nil))
(while (org-up-heading-safe)
(let* ((head (org-get-heading t t t t))
(name (substring-no-properties (or head ""))))
(push (list (pos-bol) name) result)))
result))))
;; ask the user for the destination file
(dst-files (seq-filter
(lambda (opt)
(not (string-equal opt (buffer-file-name))))
(or destinations
my/org-refile-backlog-destinations)))
(dst-file (cond
((null dst-files)
(error "No valid refile destinations available")
nil)
((= 1 (length dst-files))
(car dst-files))
(t
(completing-read "Destination: " dst-files nil t))))
(dst-buffer (ignore-errors
(or (find-buffer-visiting dst-file)
(find-file-noselect dst-file)))))
(when dst-buffer
(let* ((path (list dst-file))
(mpos nil))
;; create missing destination headings
(seq-doseq (line src-outline-lines)
(let* ((pos (nth 0 line))
(name (nth 1 line))
(head (mapconcat 'identity path "/"))
(rfloc (list head dst-file nil mpos))
(olp (append path (list name))))
(when (null (ignore-errors (org-find-olp olp)))
(with-current-buffer src-buffer
(save-excursion
(goto-char pos)
(kill-ring-save (pos-bol) (pos-eol))))
(with-temp-buffer
(org-mode)
(yank)
(org-refile nil nil rfloc)))
(setq path (append path (list name)))
(setq mpos (marker-position (ignore-errors (org-find-olp path))))))
;; refile to destination
(let* ((head (mapconcat 'identity path "/"))
(rfloc (list head dst-file nil mpos)))
(with-current-buffer src-buffer
(org-refile nil nil rfloc))))))))))
(map! :after org
(:map org-mode-map
:localleader
(:prefix ("r" . "+refile")
:desc "Backlog" "b" #'my/org-refile-backlog)))