Hi Nicolas,

On 07/09/2018 01:22 AM, Nicolas Goaziou wrote:
It looks good. Could you send it on this ML as a patch so I can comment
it more conveniently?

Since you want to comment I guess you want the patch in the e-mail body rather than attached. Here goes nothing.

From bb02cd6c00b32155c0a25f409f1bfa4160b2ddcd Mon Sep 17 00:00:00 2001
From: Jesse Johnson <holocronwea...@gmail.com>
Date: Sun, 22 Apr 2018 18:12:54 -0700
Subject: [PATCH] Add priority inheritance

* New org-use-priority-inheritance defcustom to toggle inheritance.
* org-get-priority now takes a pos and implements inheritance.
* org-get-priority-function can make use of inheritance by returning t.
* org-agenda-fix-displayed-priority ensures inherited priority is
  visible.
* Updates where priority is used so that inheritance is respected.  As
  a side effect, org-get-priority-function is now more widely
  respected.
---
 lisp/org-agenda.el | 117 +++++++++++++++++++++++++++++++++--------------------
 lisp/org-habit.el  |  16 ++++----
 lisp/org.el        | 102 ++++++++++++++++++++++++++++++++++------------
 3 files changed, 157 insertions(+), 78 deletions(-)

diff --git a/lisp/org-agenda.el b/lisp/org-agenda.el
index eaeddb6..e18e73d 100644
--- a/lisp/org-agenda.el
+++ b/lisp/org-agenda.el
@@ -4581,6 +4581,7 @@ is active."
             (setq marker (org-agenda-new-marker (point))
                   category (org-get-category)
                   level (make-string (org-reduced-level (org-outline-level)) ? )
+                              priority (org-get-priority)
                   inherited-tags
                   (or (eq org-agenda-show-inherited-tags 'always)
                   (and (listp org-agenda-show-inherited-tags)
@@ -4593,13 +4594,13 @@ is active."
                    ""
                    (buffer-substring-no-properties
                     beg1 (point-at-eol))
-                   level category tags t))
+                   level category priority tags t))
             (org-add-props txt props
               'org-marker marker 'org-hd-marker marker
               'org-todo-regexp org-todo-regexp
               'level level
               'org-complex-heading-regexp org-complex-heading-regexp
-              'priority 1000
+              'priority priority
               'type "search")
             (push txt ee)
             (goto-char (1- end))))))))))
