summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--README.org9
-rw-r--r--org-urgency.el177
3 files changed, 187 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b25c15b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*~
diff --git a/README.org b/README.org
new file mode 100644
index 0000000..f817dd7
--- /dev/null
+++ b/README.org
@@ -0,0 +1,9 @@
+#+TITLE: org-urgency
+#+SUBTITLE: Smart, rules-based Org-agenda sorting
+
+* =org-urgency=
+/Smart, rules-based Org-agenda sorting./
+
+=org-urgency= provides a system for ranking tasks known as “urgency.” Note that the term “urgency” as used in this project is the same as [[https://taskwarrior.org/docs/urgency/][Taskwarrior’s]], not the same as Org Agenda’s. Please read the package’s description and function docstrings to learn how to use it.
+
+*Warning: This project is still version < 1.0, and is therefore /not guaranteed to be stable/. I maintain it for myself, but do not have capacity to improve or test it beyond my needs.* If you find a bug, feel free to report it, but don’t expect there not to be any.
diff --git a/org-urgency.el b/org-urgency.el
new file mode 100644
index 0000000..4ae9923
--- /dev/null
+++ b/org-urgency.el
@@ -0,0 +1,177 @@
+;;; org-urgency.el --- Smart, rules-based Org-agenda sorting -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2025 Simon Parri
+
+;; Author: Simon Parri <simonparri@ganzeria.com>
+;; Keywords: calendar, outlines
+;; Package-Requires: ((emacs "24.3") (compat "25.1") (org "9") (seq "1"))
+;; Version: 0.50
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This code implements something like Taskwarrior's urgency system for Org
+;; Agenda. It has nothing to do with the "urgency" system mentioned in the
+;; Org manual. The name is taken from Taskwarrior.
+
+;; The general idea is that each entry will have a certain urgency, and the
+;; urgency is calculated by summing the non-nil results of calling each
+;; function in `org-urgency-functions'. Currently the best way to learn how
+;; to use the various urgency predicates is to read the source. See also
+;; `org-urgency-list' for a helper to make defining `org-urgency-functions'
+;; slightly less verbose, and `org-urgency-define' for how to define your own
+;; predicates.
+
+;;; Code:
+
+(require 'org)
+(require 'cl-macs)
+
+;;;###autoload
+(defgroup org-urgency ()
+ "Smart, rules-based Org-agenda sorting."
+ :group 'org-agenda)
+
+;;;###autoload
+(defcustom org-urgency-functions ()
+ "List of functions+arguments that determine the urgency of a given entry.
+
+Each element in this list should be of the form
+
+\\(FUNC . POST-ARGS)
+
+FUNC will be the function called, and it will be called with the first
+argument being the heading in question (as passed to the function in
+`org-agenda-cmp-user-defined'), and with POST-ARGS as the rest of the arguments.
+
+If, then, `org-urgency-functions' were set to
+
+\\((org-urgency-priority 10)
+ (org-urgency-state \"TODO\"))
+
+then the urgency of a heading H would be equivalent to
+
+\\(apply #\\='+
+ (seq-remove #\\='null
+ (list
+ (org-urgency-priority h 10)
+ (org-urgency-state h \"TODO\" 10))))
+
+See the relevant `org-urgency-' functions for details.
+
+Also see the function `org-urgency-list' for a shorthand way to assemble
+an appropriate list for this variable."
+ :type 'sexp)
+
+(defun org-urgency--get (h prop)
+ "Get PROP of H."
+ (get-text-property 1 prop h))
+
+;;;###autoload
+(defmacro org-urgency-define (name args &rest body)
+ "Define `org-urgency-NAME' as a function.
+
+Its lambda-list will be `(H ,@ARGS N), and BODY is the body of the
+function. (H is the heading, as passed to
+`org-agenda-cmp-user-defined', and N is the coefficient/number passed by
+the user.)
+
+Inside BODY, the function `get' is defined as a function that, given a
+symbol, uses `org-urgency--get' to get the corresponding property from
+H.
+
+BODY should evaluate to a number, which will be added to the heading's
+total urgency, or nil."
+ (declare (indent 2))
+ `(defun ,(intern (format "org-urgency-%s" name)) (h ,@args n)
+ "Weight function for use in `org-urgency-functions'."
+ (cl-flet ((get (prop)
+ (org-urgency--get h prop)))
+ ,@body)))
+
+(org-urgency-define priority= (prio)
+ (when (= (or (get 'priority) 1001) prio)
+ n))
+
+(org-urgency-define priority ()
+ (* (or (get 'priority) 1001) n))
+
+(org-urgency-define scheduled ()
+ (when (org-get-scheduled-time (get 'org-marker))
+ n))
+
+(org-urgency-define deadline ()
+ (when (org-get-deadline-time (get 'org-marker))
+ n))
+
+(org-urgency-define tag (tag)
+ (when (member tag (get 'tags))
+ n))
+
+(org-urgency-define habit ()
+ (require 'org-habit)
+ (when (org-is-habit-p (get 'org-marker))
+ n))
+
+(org-urgency-define state (state)
+ (when (equal (get 'todo-state) state)
+ n))
+
+(org-urgency-define effort ()
+ (* (or (get 'effort-minutes) 60.0) n))
+
+;;;###autoload
+(defun org-urgency-list (list)
+ "Process LIST to make it a suitable value for `org-urgency-functions'.
+
+LIST should be a list of lists whose `car' is a symbol and whose `cdr'
+is a list of arguments. An element such as
+
+\\(tag \"@home\" 10)
+
+will become
+
+\\(org-urgency-tag \"@home\" 10)"
+ (mapcar
+ (lambda (x)
+ (cons (intern (format "org-urgency-%s" (car x)))
+ (cdr x)))
+ list))
+
+(defun org-urgency-calculate (h)
+ "Calculate the urgency of H."
+ (if (not (equal (org-urgency--get h 'type) "diary"))
+ (thread-last
+ org-urgency-functions
+ (mapcar (lambda (x)
+ (cl-destructuring-bind (f &rest args) x
+ (apply f h args))))
+ (seq-remove #'null)
+ (apply #'+))
+ 0))
+
+;;;###autoload
+(defun org-urgency-compare (a b)
+ "Compare heading A to heading B.
+
+This function is suitable as a value for `org-agenda-cmp-user-defined'."
+ (let ((a (org-urgency-calculate a))
+ (b (org-urgency-calculate b)))
+ (cond ((> a b) +1)
+ ((< a b) -1)
+ (t nil))))
+
+(provide 'org-urgency)
+;;; org-urgency.el ends here