Sort mixed header list in org mode

I have a long list of headers in org-mode :

 * Tasks [/] ** TODO Foo ** TODO Bar ** DONE World ** DONE Abba 

which I want to sort as follows:

 * Tasks [/] ** TODO Bar ** TODO Foo ** DONE Abba ** DONE World 

With org-sort-entries I can either get

 * Tasks [/] ** DONE Abba ** TODO Bar ** TODO Foo ** DONE World 

(i.e. alphabetically) or

 * Tasks [/] ** TODO Foo ** TODO Bar ** DONE World ** DONE Abba 

(i.e., grouping according to status).

In other words, I want to sort the TODO and DONE elements in alphabetical order, but keep them in two blocks. How can i achieve this? I want to save the whole set of headers in one subtree!

Edit:

I was unable to use the suggestions below. So, I tried using the tips 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 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 headers marked with TODO and sort them alphabetically, and then do the same with the DONE headers. So far, I only work for TODO ...

+6
source share
4 answers

SHORT ANSWER

  • STEP number 1 . Place the cursor on the parent.

  • STEP # 2 : Mx org-sort-entries RET a RET

  • STEP # 3 : Mx org-sort-entries RET o RET

  • STEP # 4 : Crack open your favorite drink and have a drink.


SEPARATE RESPONSE

To extend the response to @pmr (i.e. sort the selected region), you can also consider using the main headers to organize the subheadings. For example, I like to do several sortings - first alphabetically, then in order, third in priority and fourth in time. For subheadings that do not contain deadlines and contain only one type of todo status, for my needs it’s enough to sort only alphabetically. The following is an example function that I use to sort the entire buffer containing various main headers 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 current-time when sorting records based on a timestamp if the record does not contain a timestamp. The consequence of using current-time is that records that fail to record will be sorted before future tasks that contain timestamps when sorting with the t , c , s or d parameters. To weight undated records so that they are sorted after dated records, you can use a later date than current-time . The corresponding let-bound variable is defined as (now (current-time)) , and its use in the function is written as (org-float-time now) . This can be addressed in many different ways - for example, by changing the code containing (org-float-time now) , with something that dates from an artificial date in the future - for example, (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 the entire buffer containing the main headers and subheadings. This is especially useful if the user needs to synchronize with the Toodledo server using the org-toodledo : https://github.com/christopherjwhite/org-toodledo To speed up the process of reorganizing the buffer containing hundreds of subheadings, the user may wish to consider suppressing messages by changes in responsible functions, however this 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 an example of a cleanup function to set the correct distance between records and remove any empty lines at the end of the buffer. The regex suggests that the headings and subheadings are all left and 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 decision does not depend on the choice of regions, but instead acts on each main heading and organizes everything under this main heading - i.e. all subheadings are organized. The code in this answer will rearrange the records, updating them under the correct main heading. For example, if the task is completed, the completed subtitle will be ** None - the code will search for all subtitles with ** None and move them to the main * DONE header, and then sort them alphabetically.

Main headings: * TASKS ; * EVENTS ; * SOMEDAY ; * CONTACTS ; * DONE .

The following subheadings were chosen because they are the method of getting things, and the Toodledo server uses the 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 
+8
source

The top-level interactive function org-sort will be (in your case) a call to org-sort-entries that works in the region. You just need to make sure your region does not include another block.

To programmatically expand the area above the TODO keyword block, you can combine outline-get-next-sibling with org-heading-components and the active area.

+1
source

I tried to solve the usual replace and sort function. I think this is not useful. But this is one of the possible answers.

(The "@" character must be a unique string.)

  • Mx fundamental-mode
  • Mx replace-string RET Cq Cj RET @
  • Cx cx
  • Mx replace-regexp RET @[*][ ] RET Cq Cj * space RET
  • Cx cx
  • M->
  • Mx sort-lines
  • Cx cx
  • Mx replace-string RET @ RET Cq Cj

Input:

 * TODO Foo ** TODO1 todo1 * TODO Bar ** TODO2 todo2 * DONE World ** DONE1 done1 * DONE Abba ** DONE2 done2 

Output:

 * DONE Abba ** DONE2 done2 * DONE World ** DONE1 done1 * TODO Bar ** TODO2 todo2 * TODO Foo ** TODO1 todo1 
0
source

Based on the lawlist short answer, here's an interactive function to group by the TODO keyword and sort alphabetically. This is achieved by sorting:

  • First Sort Alphabetically
  • Then sort by TODO keyword

Note that this works because TODO -sort in org-sort-entries retains its original alphabetical appearance.

 (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 ) 
0
source

All Articles