mirror of
https://git.sr.ht/~magic_rb/dotfiles
synced 2024-11-25 17:46:14 +01:00
169 lines
5.5 KiB
Org Mode
169 lines
5.5 KiB
Org Mode
|
:PROPERTIES:
|
||
|
:ID: 22d678ce-7a3a-486c-abfb-f6cebdd77f90
|
||
|
:END:
|
||
|
#+title: Org Agenda
|
||
|
#+filetags: :emacs-load:
|
||
|
|
||
|
# SPDX-FileCopyrightText: 2022 Richard Brežák <richard@brezak.sk>
|
||
|
#
|
||
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||
|
|
||
|
Put state changes into the ~LOGBOOK~ section and not into a random spot.
|
||
|
|
||
|
#+BEGIN_SRC emacs-lisp
|
||
|
(setq org-log-into-drawer t)
|
||
|
#+END_SRC
|
||
|
|
||
|
Set priority levels to A, B, and C.
|
||
|
|
||
|
#+BEGIN_SRC emacs-lisp :resutls none
|
||
|
(setq org-highest-priority ?A)
|
||
|
(setq org-default-priority ?B)
|
||
|
(setq org-lowest-priority ?C)
|
||
|
#+END_SRC
|
||
|
|
||
|
* Dynamic Org Agenda using Org Roam DB
|
||
|
#+BEGIN_NOTE
|
||
|
This whole system depends on [[id:a56794cf-b8f9-4537-a390-bd7ee6bb35ae][Vulpea]]
|
||
|
#+END_NOTE
|
||
|
|
||
|
#+BEGIN_SRC emacs-lisp :results none
|
||
|
(with-eval-after-load "vulpea"
|
||
|
#+END_SRC
|
||
|
|
||
|
First we have to exclude the =agenda= tag from inheritance.
|
||
|
|
||
|
#+BEGIN_SRC emacs-lisp :results none
|
||
|
(add-to-list 'org-tags-exclude-from-inheritance "project")
|
||
|
#+END_SRC
|
||
|
|
||
|
Then we need a function to check whether a buffer contains any todo entry.
|
||
|
|
||
|
#+BEGIN_SRC emacs-lisp :results none
|
||
|
(defun vulpea-project-p ()
|
||
|
"Return non-nil if current buffer has any todo entry.
|
||
|
|
||
|
TODO entries marked as done are ignored, meaning the this
|
||
|
function returns nil if current buffer contains only completed
|
||
|
tasks."
|
||
|
(when (eq major-mode 'org-mode)
|
||
|
(org-element-map
|
||
|
(org-element-parse-buffer 'headline)
|
||
|
'headline
|
||
|
(lambda (h)
|
||
|
(eq (org-element-property :todo-type h)
|
||
|
'todo))
|
||
|
nil 'first-match)))
|
||
|
#+END_SRC
|
||
|
|
||
|
Then we need a function which will check whether the current buffer contains any TODOs and if so, then add a roam tag to that file, so that we can easily get a list of all files with TODOs.
|
||
|
|
||
|
#+BEGIN_SRC emacs-lisp :results none
|
||
|
(add-hook 'find-file-hook #'vulpea-project-update-tag)
|
||
|
(add-hook 'before-save-hook #'vulpea-project-update-tag)
|
||
|
|
||
|
(defun vulpea-project-update-tag ()
|
||
|
"Update PROJECT tag in the current buffer."
|
||
|
(when (and (not (active-minibuffer-window))
|
||
|
(vulpea-buffer-p))
|
||
|
(save-excursion
|
||
|
(goto-char (point-min))
|
||
|
(let* ((tags (vulpea-buffer-tags-get))
|
||
|
(original-tags tags))
|
||
|
(if (vulpea-project-p)
|
||
|
(setq tags (cons "project" tags))
|
||
|
(setq tags (remove "project" tags)))
|
||
|
|
||
|
;; cleanup duplicates
|
||
|
(setq tags (seq-uniq tags))
|
||
|
|
||
|
;; update tags if changed
|
||
|
(when (or (seq-difference tags original-tags)
|
||
|
(seq-difference original-tags tags))
|
||
|
(apply #'vulpea-buffer-tags-set tags))))))
|
||
|
|
||
|
(defun vulpea-buffer-p ()
|
||
|
"Return non-nil if the currently visited buffer is a note."
|
||
|
(and buffer-file-name
|
||
|
(or (string-prefix-p
|
||
|
(expand-file-name (file-name-as-directory org-roam-directory))
|
||
|
(file-name-directory buffer-file-name))
|
||
|
(string-prefix-p
|
||
|
(expand-file-name (file-name-as-directory "~/dotfiles/emacs-lisp"))
|
||
|
(file-name-directory buffer-file-name)))))
|
||
|
#+END_SRC
|
||
|
|
||
|
Now for the second last function, we need to actually return the list of files containing the =project= tag, to be consumed by org-agenda.
|
||
|
|
||
|
#+BEGIN_SRC emacs-lisp :results none
|
||
|
(defun vulpea-project-files ()
|
||
|
"Return a list of note files containing 'project' tag." ;
|
||
|
(seq-uniq
|
||
|
(seq-map
|
||
|
#'car
|
||
|
(org-roam-db-query
|
||
|
[:select [nodes:file]
|
||
|
:from tags
|
||
|
:left-join nodes
|
||
|
:on (= tags:node-id nodes:id)
|
||
|
:where (or (like tag '"%project%") (like tag '"%project-forced%"))]))))
|
||
|
#+END_SRC
|
||
|
|
||
|
Finally we can update the list of project files before every =org-agenda= invocation.
|
||
|
|
||
|
#+BEGIN_SRC emacs-lisp :results none
|
||
|
(defun vulpea-agenda-files-update (&rest _)
|
||
|
"Update the value of `org-agenda-files'."
|
||
|
(setq org-agenda-files (vulpea-project-files)))
|
||
|
|
||
|
(advice-add 'org-agenda :before #'vulpea-agenda-files-update)
|
||
|
#+END_SRC
|
||
|
|
||
|
** Migration
|
||
|
|
||
|
To migrate existing org-roam files to this new system, run this elisp code.
|
||
|
|
||
|
#+BEGIN_SRC emacs-lisp :results none :tangle no
|
||
|
(dolist (file (org-roam-list-files))
|
||
|
(message "processing %s" file)
|
||
|
(with-current-buffer (or (find-buffer-visiting file)
|
||
|
(find-file-noselect file))
|
||
|
(vulpea-project-update-tag)
|
||
|
(save-buffer)))
|
||
|
#+END_SRC
|
||
|
|
||
|
#+BEGIN_SRC emacs-lisp :results none :exports none
|
||
|
)
|
||
|
#+END_SRC
|
||
|
|
||
|
* Custom Tags
|
||
|
|
||
|
Define a number of custom tags to ease organisation.
|
||
|
|
||
|
#+BEGIN_SRC emacs-lisp :results none
|
||
|
(defun my/org-match-at-point-p (match)
|
||
|
"Return non-nil if headline at point matches MATCH.
|
||
|
Here MATCH is a match string of the same format used by
|
||
|
`org-tags-view'."
|
||
|
(funcall (cdr (org-make-tags-matcher match))
|
||
|
(org-get-todo-state)
|
||
|
(org-get-tags-at)
|
||
|
(org-reduced-level (org-current-level))))
|
||
|
|
||
|
(defun my/org-agenda-skip-without-match (match)
|
||
|
"Skip current headline unless it matches MATCH.
|
||
|
|
||
|
Return nil if headline containing point matches MATCH (which
|
||
|
should be a match string of the same format used by
|
||
|
`org-tags-view'). If headline does not match, return the
|
||
|
position of the next headline in current buffer.
|
||
|
|
||
|
Intended for use with `org-agenda-skip-function', where this will
|
||
|
skip exactly those headlines that do not match."
|
||
|
(save-excursion
|
||
|
(unless (org-at-heading-p) (org-back-to-heading))
|
||
|
(let ((next-headline (save-excursion
|
||
|
(or (outline-next-heading) (point-max)))))
|
||
|
(if (my/org-match-at-point-p match) nil next-headline))))
|
||
|
#+END_SRC
|