changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > core / emacs/lib/inbox.el

changeset 642: f58f3b88c49e
parent: 48bcbca019e6
child: af486e0a40c9
author: Richard Westhaver <ellis@rwest.io>
date: Sun, 08 Sep 2024 21:14:30 -0400
permissions: -rw-r--r--
description: move project-info to scrum
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) "* NOTE %^{item}\n%a")
75  ("l" "inbox-link" entry (file ,org-inbox-file)
76  "* LINK %l")
77  ("L" "inbox-protocol-link" entry (file ,org-inbox-file)
78  "* LINK [[%:link][%:description]]\n%:initial" :empty-lines 1)
79  ("w" "inbox-web-link" entry (file ,org-inbox-file)
80  "* LINK %?"
81  :hook (lambda ()
82  (goto-char (pos-eol))
83  (org-web-tools-insert-link-for-url (org-web-tools--get-first-url))))
84  ("1" "current-task-item" item (clock) "%i%?")
85  ("2" "current-task-checkbox" checkitem (clock) "%i%?")
86  ("3" "current-task-region" plain (clock) "%i" :immediate-finish t :empty-lines 1)
87  ("4" "current-task-kill" plain (clock) "%c" :immediate-finish t :empty-lines 1)
88  ("l" "log" item (file+headline "log.org" "log") "%U %?" :prepend t)
89  ("s" "secret" table-line (file+function "krypt" org-ask-location) "| %^{key} | %^{val} |" :immediate-finish t :kill-buffer t)
90  ("N" "note-item" plain (file+function "notes.org" org-ask-location) "%?")))
91 
92 (add-hook 'org-after-todo-state-change-hook #'org-id-get-create)
93 (add-hook 'org-after-todo-state-change-hook #'org-expiry-insert-created)
94 
95 (setq org-default-notes-file (join-paths org-directory "inbox.org")
96  org-capture-use-agenda-date t
97  org-archive-location "archive.org::")
98 
99 ;;; Utils
100 ;; `org-archive-all-done' doesn't work the way we want. This function
101 ;; will archive all done tasks in the current subtree, or the whole file
102 ;; if prefix arg is given.
103 (defun org-archive-done (&optional scope)
104  "archive all tasks with todo-state of 'DONE' or 'NOPE'."
105  (interactive "P")
106  (org-map-entries
107  (lambda ()
108  (org-archive-subtree)
109  (setq org-map-continue-from (org-element-property :begin (org-element-at-point))))
110  "/+DONE|NOPE" scope))
111 
112 (defun org-children-done ()
113  "Mark all sub-tasks in this heading as 'DONE'."
114  (interactive)
115  (org-map-entries
116  (lambda ()
117  (unless (= (org-current-level) 1)
118  (org-todo "DONE"))
119  nil 'tree)))
120 
121 (defmacro with-inbox-buffer (&rest body)
122  `(save-excursion
123  (with-current-buffer (find-file org-inbox-file)
124  ,@body)))
125 
126 (defun org-sort-todo-priority ()
127  "Sorting function used by `org-sort' to sort by todo order
128  followed by priority. Returns a pair of numbers (TODO . PRIO)."
129  (let* ((elt (cadr (org-element-at-point)))
130  (todo (when-let ((kw (plist-get elt :todo-keyword)))
131  (when (stringp kw)
132  (substring-no-properties kw))))
133  (prio (pcase (plist-get elt :priority)
134  ("A" 1)
135  ("B" 2)
136  ("C" 3)
137  (t 2)))
138  (res))
139  ;; FIXME todo states shouldn't be hardcoded
140  (cond
141  ((null todo) (setq res (cons 3 prio)))
142  ((string= todo "WATCH") (setq res (cons 3 prio)))
143  ((string= todo "WAIT") (setq res (cons 1 prio)))
144  ((string= todo "HOLD") (setq res (cons 1 prio)))
145  ((string= todo "WIP") (setq res (cons 1 prio)))
146  ((string= todo "GOTO") (setq res (cons 2 prio)))
147  ((string= todo "TODO") (setq res (cons 2 prio)))
148  ((string= todo "RESEARCH") (setq res (cons 3 prio)))
149  ((string= todo "DONE") (setq res (cons 4 prio)))
150  ((string= todo "NOPE") (setq res (cons 4 prio))))
151  (unless res (setq res (cons 0 prio)))
152  res))
153 
154 (defun org-sort-compare-todo-priority (a b)
155  "Given two cons consisting of (TODO . PRIO), return t if A
156  should come before B."
157  (message "a: %S b: %S" a b)
158  (cond
159  ((< (car a) (car b)) t)
160  ((> (car a) (car b)) nil)
161  ((= (car a) (car b))
162  (cond
163  ((< (cdr a) (cdr b)) t)
164  ((> (cdr a) (cdr b)) nil)))))
165 
166 
167 (defun org-inbox-sort ()
168  "Sort the current heading by todo order followed by priority."
169  (interactive)
170  (with-inbox-buffer
171  (org-sort-entries nil ?f #'org-sort-todo-priority #'org-sort-compare-todo-priority)))
172 
173 (defun org-inbox-compact ()
174  "Assign missing IDs and creation dates, archive DONE tasks."
175  (interactive)
176  (with-inbox-buffer
177  (org-id-update-id-locations)
178  (org-id-add-to-headlines-in-file)
179  (org-archive-done)
180  (org-map-entries #'org-expiry-insert-created)
181  (org-inbox-sort)))
182 
183 (defun org-inbox-open ()
184  "Open `org-inbox-file' or switch to its buffer if already open."
185  (interactive)
186  (if-let ((inbox (get-buffer org-inbox-buffer-name)))
187  (switch-to-buffer inbox)
188  (find-file org-inbox-file)
189  (rename-buffer org-inbox-buffer-name)))
190 
191 (defun org-inbox-close ()
192  "Close the org-inbox and associated buffers."
193  (interactive)
194  (when-let ((inbox (get-buffer org-inbox-buffer-name)))
195  (kill-buffer inbox)))
196 
197 ;;; dblocks
198 
199 ;; summary
200 (defun org-dblock-write:summary (params)
201  "Generate a file or heading summary section.")
202 
203 (defun org-summary ()
204  "Insert or update a summary section.")
205 
206 (defun org-inbox-configure-dblock ()
207  "Configure the current org-inbox-dblock at point."
208  (interactive)
209  (with-demoted-errors "Error: %S"
210  (let* ((beginning (org-beginning-of-dblock))
211  (parameters (org-prepare-dblock)))
212  (org-inbox-show-config-buffer (current-buffer) beginning parameters))))
213 
214 ;;; ui
215 (defun org-inbox-show-config (&optional buffer position parameters)
216  (interactive)
217  (switch-to-buffer org-inbox-config-buffer-name)
218  (erase-buffer)
219  (remove-overlays)
220  (widget-insert "\n\n")
221  (widget-create 'push-button
222  :notify (lambda(_widget &rest _ignore)
223  (with-current-buffer buffer
224  (goto-char position)
225  )
226  (kill-buffer)
227  (org-ctrl-c-ctrl-c))
228  (propertize "Apply" 'face 'font-lock-comment-face))
229  (widget-insert " ")
230  (widget-create 'push-button
231  :notify (lambda (_widget &rest _ignore)
232  (kill-buffer))
233  (propertize "Cancel" 'face 'font-lock-string-face))
234  (use-local-map widget-keymap)
235  (widget-setup))
236 
237 (provide 'inbox)
238 ;; inbox.el ends here