I have a long list of headings in org-mode
:
* Tasks [/]
** TODO Foo
** TODO Bar
** DONE World
** DONE Abba
that I want to sort as follows:
* Tasks [/]
** TODO Bar
** TODO Foo
** DONE Abba
** DONE World
With org-sort-entries
I can either obtain
* Tasks [/]
** DONE Abba
** TODO Bar
** TODO Foo
** DONE World
(i.e. alphabetical order), or
* Tasks [/]
** TODO Foo
** TODO Bar
** DONE World
** DONE Abba
(i.e. grouping according to the status).
In other words, I want to sort the TODO
and the DONE
items alphabetically, but keep them in two blocks. How could I achieve it? I want to keep the whole set of headings in the same subtree!
I didn't manage to utilize the suggestions below. So, I tried to use the tips provided below to come up with the solution I need. Here is the code I have:
(defun drorata-sort-TODO-DONE-headings ()
(interactive)
(save-excursion
;; Sort according to the TODO/DONE keywords
(org-sort-entries t ?o)
;; Now there is a block of TODO's and a block of DONE's
;; Mark the TODO's
(next-line)
(beginning-of-line)
(set-mark-command nil)
(search-forward-regexp "[*].* DONE" nil t)
(beginning-of-line)
;; Sort the marked region (of TODO's) alphabetically
(org-sort-entries t ?a)
;; Now its time to sort the DONE's
(search-forward "[*].* DONE" nil t)
(beginning-of-line)
(set-mark-command nil)
;; How can I find all headings labeled with DONE in the current level?
)
)
My idea is to mark the headings that are labeled by TODO
and sort them alphabetically and then do the same to the DONE
headings. So far, I have it working only for the TODO
's...
THE SHORT ANSWER
STEP # 1: Place the cursor on the parent.
STEP # 2: M-x org-sort-entries RET a RET
STEP # 3: M-x org-sort-entries RET o RET
STEP # 4: Crack open your favorite beverage and have a drink.
THE LONG-WINDED ANSWER
To expand upon the answer of @pmr (i.e., sorting a selected region), one may also wish to consider acting upon main headings to organize subheadings. For example, I like to perform a multi-sort -- first by alphabetic, second by todo-order, third by priority, and fourth by time. For subheadings that do not contain deadlines and contain only one type of todo status, it is sufficient for my needs to only sort by alphabetic. Set forth below is a sample function that I use to sort the entire buffer containing various main headings and subheadings. Here is the cheat-sheet from org-sort-entries
:
Sort: [a]lpha [n]umeric [p]riority p[r]operty todo[o]rder [f]unc
[t]ime [s]cheduled [d]eadline [c]reated
A/N/P/R/O/F/T/S/D/C means reversed.
NOTE: org-sort-entries
uses the current-time
when sorting entries based on time-stamp if the entry does not contain a time-stamp. The consequence of using the current-time
is that undated entries will be sorted prior to future tasks containing time-stamps when sorting with the options t
, c
, s
, or d
. To weight the undated entries so that they are sorted after the dated entries, it is possible to use a later date than the current-time
. The relevant let-bound variable is defined as (now (current-time))
, and its usage within the function is written as (org-float-time now)
. This can be dealt with many different ways -- e.g., modifying the code containing (org-float-time now)
with something containing an artificial date far off into the future -- e.g., (org-time-string-to-seconds "<2030-12-31 Tue>")
.
(defun lawlist-sort ()
(when
(save-excursion
(goto-char (point-max))
(re-search-backward "^\\* CONTACTS" nil t)
(re-search-forward "^\\*\\* \\(Planning\\)" nil t))
(goto-char (point-max))
(re-search-backward "^\\* CONTACTS" nil t)
(org-sort-entries t ?a) )
(when
(save-excursion
(goto-char (point-max))
(re-search-backward "^\\* DONE" nil t)
(re-search-forward "^\\*\\* \\(None\\)" nil t))
(goto-char (point-max))
(re-search-backward "^\\* DONE" nil t)
(org-sort-entries t ?a) )
(when
(save-excursion
(goto-char (point-max))
(re-search-backward "^\\* UNDATED" nil t)
(re-search-forward "^\\*\\* \\(Someday\\)" nil t))
(goto-char (point-max))
(re-search-backward "^\\* UNDATED" nil t)
(org-sort-entries t ?a) )
(when
(save-excursion
(goto-char (point-max))
(re-search-backward "^\\* EVENTS" nil t)
(re-search-forward "^\\*\\* \\(Reference\\|Delegated\\|Postponed\\|Waiting\\)" nil t))
(goto-char (point-max))
(re-search-backward "^\\* EVENTS" nil t)
(org-sort-entries t ?a)
(org-sort-entries t ?o)
(org-sort-entries t ?p)
(org-sort-entries t ?t) )
(when
(save-excursion
(goto-char (point-max))
(re-search-backward "^\\* TASKS" nil t)
(re-search-forward "^\\*\\* \\(Active\\|Next Action\\|Hold\\|Canceled\\)" nil t))
(goto-char (point-max))
(re-search-backward "^\\* TASKS" nil t)
(org-sort-entries t ?a)
(org-sort-entries t ?o)
(org-sort-entries t ?p)
(org-sort-entries t ?t) ) )
Here is an example of how to sort and reorganize an entire buffer containing main headings and subheadings. This is especially useful if the user desires synchronization with the Toodledo server using the org-toodledo
library: https://github.com/christopherjwhite/org-toodledo To speed up the process when reorganizing a buffer containing hundreds of subheadings, the user may wish to consider suppressing the messages by modifying the functions responsible -- however, that is beyond the scope of this answer.
(setq org-todo-keywords '(
(sequence
"Active(a)"
"Next Action(n)"
"Canceled(c)"
"Hold(h)"
"Reference(r)"
"Delegated(d)"
"Waiting(w)"
"Postponed(P)"
"Someday(s)"
"Planning(p)"
"|"
"None(N)") ))
(defun lawlist-reorganize ()
(interactive)
(with-current-buffer (get-buffer "test.org")
(setq buffer-read-only nil)
(lawlist-refile-tasks)
(lawlist-refile-events)
(lawlist-refile-undated)
(lawlist-refile-contacts)
(lawlist-refile-done)
(lawlist-sort)
(goto-char (point-min))
(save-buffer)
(setq buffer-read-only t)))
(defun lawlist-refile-tasks ()
(interactive)
(let* (
(org-archive-location "/Users/HOME/Desktop/test.org::* TASKS")
(org-archive-save-context-info nil))
(goto-char (point-min))
(unless (re-search-forward "^\\* TASKS" nil t)
(goto-char (point-max))
(insert "* TASKS\n\n"))
(goto-char (point-max))
(while
(re-search-backward
"^\\*\\* \\(Active\\|Next Action\\|Hold\\|Canceled\\)" nil t)
(org-archive-subtree))))
(defun lawlist-refile-events ()
(let* (
(org-archive-location "/Users/HOME/Desktop/test.org::* EVENTS")
(org-archive-save-context-info nil))
(goto-char (point-min))
(unless (re-search-forward "^\\* EVENTS" nil t)
(goto-char (point-max))
(insert "* EVENTS\n\n"))
(goto-char (point-max))
(while
(re-search-backward
"^\\*\\* \\(Reference\\|Delegated\\|Postponed\\|Waiting\\)" nil t)
(org-archive-subtree))))
(defun lawlist-refile-undated ()
(let* (
(org-archive-location "/Users/HOME/Desktop/test.org::* UNDATED")
(org-archive-save-context-info nil))
(goto-char (point-min))
(unless (re-search-forward "^\\* UNDATED" nil t)
(goto-char (point-max))
(insert "* UNDATED\n\n"))
(goto-char (point-max))
(while
(re-search-backward
"^\\*\\* \\(Someday\\)" nil t)
(org-archive-subtree))))
(defun lawlist-refile-done ()
(let* (
(org-archive-location "/Users/HOME/Desktop/test.org::* DONE")
(org-archive-save-context-info nil))
(goto-char (point-min))
(unless (re-search-forward "^\\* DONE" nil t)
(goto-char (point-max))
(insert "* DONE\n\n"))
(goto-char (point-max))
(while
(re-search-backward
"^\\*\\* \\(None\\)" nil t)
(org-archive-subtree))))
(defun lawlist-refile-contacts ()
(let* (
(org-archive-location "/Users/HOME/Desktop/test.org::* CONTACTS")
(org-archive-save-context-info nil))
(goto-char (point-min))
(unless (re-search-forward "^\\* CONTACTS" nil t)
(goto-char (point-max))
(insert "* CONTACTS\n\n"))
(goto-char (point-max))
(while
(re-search-backward
"^\\*\\* \\(Planning\\)" nil t)
(org-archive-subtree))))
Here is a sample clean-up function to put proper spacing between entries and delete any empty lines at the end of the buffer -- the regexp assumes headings and subheadings are all flush-left:
(defun lawlist-cleanup ()
(interactive)
(let ((query-replace-lazy-highlight nil))
(replace-regexp "\n+\\*\\* " "\n\n** " nil (point-min) (point-max))
(replace-regexp "\n+\\* " "\n\n\n* " nil (point-min) (point-max))
(goto-char (point-max))
(delete-blank-lines)
(let ((trailnewlines (abs (skip-chars-backward "\n\t"))))
(if (> trailnewlines 0)
(delete-char trailnewlines))) ))
This solution does not rely upon selecting regions, but instead acts upon each main heading and organizes everything under that main heading -- i.e., all subheadings are organized. The code in this answer will regroup entries by refiling them underneath the correct main heading. For example -- if a task has been completed, the completed subheading will be ** None
-- the code will look for all subheadings with ** None
and move them to the main heading of * DONE
, and then sort them alphabetically.
The main headings are: * TASKS
; * EVENTS
; * SOMEDAY
; * CONTACTS
; * DONE
.
The following subheadings were chosen because they are a method of Getting Things Done, and the Toodledo server uses this same methodology: ** Active
; ** Next Action
; ** Hold
; ** Canceled
; ** Reference
; ** Delegated
; ** Postponed
; ** Waiting
; ** Someday
; ** Planning
; ** None
.
* TASKS
** Active
** Next Action
** Hold
** Canceled
* EVENTS
** Reference
** Delegated
** Postponed
** Waiting
* SOMEDAY
** Someday
* CONTACTS
** Planning
* DONE
** None
The top-level interactive function org-sort
will (in your case) call org-sort-entries
, which acts on a region. You just need to make sure your region does not include the other block.
To programmatically extend the region over a block of TODO keywords you can combine outline-get-next-sibling
with org-heading-components
and an active region.
Building on lawlist's short answer, here's an interactive function to group by TODO
keyword and sort alphabetically. This achieves the sort by:
TODO
keywordNote this works because the TODO
-sort in org-sort-entries
preserves the initial alphabetical sort.
(defun apl-org-sort-entries-todo-alphabetical ()
"Group Org-mode entries by TODO keyword and sort alphabetically.
This function achieves this by first sorting alphabetically and
then sorting by TODO keyword. This works because the TODO-sort in
`org-sort-entries' preserves the initial alphabetical sort."
(interactive)
;; First sort alphabetically
(org-sort-entries t ?a)
;; Then sort by TODO keyword
(org-sort-entries t ?o)
;; Rotate subtree to show children
(org-cycle) ; SUBTREE -> FOLDED
(org-cycle) ; FOLDED -> CHILDREN
)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With