;;; org-urgency.el --- Smart, rules-based Org-agenda sorting -*- lexical-binding: t; -*- ;; Copyright (C) 2025 Simon Parri ;; Author: Simon Parri ;; 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 . ;;; 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 (= (get 'priority) prio) n)) (org-urgency-define priority () (* (get 'priority) 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