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 '16 at 17:25
  • @wasamasa I wonder why sorting the subtrees will also modify the higher level items. – stackunderflow Dec 15 '16 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 '16 at 0:33

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.

| improve this answer | |
  • "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 '19 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)
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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