alphapapa / org-ql

A searching tool for Org-mode, including custom query languages, commands, saved searches and agenda-like views, etc.
GNU General Public License v3.0
1.42k stars 110 forks source link

sorting by ancestor priorities #173

Open stfl opened 3 years ago

stfl commented 3 years ago

I am using priorities only on PROJ headings but I want the priorities to be reflected on the children headings. The feature I'd like would be priority inheritance but it seems that org-mode does not support that.

So I tried to get this behavior with org-ql to simply sort the entries by the highest priority up the heading tree. I managed to get the min prio on the tree but it requires my to have my cursor on the heading or in that buffer because I am traversing with (org-up-heading-safe)

I am struggling with elisp so that's the best I could get up with for now.

(defun org-priority-show-ancestors (item)
    (let* ((prio-raw (org-element-property :priority item))
           (prio (cond (prio-raw prio-raw)
                       (t (+ 0.5 org-priority-default)))))  ;; display empty prio below default
      ;; move up to the parent
      (if (org-up-heading-safe)
          (cons prio (org-priority-show-ancestors (org-element-at-point)))
        (list prio))))

(defun org-priority-min-ancestors (item)
  (org-with-wide-buffer (apply 'min (org-priority-show-ancestors item))))

(defun org-priority-min-ancestors-current ()
  (org-priority-min-ancestors (org-element-at-point)))

(defun org-ql--min-ancestor-priority< (a b)
  "Return non-nil if A's priority is higher than B's.
A and B are Org headline elements."
  (cl-macrolet ((priority (item)
                          `(org-priority-min-ancestors ,item)))
    ;; NOTE: Priorities are numbers in Org elements.  This might differ from the priority selector logic.
    (let ((a-priority (priority a))
          (b-priority (priority b)))
      (cond ((and a-priority b-priority)
             (< a-priority b-priority))
            (a-priority t)
            (b-priority nil)))))

The sorting works but only for the priority of the current element while considering org-priority-default which seems to be ignored in the default prio sorting.

There are most likely better ways to get up to the parent heading based on item. That's actually more of an org-mode question and me asking for help.

alphapapa commented 3 years ago

The feature I'd like would be priority inheritance but it seems that org-mode does not support that.

That's right, it doesn't.

That's actually more of an org-mode question and me asking for help.

I don't understand what you're asking. I see that code, but I don't know how you are using it or what you expect it to do.

Anyway, the concept you're probably missing is that you should use the markers in each element's plist (or its text properties) to access them in their respective buffers.

stfl commented 3 years ago

Sorry I wasn't clear in my description. What I want my code to do it iterate over the ancestor tree -> return a list of the priorities -> get the min value and use this value for sorting.

Thank you for the hint with the markers. I've seen that but I couldn't figure out how to set the cursor to this location in the buffer. I believe I need to move the cursor because - what I understand - (org-up-heading-safe) requires me to sit there.

I will give it another shot playing around some more. Thank you!

alphapapa commented 3 years ago

You can use the macro org-with-point-at with a marker as the argument. Something simple, like this, should work (untested):

(org-with-point-at marker
  (cl-loop maximizing (save-match-data
                        ;; TODO: Is the save-match-data above necessary?
                        (if (and (looking-at org-heading-regexp)
                                 (save-match-data
                                   (string-match org-priority-regexp (match-string 0))))
                            (org-get-priority (match-string 0))
                          org-default-priority))
           while (and (org-up-heading-safe)
                      (equal "PROJ" (nth 0 (org-heading-components))))))
stfl commented 3 years ago

Thank you very much for your support. I finally got around to try this out. I learned a lot about elisp today and this is what I came up with.

(defun stfl/org-ql-min-ancestor-priority< (a b)
  "Return non-nil if A's minimum ancestor priority is higher than B's.
A and B are Org headline elements.
org-default-priority is treated as lower than the same set value"
  (cl-macrolet ((priority (item)
                          `(org-with-point-at (org-element-property :org-marker ,item)
                             (stfl/org-min-ancestor-priority))))
    ;; NOTE: Priorities are numbers in Org elements.  This might differ from the priority selector logic.
    (let ((a-priority (priority a))
          (b-priority (priority b)))
      (cond ((and a-priority b-priority)
             (< a-priority b-priority))
            (a-priority t)
            (b-priority nil)))))

(defun stfl/org-min-ancestor-priority ()
    (cl-loop minimize (save-match-data (stfl/org-priority-or-default))
             while (and (not (equal "PROJ" (nth 2 (org-heading-components))))
                        (org-up-heading-safe))))

(defun stfl/org-priority-or-default ()
  (let* ((prio-raw (org-element-property :priority (org-element-at-point)))
         (prio (cond (prio-raw prio-raw)
                     (t (+ 0.5 org-priority-default)))))  ;; display empty prio below default
    prio))
stfl commented 3 years ago

Ok. it does not work. every time I reload the org-ql-view there is a couple of duplicate elements added.

stfl commented 3 years ago

The problem with adding additional elements also appears when I use a simple priority sorting that also considers org-default-priority The following is identical to org-ql--priority< while adding the (or ...)

(defun stfl/org-ql-priority-or-default< (a b)
  "Return non-nil if A's priority is higher than B's.
A and B are Org headline elements.
org-default-priority is treated as lower than the same set value"
  (cl-macrolet ((priority (item)
                    `(or (org-element-property :priority ,item)
                         (+ 0.5 org-default-priority))))
    ;; NOTE: Priorities are numbers in Org elements.  This might differ from the priority selector logic.
    (let ((a-priority (priority a))
          (b-priority (priority b)))
      (cond ((and a-priority b-priority)
             (< a-priority b-priority))
            (a-priority t)
            (b-priority nil)))))

Did I miss anything here?

alphapapa commented 3 years ago

Ok. it does not work. every time I reload the org-ql-view there is a couple of duplicate elements added.

I'm not sure, but maybe this is related to #186. Please let me know if the problem still happens.

BTW, a suggestion purely regarding style: since those supporting functions are only used in one place, having them as separate functions doesn't seem to make the code easier to follow. I'd suggest either inlining them or using cl-labels to define them within the parent function.

ParetoOptimalDev commented 1 year ago

@stfl I found your configuration here.

I don't quite get what this does though and was interested in trying it, but stopped halfway through due to other obligations.

Do you mind posting an example of what the end result looks like and how this sorts?