Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sort a mixed list of headings in org mode

Tags:

emacs

org-mode

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!

Edit:

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...

like image 457
Dror Avatar asked Mar 06 '14 17:03

Dror


3 Answers

THE SHORT ANSWER

  • STEP # 1:  Place the cursor on the parent.

  • STEP # 2M-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
like image 102
24 revs Avatar answered Nov 09 '22 16:11

24 revs


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.

like image 25
pmr Avatar answered Nov 09 '22 17:11

pmr


Building on lawlist's short answer, here's an interactive function to group by TODO keyword and sort alphabetically. This achieves the sort by:

  1. First sorting alphabetically
  2. Then sorting by TODO keyword

Note 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
  )
like image 1
aparkerlue Avatar answered Nov 09 '22 18:11

aparkerlue