changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > core / emacs/lib/inbox.el

changeset 651: af486e0a40c9
parent: f58f3b88c49e
child: 585f14458a65
author: Richard Westhaver <ellis@rwest.io>
date: Sat, 14 Sep 2024 22:13:06 -0400
permissions: -rw-r--r--
description: multi-binaries, working on removing x.lisp
1 ;;; lib/inbox.el --- Inbox API -*- lexical-binding: t -*-
2 
3 ;; Copyright (C) 2023 Richard Westhaver
4 ;; Version: "0.2.0"
5 ;; Keywords: maint, tools, outlines, extensions
6 
7 ;; This program is free software; you can redistribute it and/or modify
8 ;; it under the terms of the GNU General Public License as published by
9 ;; the Free Software Foundation, either version 3 of the License, or
10 ;; (at your option) any later version.
11 
12 ;; This program is distributed in the hope that it will be useful,
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;; GNU General Public License for more details.
16 
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with this program. If not, see <https://www.gnu.org/licenses/>.
19 
20 ;;; Commentary:
21 
22 ;; This is the elisp interface to the CC Inbox system. The main
23 ;; interface is the inbox.org file which manages personal tasks.
24 
25 ;; Users may use `org-capture' to insert tasks and notes into their
26 ;; own `org-inbox-file' and refactor them to a more sensible
27 ;; destination with `org-refile'.
28 
29 ;;; Code:
30 (require 'org)
31 (require 'org-agenda)
32 (require 'default)
33 ;; (require 'uml-mode)
34 (require 'org-expiry)
35 
36 (defgroup inbox nil
37  "CC Inbox")
38 
39 ;;; Vars
40 (defcustom org-inbox-file
41  (concat (file-name-as-directory org-directory) "inbox.org")
42  "Custom inbox file location."
43  :type 'file
44  :group 'inbox)
45 
46 (defcustom org-inbox-date-start-format "<%Y-%m-%d %a>"
47  "Format of DATE_START property timestamp for week headings. See
48 `org-time-stamp-formats' for accepted values."
49  :type 'string
50  :group 'inbox)
51 
52 (defvar org-inbox-buffer-name "*Inbox*"
53  "The name of the org-inbox buffer.")
54 
55 (defvar org-inbox-config-buffer-name "*Inbox Config*"
56  "Then name of the org-inbox configuration buffer.")
57 
58 (defvar org-inbox-properties
59  '("NEXT" "PREV" "FROM" "TO" "OWNER" "PROJECT" "BLOCKER" "VERSION"))
60 
61 (defvar org-inbox-db-schema
62  '(id file node edge contents properties schedule))
63 
64 ;;; Capture
65 (setq org-id-link-to-org-use-id t
66  org-protocol-default-template-key "L")
67 
68 ;; capture templates
69 (setq org-capture-templates
70  `(("i" "inbox-item" entry (file ,org-inbox-file)
71  "* %?\n%i"
72  :empty-lines 1)
73  ("t" "inbox-task" entry (file ,org-inbox-file) "* TODO %^{item}\n")
74  ("n" "inbox-note" entry (file ,org-inbox-file) "* %^{item}\n%a")
75  ("w" "inbox-web-link" entry (file ,org-inbox-file)
76  "* %?"
77  :hook (lambda ()
78  (goto-char (pos-eol))
79  (org-web-tools-insert-link-for-url (org-web-tools--get-first-url))))
80  ("1" "current-task-item" item (clock) "%i%?")
81  ("2" "current-task-checkbox" checkitem (clock) "%i%?")
82  ("3" "current-task-region" plain (clock) "%i" :immediate-finish t :empty-lines 1)
83  ("4" "current-task-kill" plain (clock) "%c" :immediate-finish t :empty-lines 1)
84  ("l" "log" item (file+headline "log.org" "log") "%U %?" :prepend t)
85  ("s" "secret" table-line (file+function "krypt" org-ask-location) "| %^{key} | %^{val} |" :immediate-finish t :kill-buffer t)
86  ("N" "note-item" plain (file+function "notes.org" org-ask-location) "%?")))
87 
88 (add-hook 'org-after-todo-state-change-hook #'org-id-get-create)
89 (add-hook 'org-after-todo-state-change-hook #'org-expiry-insert-created)
90 
91 (setq org-default-notes-file (join-paths org-directory "inbox.org")
92  org-capture-use-agenda-date t
93  org-archive-location "archive.org::")
94 
95 ;;; Utils
96 ;; `org-archive-all-done' doesn't work the way we want. This function
97 ;; will archive all done tasks in the current subtree, or the whole file
98 ;; if prefix arg is given.
99 (defun org-archive-done (&optional scope)
100  "archive all tasks with todo-state of 'DONE' or 'NOPE'."
101  (interactive "P")
102  (org-map-entries
103  (lambda ()
104  (org-archive-subtree)
105  (setq org-map-continue-from (org-element-property :begin (org-element-at-point))))
106  "/+DONE|NOPE" scope))
107 
108 (defun org-children-done ()
109  "Mark all sub-tasks in this heading as 'DONE'."
110  (interactive)
111  (org-map-entries
112  (lambda ()
113  (unless (= (org-current-level) 1)
114  (org-todo "DONE"))
115  nil 'tree)))
116 
117 (defmacro with-inbox-buffer (&rest body)
118  `(save-excursion
119  (with-current-buffer (find-file org-inbox-file)
120  ,@body)))
121 
122 (defun org-sort-todo-priority ()
123  "Sorting function used by `org-sort' to sort by todo order
124  followed by priority. Returns a pair of numbers (TODO . PRIO)."
125  (let* ((elt (cadr (org-element-at-point)))
126  (todo (when-let ((kw (plist-get elt :todo-keyword)))
127  (when (stringp kw)
128  (substring-no-properties kw))))
129  (prio (pcase (plist-get elt :priority)
130  ("A" 1)
131  ("B" 2)
132  ("C" 3)
133  (t 2)))
134  (res))
135  ;; FIXME todo states shouldn't be hardcoded
136  (cond
137  ((null todo) (setq res (cons 3 prio)))
138  ((string= todo "WATCH") (setq res (cons 3 prio)))
139  ((string= todo "WAIT") (setq res (cons 1 prio)))
140  ((string= todo "HOLD") (setq res (cons 1 prio)))
141  ((string= todo "WIP") (setq res (cons 1 prio)))
142  ((string= todo "GOTO") (setq res (cons 2 prio)))
143  ((string= todo "TODO") (setq res (cons 2 prio)))
144  ((string= todo "RESEARCH") (setq res (cons 3 prio)))
145  ((string= todo "DONE") (setq res (cons 4 prio)))
146  ((string= todo "NOPE") (setq res (cons 4 prio))))
147  (unless res (setq res (cons 0 prio)))
148  res))
149 
150 (defun org-sort-compare-todo-priority (a b)
151  "Given two cons consisting of (TODO . PRIO), return t if A
152  should come before B."
153  (message "a: %S b: %S" a b)
154  (cond
155  ((< (car a) (car b)) t)
156  ((> (car a) (car b)) nil)
157  ((= (car a) (car b))
158  (cond
159  ((< (cdr a) (cdr b)) t)
160  ((> (cdr a) (cdr b)) nil)))))
161 
162 
163 (defun org-inbox-sort ()
164  "Sort the current heading by todo order followed by priority."
165  (interactive)
166  (with-inbox-buffer
167  (org-sort-entries nil ?f #'org-sort-todo-priority #'org-sort-compare-todo-priority)))
168 
169 (defun org-inbox-compact ()
170  "Assign missing IDs and creation dates, archive DONE tasks."
171  (interactive)
172  (with-inbox-buffer
173  (org-id-update-id-locations)
174  (org-id-add-to-headlines-in-file)
175  (org-archive-done)
176  (org-map-entries #'org-expiry-insert-created)
177  (org-inbox-sort)))
178 
179 (defun org-inbox-open ()
180  "Open `org-inbox-file' or switch to its buffer if already open."
181  (interactive)
182  (if-let ((inbox (get-buffer org-inbox-buffer-name)))
183  (switch-to-buffer inbox)
184  (find-file org-inbox-file)
185  (rename-buffer org-inbox-buffer-name)))
186 
187 (defun org-inbox-close ()
188  "Close the org-inbox and associated buffers."
189  (interactive)
190  (when-let ((inbox (get-buffer org-inbox-buffer-name)))
191  (kill-buffer inbox)))
192 
193 ;;; dblocks
194 
195 ;; summary
196 (defun org-dblock-write:summary (params)
197  "Generate a file or heading summary section.")
198 
199 (defun org-summary ()
200  "Insert or update a summary section.")
201 
202 (defun org-inbox-configure-dblock ()
203  "Configure the current org-inbox-dblock at point."
204  (interactive)
205  (with-demoted-errors "Error: %S"
206  (let* ((beginning (org-beginning-of-dblock))
207  (parameters (org-prepare-dblock)))
208  (org-inbox-show-config-buffer (current-buffer) beginning parameters))))
209 
210 ;;; ui
211 (defun org-inbox-show-config (&optional buffer position parameters)
212  (interactive)
213  (switch-to-buffer org-inbox-config-buffer-name)
214  (erase-buffer)
215  (remove-overlays)
216  (widget-insert "\n\n")
217  (widget-create 'push-button
218  :notify (lambda(_widget &rest _ignore)
219  (with-current-buffer buffer
220  (goto-char position)
221  )
222  (kill-buffer)
223  (org-ctrl-c-ctrl-c))
224  (propertize "Apply" 'face 'font-lock-comment-face))
225  (widget-insert " ")
226  (widget-create 'push-button
227  :notify (lambda (_widget &rest _ignore)
228  (kill-buffer))
229  (propertize "Cancel" 'face 'font-lock-string-face))
230  (use-local-map widget-keymap)
231  (widget-setup))
232 
233 (provide 'inbox)
234 ;; inbox.el ends here