How can I apply org-sort-entries using a custom TODO keyword order?

Viewed 285

More specifically, I want to use a different keyword order than the order used for toggling between todo states.

I know I can use org-sort-entries nil ?O (or ?o) to sort by todo keyword, and this gets me nearly what I want. However, I'd like to use a different keyword ordering. The order of keywords I've defined in org-todo-keywords is the order I want to use for toggling between keyword states; but it's not the order I want to use for sorting my tasks. If I fix this by changing org-todo-keywords, then the state toggle (Shift + arrow keys) won't work the way I want. Is it possible to use a different keyword order for sorting vs state toggling?

I suspect there is a way to do it using org-sort-entries' GETKEY-FUNC and COMPARE-FUNC parameters, though I don't know elisp well enough to write it off the top of my head. Is there any simpler way - is it possible with just ?o somehow?

As a bonus, if there are headings in the list with NO todo keyword at all, I'd like to be able to specify where in the sort order they go. For instance, I might want my order to be (no-keyword), "STARTED", "TODO", "DONE" so that I end up with sorted rows like this:

* Project A
** Reference for this project: projectA-url.com
** STARTED Write the docs
** STARTED Build the second bit
** TODO Build the third bit
** DONE Set up the infrastructure
** DONE Build the first bit
1 Answers

In the end I couldn't find any way to do this using ?o. If someone can suggest a way to do that, that will be the better answer to my question.

I did implement this using ?f and a GETKEY-FUNC however. It looks like:

(defun todosort-key-func ()
  (cl-position
   (nth 2 (org-heading-components))
   '(nil "STARTED" "TODO" "DONE")
   :test 'equal
   )
  )

(org-map-entries
  '(org-sort-entries nil ?f 'todosort-key-func)
  "LEVEL=1" 'file)
  )

Here (nth 2 (org-heading-components) is retrieving the TODO keyword of the item (there might be a nicer way to do this?), and cl-position is finding its index in the hard-coded list, so that we sort by keywords in the specified order.

Edit: I also found I had a bug in that if one of my projects had no tasks yet, the whole org-map-entries call would fail part-way through since org-sort-entries would throw a "Nothing to sort" error. I would prefer that if we try to sort an empty list, we just return an empty list, rather than stop! I'm very new to this so there may be a better way, but I got round it by wrapping the whole thing in a condition-case to catch the error, like so:

(condition-case nil
  (org-map-entries
    '(org-sort-entries nil ?f 'todosort-key-func)
    "LEVEL=1" 'file)
    )
  (user-error (message "user-error, probably empty sort list, continuing"))
  )
Related