@@ -5078,7 +5079,7 @@ of what a project is and how to check if it stuck, customize the variable
       (setq entries
         (mapcar
          (lambda (x)
-           (setq x (org-agenda-format-item "" x nil "Diary" nil 'time))
+           (setq x (org-agenda-format-item "" x nil "Diary" nil nil 'time))
            ;; Extend the text properties to the beginning of the line
            (org-add-props x (text-properties-at (1- (length x)) x)
          'type "diary" 'date date 'face 'org-agenda-diary))
@@ -5361,6 +5362,7 @@ and the timestamp type relevant for the sorting strategy in
           ts-date (car ts-date-pair)
           ts-date-type (cdr ts-date-pair)
           txt (org-trim (buffer-substring (match-beginning 2) (match-end 0)))
+              priority (1+ (org-get-priority))
           inherited-tags
           (or (eq org-agenda-show-inherited-tags 'always)
           (and (listp org-agenda-show-inherited-tags)
@@ -5370,8 +5372,7 @@ and the timestamp type relevant for the sorting strategy in
                (memq 'todo org-agenda-use-tag-inheritance))))
           tags (org-get-tags nil (not inherited-tags))
           level (make-string (org-reduced-level (org-outline-level)) ? )
-          txt (org-agenda-format-item "" txt level category tags t)
-          priority (1+ (org-get-priority txt)))
+          txt (org-agenda-format-item "" txt level category priority tags t))
     (org-add-props txt props
       'org-marker marker 'org-hd-marker marker
       'priority priority
@@ -5570,6 +5571,9 @@ displayed in agenda view."
                (assq (point) deadline-position-alist))
           (throw :skip nil))
         (let* ((category (org-get-category pos))
+                   (priority (if habit?
+                                 (org-habit-get-priority (org-habit-parse-todo))
+                               (org-get-priority item)))
            (inherited-tags
             (or (eq org-agenda-show-inherited-tags 'always)
             (and (consp org-agenda-show-inherited-tags)
@@ -5588,11 +5592,10 @@ displayed in agenda view."
            (item
             (org-agenda-format-item
              (and inactive? org-agenda-inactive-leader)
-             head level category tags time-stamp org-ts-regexp habit?)))
+             head level category priority tags
+                     time-stamp org-ts-regexp habit?)))
           (org-add-props item props
-        'priority (if habit?
-                  (org-habit-get-priority (org-habit-parse-todo))
-                (org-get-priority item))
+        'priority priority
         'org-marker (org-agenda-new-marker pos)
         'org-hd-marker (org-agenda-new-marker)
         'date date
@@ -5635,6 +5638,7 @@ displayed in agenda view."
       (setq marker (org-agenda-new-marker beg)
         level (make-string (org-reduced-level (org-outline-level)) ? )
         category (org-get-category beg)
+                priority (org-get-priority)
         inherited-tags
         (or (eq org-agenda-show-inherited-tags 'always)
             (and (listp org-agenda-show-inherited-tags)
@@ -5657,7 +5661,7 @@ displayed in agenda view."
         (if (string-match "\\S-" r)
         (setq txt r)
           (setq txt "SEXP entry returned empty string"))
-        (setq txt (org-agenda-format-item extra txt level category tags 'time)) +        (setq txt (org-agenda-format-item extra txt level category priority tags 'time))
         (org-add-props txt props 'org-marker marker
                'date date 'todo-state todo-state
                'level level 'type "sexp" 'warntime warntime)
@@ -5785,6 +5789,7 @@ then those holidays will be skipped."
           (throw :skip nil)
         (goto-char (match-beginning 0))
         (setq hdmarker (org-agenda-new-marker)
+                  priority (org-get-priority)
           inherited-tags
           (or (eq org-agenda-show-inherited-tags 'always)
               (and (listp org-agenda-show-inherited-tags)
@@ -5806,8 +5811,7 @@ then those holidays will be skipped."
             (closedp "Closed:    ")
             (statep (concat "State:     (" state ")"))
             (t (concat "Clocked:   (" clocked  ")")))
-               txt level category tags timestr)))
-      (setq priority 100000)
+               txt level category priority tags timestr)))
       (org-add-props txt props
         'org-marker marker 'org-hd-marker hdmarker 'face 'org-agenda-done
         'priority priority 'level level
@@ -6032,6 +6036,13 @@ specification like [h]h:mm."
            (level (make-string (org-reduced-level (org-outline-level))
                        ?\s))
            (head (buffer-substring (point) (line-end-position)))
+                   (priority
+                    ;; Adjust priority to today reminders about deadlines.
+                    ;; Overdue deadlines get the highest priority
+                    ;; increase, then imminent deadlines and eventually
+                    ;; more distant deadlines.
+                    (let ((adjust (if today? (- diff) 0)))
+                      (+ adjust (org-get-priority))))
            (inherited-tags
             (or (eq org-agenda-show-inherited-tags 'always)
             (and (listp org-agenda-show-inherited-tags)
@@ -6059,7 +6070,7 @@ specification like [h]h:mm."
             ((and today? (< deadline today)) (format past (- diff)))
             ((and today? (> deadline today)) (format future diff))
             (t now)))
-             head level category tags time))
+             head level category priority tags time))
            (face (org-agenda-deadline-face
               (- 1 (/ (float diff) (max wdays 1)))))
            (upcoming? (and today? (> deadline today)))
@@ -6070,13 +6081,7 @@ specification like [h]h:mm."
         'warntime warntime
         'level level
         'ts-date deadline
-        'priority
-        ;; Adjust priority to today reminders about deadlines.
-        ;; Overdue deadlines get the highest priority
-        ;; increase, then imminent deadlines and eventually
-        ;; more distant deadlines.
-        (let ((adjust (if today? (- diff) 0)))
-          (+ adjust (org-get-priority item)))
+        'priority priority
         'todo-state todo-state
         'type (if upcoming? "upcoming-deadline" "deadline")
         'date (if upcoming? date deadline)
@@ -6222,16 +6227,19 @@ scheduled items with an hour specification like [h]h:mm."
       ;; Skip habits if `org-habit-show-habits' is nil, or if we
       ;; only show them for today.  Also skip done habits.
       (when (and habitp
-             (or donep
-             (not (bound-and-true-p org-habit-show-habits))
-             (and (not todayp)
-                  (bound-and-true-p
-                   org-habit-show-habits-only-for-today))))
+                     (or donep
+                         (not (bound-and-true-p org-habit-show-habits))
+                         (and (not todayp)
+                              (bound-and-true-p
+ org-habit-show-habits-only-for-today))))
         (throw :skip nil))
       (save-excursion
         (re-search-backward "^\\*+[ \t]+" nil t)
         (goto-char (match-end 0))
         (let* ((category (org-get-category))
+                   (habit (and habitp (org-habit-parse-todo)))
+                   (priority (if habit (org-habit-get-priority habit)
+                               (+ 99 diff (org-get-priority))))
            (inherited-tags
             (or (eq org-agenda-show-inherited-tags 'always)
             (and (listp org-agenda-show-inherited-tags)
@@ -6259,12 +6267,11 @@ scheduled items with an hour specification like [h]h:mm."
                (if (and todayp pastschedp)
                (format past diff)
              first))
-             head level category tags time nil habitp))
+             head level category priority tags time nil habitp))
            (face (cond ((and (not habitp) pastschedp)
                 'org-scheduled-previously)
                    (todayp 'org-scheduled-today)
-                   (t 'org-scheduled)))
-           (habitp (and habitp (org-habit-parse-todo))))
+                   (t 'org-scheduled))))
           (org-add-props item props
         'undone-face face
         'face (if donep 'org-agenda-done face)
@@ -6275,9 +6282,8 @@ scheduled items with an hour specification like [h]h:mm."
         'ts-date schedule
         'warntime warntime
         'level level
-        'priority (if habitp (org-habit-get-priority habitp)
-                (+ 99 diff (org-get-priority item)))
-        'org-habit-p habitp
+        'priority priority
+        'org-habit-p habit
         'todo-state todo-state)
           (push item scheduled-items))))))
     (nreverse scheduled-items)))
@@ -6295,7 +6301,7 @@ scheduled items with an hour specification like [h]h:mm."
      (regexp org-tr-regexp)
      (d0 (calendar-absolute-from-gregorian date))
      marker hdmarker ee txt d1 d2 s1 s2 category
-     level todo-state tags pos head donep inherited-tags)
+     level priority todo-state tags pos head donep inherited-tags)
     (goto-char (point-min))
     (while (re-search-forward regexp nil t)
       (catch :skip
@@ -6346,7 +6352,7 @@ scheduled items with an hour specification like [h]h:mm."
               (and (eq org-agenda-show-inherited-tags t)
                    (or (eq org-agenda-use-tag-inheritance t)
                    (memq 'agenda org-agenda-use-tag-inheritance))))
-
+              priority (org-get-priority)
               tags (org-get-tags nil (not inherited-tags)))
         (setq level (make-string (org-reduced-level (org-outline-level)) ? ))
         (looking-at "\\*+[ \t]+\\(.*\\)")
@@ -6363,7 +6369,7 @@ scheduled items with an hour specification like [h]h:mm."
                   (nth (if (= d1 d2) 0 1)
                    org-agenda-timerange-leaders)
                   (1+ (- d0 d1)) (1+ (- d2 d1)))
-                 head level category tags
+                 head level category priority tags
                  (cond ((and (= d1 d0) (= d2 d0))
                     (concat "<" start-time ">--<" end-time ">"))
                                    ((= d1 d0)
@@ -6376,11 +6382,11 @@ scheduled items with an hour specification like [h]h:mm."
         'type "block" 'date date
         'level level
         'todo-state todo-state
-        'priority (org-get-priority txt))
+        'priority priority)
           (push txt ee))))
-    (goto-char pos)))
-    ;; Sort the entries by expiration date.
-    (nreverse ee)))
+      (goto-char pos)))
+  ;; Sort the entries by expiration date.
+  (nreverse ee)))

 ;;; Agenda presentation and sorting

@@ -6409,8 +6415,9 @@ The flag is set if the currently compiled format contains a `%b'.")
       (cl-return (cadr entry))
     (cl-return (apply #'create-image (cdr entry)))))))

-(defun org-agenda-format-item (extra txt &optional level category tags dotime
-                     remove-re habitp)
+(defun org-agenda-format-item (extra txt
+                                     &optional level category priority tags
+                                     dotime remove-re habitp)
   "Format TXT to be inserted into the agenda buffer.
 In particular, add the prefix and corresponding text properties.

@@ -6419,10 +6426,11 @@ LEVEL may be a string to replace the `%l' specifier.
 CATEGORY (a string, a symbol or nil) may be used to overrule the default
 category taken from local variable or file name.  It will replace the `%c'
 specifier in the format.
+PRIORITY can be the integer priority of the headline.
+TAGS can be the tags of the headline.
 DOTIME, when non-nil, indicates that a time-of-day should be extracted from
 TXT for sorting of this entry, and for the `%t' specifier in the format.
 When DOTIME is a string, this string is searched for a time before TXT is.
-TAGS can be the tags of the headline.
 Any match of REMOVE-RE will be removed from TXT."
   ;; We keep the org-prefix-* variable values along with a compiled
   ;; formatter, so that multiple agendas existing at the same time do
@@ -6440,6 +6448,9 @@ Any match of REMOVE-RE will be removed from TXT."
       ;; Diary entries sometimes have extra whitespace at the beginning
       (setq txt (org-trim txt))

+      ;; Fix the priority part in txt
+      (setq txt (org-agenda-fix-displayed-priority txt priority))
+
       ;; Fix the tags part in txt
       (setq txt (org-agenda-fix-displayed-tags
          txt tags
@@ -6611,6 +6622,20 @@ The modified list may contain inherited tags, and tags matched by
               (if have-i "::" ":"))))))
   txt)

+(defun org-agenda-fix-displayed-priority (txt priority)
+  "Modifies TXT to show correct PRIORITY.
+Respects `org-use-priority-inheritance' by adding PRIORITY if not
+already present. No change is made if `org-get-priority-function'
+is non-nil since TXT may be using non-standard priority cookies."
+  (when (and priority
+             org-use-priority-inheritance
+             (not (functionp org-get-priority-function))
+             (not (string-match org-priority-regexp txt)))
+    (let ((priority-str
+           (char-to-string (org-priority-integer-to-char priority))))
+      (setq txt (concat "[#" priority-str "] " txt))))
+  txt)
+
 (defun org-downcase-keep-props (s)
   (let ((props (text-properties-at 0 s)))
     (setq s (downcase s))
@@ -6646,14 +6671,14 @@ TODAYP is t when the current agenda view is on today."
     (unless (and remove (member time have))
       (setq time (replace-regexp-in-string " " "0" (format "%04s" time)))
       (push (org-agenda-format-item
-         nil string nil "" nil
+         nil string nil "" nil nil nil
          (concat (substring time 0 -2) ":" (substring time -2)))
         new)
       (put-text-property
        2 (length (car new)) 'face 'org-time-grid (car new))))
       (when (and todayp org-agenda-show-current-time-in-grid)
     (push (org-agenda-format-item
-           nil org-agenda-current-time-string nil "" nil
+           nil org-agenda-current-time-string nil "" nil nil nil
            (format-time-string "%H:%M "))
           new)
     (put-text-property
@@ -8932,6 +8957,10 @@ If FORCE-TAGS is non nil, the car of it returns the new tags."
   (let* ((inhibit-read-only t)
      (line (org-current-line))
      (org-agenda-buffer (current-buffer))
+         (priority (with-current-buffer (marker-buffer hdmarker)
+                     (org-with-wide-buffer
+                      (goto-char hdmarker)
+                      (org-get-priority))))
      (thetags (with-current-buffer (marker-buffer hdmarker)
             (org-get-tags hdmarker)))
      props m pl undone-face done-face finish new dotime level cat tags)
@@ -8955,7 +8984,7 @@ If FORCE-TAGS is non nil, the car of it returns the new tags."
               (extra (org-get-at-bol 'extra)))
           (with-current-buffer (marker-buffer hdmarker)
             (org-with-wide-buffer
-             (org-agenda-format-item extra newhead level cat tags dotime)))) +             (org-agenda-format-item extra newhead level cat priority tags dotime))))          pl (text-property-any (point-at-bol) (point-at-eol) 'org-heading t)
         undone-face (org-get-at-bol 'undone-face)
         done-face (org-get-at-bol 'done-face))
diff --git a/lisp/org-habit.el b/lisp/org-habit.el
index 375714e..2d5d0d8 100644
--- a/lisp/org-habit.el
+++ b/lisp/org-habit.el
@@ -248,23 +248,23 @@ This list represents a \"habit\" for the rest of this module."
 (defsubst org-habit-get-priority (habit &optional moment)
   "Determine the relative priority of a habit.
 This must take into account not just urgency, but consistency as well."
-  (let ((pri 1000)
-    (now (if moment (time-to-days moment) (org-today)))
-    (scheduled (org-habit-scheduled habit))
-    (deadline (org-habit-deadline habit)))
+  (let ((pri (org-get-priority))
+        (now (if moment (time-to-days moment) (org-today)))
+        (scheduled (org-habit-scheduled habit))
+        (deadline (org-habit-deadline habit)))
     ;; add 10 for every day past the scheduled date, and subtract for every
     ;; day before it
     (setq pri (+ pri (* (- now scheduled) 10)))
     ;; add 50 if the deadline is today
     (if (and (/= scheduled deadline)
-         (= now deadline))
-    (setq pri (+ pri 50)))
+             (= now deadline))
+        (setq pri (+ pri 50)))
     ;; add 100 for every day beyond the deadline date, and subtract 10 for
     ;; every day before it
     (let ((slip (- now (1- deadline))))
       (if (> slip 0)
-      (setq pri (+ pri (* slip 100)))
-    (setq pri (+ pri (* slip 10)))))
+          (setq pri (+ pri (* slip 100)))
+        (setq pri (+ pri (* slip 10)))))
     pri))

 (defun org-habit-get-faces (habit &optional now-days scheduled-days donep)
diff --git a/lisp/org.el b/lisp/org.el
index 66eb2f3..768b84a 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -3175,18 +3175,46 @@ See also `org-default-priority'."
   :type 'boolean)

 (defcustom org-get-priority-function nil
-  "Function to extract the priority from a string.
-The string is normally the headline.  If this is nil Org computes the
-priority from the priority cookie like [#A] in the headline.  It returns
-an integer, increasing by 1000 for each priority level.
-The user can set a different function here, which should take a string
-as an argument and return the numeric priority."
+  "Function to extract the priority from current line.
+The line is always a headline.
+
+If this is nil Org computes the priority of the headline from a
+priority cookie like [#A]. It returns an integer, increasing by
+1000 for each priority level (see
+`org-priority-char-to-integer').
+
+The user can set a different function here, which should process
+the current line and return one of:
+
+- an integer priority
+- nil if current line is not a header or otherwise has no
+associated priority
+- t if the `org-default-priority' should be used or the priority can be
+inherited from its parent
+
+Priority can only be inherited if `org-use-priority-inheritance' is
+non-nil."
   :group 'org-priorities
   :version "24.1"
   :type '(choice
       (const nil)
       (function)))

+(defcustom org-use-priority-inheritance nil
+  "Whether headline priority is inherited from its parents.
+
+If non-nil then the first explicit priority found when searching
+up the headline tree applies.  Thus a child headline can override
+its parent's priority.
+
+When nil, explicit priorities only apply to the headline they are
+given on.
+
+Regardless of setting, if no explicit priority is found then the
+default priority is used."
+  :group 'org-priorities
+  :type 'boolean)
+
 (defgroup org-time nil
   "Options concerning time stamps and deadlines in Org mode."
   :tag "Org Time"
@@ -13633,22 +13661,43 @@ and by additional input from the age of a schedules or deadline entry."
   (interactive)
   (let ((pri (if (eq major-mode 'org-agenda-mode)
          (org-get-at-bol 'priority)
-           (save-excursion
-         (save-match-data
-           (beginning-of-line)
-           (and (looking-at org-heading-regexp)
-            (org-get-priority (match-string 0))))))))
+           (org-get-priority))))
     (message "Priority is %d" (if pri pri -1000))))

-(defun org-get-priority (s)
-  "Find priority cookie and return priority."
-  (save-match-data
-    (if (functionp org-get-priority-function)
-    (funcall org-get-priority-function)
-      (if (not (string-match org-priority-regexp s))
-      (* 1000 (- org-lowest-priority org-default-priority))
-    (* 1000 (- org-lowest-priority
-           (string-to-char (match-string 2 s))))))))
+(defun org-priority-char-to-integer (character)
+  "Convert priority CHARACTER to an integer priority."
+  (* 1000 (- org-lowest-priority character)))
+
+(defun org-priority-integer-to-char (integer)
+  "Convert priority INTEGER to a character priority."
+  (- org-lowest-priority (/ integer 1000)))
+
+(defun org-get-priority (&optional pos local)
+  "Get integer priority at POS.
+POS defaults to point.  If LOCAL is non-nil priority inheritance
+is ignored regardless of the value of
+`org-use-priority-inheritance'.  Returns nil if no priority can be
+determined at POS."
+  (save-excursion
+    (save-restriction
+      (widen)
+      (goto-char (or pos (point)))
+      (beginning-of-line)
+      (if (not (looking-at org-heading-regexp))
+      (return nil)
+    (save-match-data
+      (cl-loop
+       (if (functionp org-get-priority-function)
+           (let ((priority (funcall org-get-priority-function)))
+         (unless (eq priority t)
+           (return priority)))
+         (when (looking-at org-priority-regexp)
+           (return (org-priority-char-to-integer
+            (string-to-char (match-string-no-properties 2))))))
+       (unless (and (not local)
+            org-use-priority-inheritance
+            (org-up-heading-safe))
+         (return (org-priority-char-to-integer org-default-priority)))))))))

 ;;;; Tags

@@ -13713,6 +13762,7 @@ headlines matching this string."
                    (or (buffer-file-name (buffer-base-buffer))
                    (buffer-name (buffer-base-buffer)))))))
      (org-map-continue-from nil)
+         priority
          lspos tags tags-list
      (tags-alist (list (cons 0 org-file-tags)))
      (llast 0) rtn rtn1 level category i txt
@@ -13800,7 +13850,8 @@ headlines matching this string."
             (match-beginning 1) (match-end 1)))
           (org-show-context 'tags-tree))
          ((eq action 'agenda)
-          (setq txt (org-agenda-format-item
+          (setq priority (org-get-priority)
+            txt (org-agenda-format-item
              ""
              (concat
               (if (eq org-tags-match-list-sublevels 'indented)
@@ -13808,8 +13859,8 @@ headlines matching this string."
               (org-get-heading))
              (make-string level ?\s)
              category
-             tags-list)
-            priority (org-get-priority txt))
+                         priority
+             tags-list))
           (goto-char lspos)
           (setq marker (org-agenda-new-marker))
           (org-add-props txt props
@@ -15066,9 +15117,8 @@ strings."
           (when specific (throw 'exit props)))
         (when (or (not specific) (string= specific "PRIORITY"))
           (push (cons "PRIORITY"
-              (if (looking-at org-priority-regexp)
-                  (match-string-no-properties 2)
-                (char-to-string org-default-priority)))
+                          (char-to-string
+                           (org-priority-integer-to-char (org-get-priority))))
             props)
           (when specific (throw 'exit props)))
         (when (or (not specific) (string= specific "FILE"))
--
2.7.4


Reply via email to