29

I often have a list like this:

* Main heading
** TODO [#A] Make world better
** TODO [#B] Make Emacs better 
** TODO [#B] Customize emacs 
** DONE [#C] some task
** TODO [#A] Launch rocket to mars

I would like to sort it according to the 'TODO' taskword first. Then the items inside the sorted TODO I would like to sort by priority. (it would then be nice to further sort by "Effort").

And I mean currently I can click on the main heading and sort the children already by either priority or by todo keyword, but not both.

Is sorting by both possible like sort-strategy?


Currently I have two headings

* Tasks
** TODO [#A] meh
** TODO [#B] meh2
* Completed.
** DONE [#B] meh3.

But the problem with this approach is that I have to constantly shuffle tasks around when I complete them.

[EDIT]
This is kinda similar to this except that I couldn't make sense of his answer to transfer it to my needs?

Leo Ufimtsev
  • 4,488
  • 3
  • 22
  • 45
  • 1
    The function `org-sort-entries` will either act on a main heading for all subheadings, or it can sort what is in a selected region. There are interactive options for you to choose from. You can programmatically use it also, and several sorts are possible -- I often use a, o, p, t (one after the next to accomplish 4 levels of sorting criteria). For example, you can sort everything first by alphabetic, then by todo keywords, then by priority, and then by time. – lawlist Feb 25 '15 at 22:14
  • Hello, I'm aware of the org-sort-entries option to sort child tasks. I have updated the question so as to better reflect that. Thank you for your comment. – Leo Ufimtsev Feb 25 '15 at 22:16
  • Here is a link to a detailed approach for sorting an org-mode buffer programmatically: http://stackoverflow.com/a/22232709/2112489 – lawlist Feb 25 '15 at 22:27
  • I have this link in my question already. The above is kinda tailored to someone's specific needs. Is there a more general purpose/easier approach? – Leo Ufimtsev Feb 25 '15 at 22:29
  • @LeoUfimtsev "But the problem with this approach is that I have to constantly shuffle tasks around when I complete them." Have you tried using [`org-refile`](http://orgmode.org/manual/Refile-and-copy.html) (built-in command) for moving tasks to a different subtree? – itsjeyd Feb 25 '15 at 22:32
  • Could be something. I haven't looked into refiling too much. I keep bumping into the problem that it only shows the first level. – Leo Ufimtsev Feb 25 '15 at 22:45
  • 1
    @LeoUfimtsev Try setting `org-refile-targets` to something like `(setq org-refile-targets '((nil . (:maxlevel . 6))))`. That will make `org-mode` show headings up to a depth of 6 when refiling. You can check the documentation for `org-refile-targets` for more information. – itsjeyd Feb 25 '15 at 23:16
  • Maybe I'm not understanding the question. You can sort the list once by priority, and then again by todo state. Does that not give the result you want? Or is it that you want some way to do that in a single step? – Brian Z Feb 26 '15 at 03:54
  • Well, If I sort them by keyword, then by priority, then they the TODO and DONE tasks are sorted by priority but not grouped together. I.e, there is a mix of TODO and DONE tasks. It is possible to manually select the region of just "TODO" tasks and then sort them by priority, but this get's tedious after while, thus the below solution. – Leo Ufimtsev Feb 26 '15 at 15:25
  • @LeoUfimtsev That's true, if you "sort them by keyword, then by priority", but isn't that just an order of operations problem? If I sort some entries by *priority first*, then by TODO *keyword second*, they seem to stay sorted by priority within each TODO keyword group. Still two steps, but not as tedious as what you describe. – Brian Z Feb 27 '15 at 04:41

4 Answers4

24

It would be great if there was something like org-agenda-sorting-stratagy that worked with org-sort-entries, but there doesn't seem to be. We can fake it since org-sort-entries can take an argument specifying a function assigning a (string or number) key to each heading, which will be used to sort the entries when the ?f sorting type is given. All we have to do is get a string for the TODO and PRIORITY properties. The trick is that we want to sort the TODO property by its position in org-todo-keywords, not alphabetically.

(require 'dash)

(defun todo-to-int (todo)
    (cl-first (-non-nil
            (mapcar (lambda (keywords)
                      (let ((todo-seq
                             (-map (lambda (x) (cl-first (split-string  x "(")))
                                   (cl-rest keywords)))) 
                        (cl-position-if (lambda (x) (string= x todo)) todo-seq)))
                    org-todo-keywords))))

(defun my/org-sort-key ()
  (let* ((todo-max (apply #'max (mapcar #'length org-todo-keywords)))
         (todo (org-entry-get (point) "TODO"))
         (todo-int (if todo (todo-to-int todo) todo-max))
         (priority (org-entry-get (point) "PRIORITY"))
         (priority-int (if priority (string-to-char priority) org-default-priority)))
    (format "%03d %03d" todo-int priority-int)
    ))

(defun my/org-sort-entries ()
  (interactive)
  (org-sort-entries nil ?f #'my/org-sort-key))

M-x my/org-sort-entries will sort by the TODO keyword and break ties with PRIORITY (using org-default-priority when no priority is given). This will break if you have more than 1000 TODO keywords, which is a good reason not to do that.

gsgx
  • 188
  • 8
erikstokes
  • 12,686
  • 2
  • 34
  • 56
  • Omg, thank you for script. An issue: I installed dash. Then tried the script, but I'm getting an error: Symbol definition is void: todo-to-int. I'm gonna guess you have that function somewhere in your .emacs file but forgot include in the above? or maybe some typo? – Leo Ufimtsev Feb 26 '15 at 02:02
  • I changed the function that converts a keyword to an int to `todo-to-int` after I had pasted into my answer. It's fixed now. – erikstokes Feb 26 '15 at 02:06
  • 1
    Now all works. Dude, thank you for all your effort, very highly appreciated :-D. – Leo Ufimtsev Feb 26 '15 at 02:08
  • 1
    NB: It is no longer recommended to use cl [according to docs](https://www.emacswiki.org/emacs/CommonLispForEmacs). – cammil Jan 02 '17 at 11:09
4

Add the following to your file:

#+ARCHIVE: :: * Completed.

And shuffling becomes archiving

Rather than sort the entries, how about a sorted view?

(setq org-agenda-custom-commands
      '(("cx" "TODOs sorted by state, priority, effort"
         todo "*"
         ((org-agenda-overriding-header "\nTODOs sorted by state, priority, effort")
          (org-agenda-sorting-strategy '(todo-state-down priority-down effort-up))))))

Restrict it to the current file with <. You can mark DONE and archive from the sorted view.

2

You can also define a org-agenda-cmp-user-defined function and add it to org-agenda-sorting-strategy. This is the one I created as an example.

(setq org-todo-sort-order '("WAIT" "TODO" "DOING" "CANCELED" "DONE"))

(defun my:user-todo-sort (a b)
  "Sort todo based on which I want to see first"
  (when-let ((state-a (get-text-property 14 'todo-state a))
             (state-b (get-text-property 14 'todo-state b))
             (cmp (--map (cl-position-if (lambda (x)
                                           (equal x it))
                                         org-todo-sort-order)
                         (list state-a state-b))))
    (cond ((apply '> cmp) 1)
          ((apply '< cmp) -1)
          (t nil))))
(setq org-agenda-cmp-user-defined 'my:user-todo-sort)
Prgrm.celeritas
  • 849
  • 6
  • 15
1

You can use this library made by me: https://github.com/felipelalli/org-sort-tasks

It uses Merge Sort algo by asking the user if a task A is more important than B, and then builds a sorted list.

Andrew Swann
  • 3,436
  • 2
  • 15
  • 43
Felipe
  • 241
  • 1
  • 13
  • Can you add some description of what this does, and in particular how it differs from the other answers. – Andrew Swann May 10 '19 at 06:01
  • It uses Merge Sort algo by asking the user if a task A is more important than B, and then build a sorted list. – Felipe May 10 '19 at 23:27