blob: f15e5503a6acd019cceb27a86e7843534be159fa (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
|
;;; org-agenda-config --- Org-Agenda/Todo Config -*- lexical-binding: t; -*-
;; author Craig Jennings <c@cjennings.net>
;;; Commentary:
;; Agenda views are tied to the F8 (fate) key.
;; f8 - MAIN AGENDA which organizes all tasks and events into:
;; - all unfinished priority A tasks
;; - the weekly schedule, including the habit consistency graph
;; - all priority B and C tasks
;; C-f8 - TASK LIST containing all tasks from all agenda targets.
;; M-f8 - TASK LIST containing all tasks from just the current org-mode buffer.
;; NOTE:
;; Files that contain information relevant to the agenda will be found in the
;; following places: the schedule-file, org-roam notes tagged as 'Projects' and
;; project todo.org files found in project-dir and code-dir.
;;; Code:
(use-package org-agenda
:ensure nil ;; built-in
:after (org org-roam)
:config
(setq org-agenda-prefix-format '((agenda . " %i %-25:c%?-12t% s")
(timeline . " % s")
(todo . " %i %-25:c")
(tags . " %i %-12:c")
(search . " %i %-12:c")))
(setq org-agenda-dim-blocked-tasks 'invisible)
(setq org-agenda-skip-scheduled-if-done nil)
(setq org-agenda-remove-tags t)
(setq org-agenda-compact-blocks t)
;; display the agenda from the bottom
(add-to-list 'display-buffer-alist
'("\\*Org Agenda\\*"
(display-buffer-reuse-mode-window display-buffer-below-selected)
(dedicated . t)
(window-height . fit-window-to-buffer)))
;; reset s-left/right each time org-agenda is enabled
(add-hook 'org-agenda-mode-hook (lambda ()
(local-set-key (kbd "s-<right>") #'org-agenda-todo-nextset)
(local-set-key (kbd "s-<left>")
#'org-agenda-todo-previousset)))
;; build org-agenda-list for the first time after emacs init completes.
(add-hook 'emacs-startup-hook #'cj/build-org-agenda-list))
;; ------------------------ Add Files To Org Agenda List -----------------------
;; finds files named 'todo.org' (case insensitive) and adds them to
;; org-agenda-files list.
(defun cj/add-files-to-org-agenda-files-list (directory)
"Search for files named \\='todo.org\\=' add them to org-project-files.
DIRECTORY is a string of the path to begin the search."
(interactive "D")
(setq org-agenda-files
(append (directory-files-recursively directory
"^[Tt][Oo][Dd][Oo]\\.[Oo][Rr][Gg]$" t)
org-agenda-files)))
;; ---------------------------- Rebuild Org Agenda ---------------------------
;; builds the org agenda list from all agenda targets.
;; agenda targets is the schedule, project todos, inbox, and org roam projects.
(defun cj/build-org-agenda-list ()
"Rebuilds the org agenda list.
Begins with the inbox-file and schedule-file, then searches for org-roam
Projects and adds all todo.org files from code and project directories."
(interactive)
;; reset org-agenda-files to inbox-file
(setq org-agenda-files (list inbox-file schedule-file))
(let ((new-files
(append
(cj/org-roam-list-notes-by-tag "Project"))))
(dolist (file new-files)
(unless (member file org-agenda-files)
(setq org-agenda-files (cons file org-agenda-files)))))
(cj/add-files-to-org-agenda-files-list projects-dir)
(cj/add-files-to-org-agenda-files-list code-dir))
;; ------------------------------ Agenda List All ------------------------------
;; an agenda listing tasks from all available agenda targets.
(defun cj/todo-list-all-agenda-files ()
"Displays an \\='org-agenda\\=' todo list.
The contents of the agenda will be built from org-project-files and org-roam
files that have project in their filetag."
(interactive)
(cj/build-org-agenda-list)
(org-agenda "a" "t"))
(global-set-key (kbd "C-<f8>") #'cj/todo-list-all-agenda-files)
;; ------------------------- Agenda List Current Buffer ------------------------
;; an agenda listing tasks from just the current buffer.
(defun cj/todo-list-from-this-buffer ()
"Displays an \\='org-agenda\\=' todo list built from the current buffer.
If the current buffer isn't an org buffer, inform the user."
(interactive)
(if (eq major-mode 'org-mode)
(let ((org-agenda-files (list buffer-file-name)))
(org-agenda "a" "t"))
(message (concat "Your org agenda request based on '" (buffer-name (current-buffer))
"' failed because it's not an org buffer."))))
(global-set-key (kbd "M-<f8>") #'cj/todo-list-from-this-buffer)
;; -------------------------------- Main Agenda --------------------------------
;; my custom agenda command from all available agenda targets. adapted from:
;; https://blog.aaronbieber.com/2016/09/24/an-agenda-for-life-with-org-mode.html
(defvar cj/main-agenda-hipri-title "\nHIGH PRIORITY UNRESOLVED TASKS\n"
"String to announce the high priority section of the main agenda.")
(defvar cj/main-agenda-schedule-title "\nSCHEDULE\n"
"String to announce the schedule section of the main agenda.")
(defvar cj/main-agenda-tasks-title "\nPRIORITY B AND C\n"
"String to announce the schedule section of the main agenda.")
(defun cj/org-skip-subtree-if-habit ()
"Skip an agenda entry if it has a STYLE property equal to \"habit\"."
(let ((subtree-end (save-excursion (org-end-of-subtree t))))
(if (string= (org-entry-get nil "STYLE") "habit")
subtree-end
nil)))
(defun cj/org-skip-subtree-if-priority (priority)
"Skip an agenda subtree if it has a priority of PRIORITY.
PRIORITY may be one of the characters ?A, ?B, or ?C."
(let ((subtree-end (save-excursion (org-end-of-subtree t)))
(pri-value (* 1000 (- org-lowest-priority priority)))
(pri-current (org-get-priority (thing-at-point 'line t))))
(if (= pri-value pri-current)
subtree-end
nil)))
(defun cj/org-skip-subtree-if-keyword (keywords)
"Skip an agenda subtree if it has a TODO keyword in KEYWORDS.
KEYWORDS must be a list of strings."
(let ((subtree-end (save-excursion (org-end-of-subtree t))))
(if (member (org-get-todo-state) keywords)
subtree-end
nil)))
(setq org-agenda-custom-commands
'(("d" "Daily Agenda with Tasks"
((tags "PRIORITY=\"A\""
((org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done))
(org-agenda-overriding-header cj/main-agenda-hipri-title)))
(agenda "" ((org-agenda-ndays 1)
(org-agenda-overriding-header cj/main-agenda-schedule-title)))
(alltodo ""
((org-agenda-skip-function '(or (cj/org-skip-subtree-if-habit)
(cj/org-skip-subtree-if-priority ?A)
(cj/org-skip-subtree-if-priority ?D)
(cj/org-skip-subtree-if-keyword '("PROJECT"))
(org-agenda-skip-if nil '(scheduled deadline))))
(org-agenda-overriding-header cj/main-agenda-tasks-title))))
((org-agenda-compact-blocks nil)))))
(defun cj/main-agenda-display ()
"Display the main \'org-agenda\' display.
This uses all org-agenda targes and presents three sections:
- all unfinished priority A tasks
- the weekly schedule, including the habit consistency graph
- all priority B and C tasks"
(interactive)
(cj/build-org-agenda-list)
(org-agenda "a" "d"))
(global-set-key (kbd "<f8>") #'cj/main-agenda-display)
;; ------------------------- Add Timestamp To Org Entry ------------------------
;; simply adds a timestamp to put the org entry on an agenda
(defun cj/add-timestamp-to-org-entry (s)
"Add an event with time S to appear underneath the line-at-point.
This allows a line to show in an agenda without being scheduled or a deadline."
(interactive "sTime: ")
(defvar cj/timeformat "%Y-%m-%d %a")
(org-end-of-line)
(save-excursion
(open-line 1)
(forward-line 1)
(insert (concat "<" (format-time-string cj/timeformat (current-time)) " " s ">" ))))
(global-set-key (kbd "M-t") #'cj/add-timestamp-to-org-entry)
;; --------------------------- Notifications / Alerts --------------------------
;; send libnotify notifications about agenda items
(use-package alert
:defer .5
:after org-agenda
:config
(setq alert-fade-time 10) ;; secs to vanish alert
(setq alert-default-style 'libnotify)) ;; work with dunst
(use-package org-alert
:defer .5
:after alert
:bind
("C-c A" . org-alert-check)
:config
(setq alert-default-style 'libnotify) ;; work with dunst
(setq org-alert-interval 180) ;; seconds between agenda checks (180 = 3 mins)
(setq org-alert-notify-cutoff 5) ;; minutes before a deadline to send alert
(setq org-alert-notify-after-event-cutoff 10) ;; mins post deadline to stop alerts
(setq org-alert-notification-title "Reminder")
(org-alert-enable))
(provide 'org-agenda-config)
;;; org-agenda-config.el ends here
|