Say we have a org buffer with content:

* top-level heading
some text $          # $ denote cursor
** heading 3
** heading 2
** heading 1
** heading 0

Now evaluate (save-excursion (org-sort-entries nil ?a)). The cursor goes to the beginning of top-level heading, rather than stays put at $ as I would expect.

Why does save-excursion does not work under this situation?

Updated: Even with (save-excursion (save-restriction (org-sort-entries nil ?a))), the cursor still moves.

  • 2
    Keep in mind thatsave-excursion on code doing anything else than point movement may fail in non-obvious ways. This includes modifying the buffer by sorting its contents.
    – wasamasa
    Dec 15, 2016 at 17:25
  • @wasamasa I wonder why sorting the subtrees will also modify the higher level items. Dec 15, 2016 at 23:31
  • Let's assume that your point is at position 250. Now we perform a sort and one or more tasks get inserted before the original position -- your original position just got pushed downwards by the text that was inserted -- instead of 250, it may now be 1000. Now let us assume that text above the initial point was cut and pasted after the initial point -- instead of 250, it may now be 75. I did a test earlier today with a marker (setting it with point-marker) and the marker was changed to a position of 1 after the sort, whereas the initial marker position was 250 -- marker is not the answer.
    – lawlist
    Dec 16, 2016 at 0:33

2 Answers 2


This post provides a work-around, but not an answer to "why does save-excursion not work here?" The problem is interesting. I find org internals to be baroque, and a skim through the source code for org-sort-entries seems to conform with that view.

Unless you're deeply interested in the internals, you can use the following work-around to save and restore point after calling org-sort-entries:

(let ((home (point)))
  (org-sort-entries nil ?a)
  (goto-char home))

I tested it only on your example, so YMMV in more complicated cases.

  • "I find org internals to be baroque," - Plain old sort-lines has the same problem; they both call sort-subr, which is where the problem lies, I guess.
    – npostavs
    Sep 12, 2019 at 20:16

One idea would be to record the headline and search for it to get back there again at the end (but this example will find the last occurrence if there are duplicate headings in the same file):

(let ((sort-init-pos (point))
       (heading-regexp "^\\(\\*+\\)\\(?: +\\(.*?\\)\\)?[ \t]*\\(\n.*DEADLINE.*$\\)")
  (org-back-to-heading t)
  (if (and
        (looking-at org-heading-regexp)
        (and (looking-at heading-regexp) (match-string 3)))
    (re-search-forward heading-regexp nil t)
    (re-search-forward org-heading-regexp nil t))
  (setq sort-item-whole
    (regexp-quote (buffer-substring-no-properties (match-beginning 0) (match-end 0))))
  (setq sort-item-partial
    (regexp-quote (buffer-substring-no-properties (match-beginning 0) sort-init-pos)))
  (message "Use `org-sort-entries' at this location.")
  (goto-char (point-max))
  (re-search-backward sort-item-whole nil t)
  (isearch-highlight (match-beginning 0) (match-end 0))
  (re-search-forward sort-item-partial nil t)
  (sit-for 0.1)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.