3

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.

Dan
  • 32,584
  • 6
  • 98
  • 168
Saddle Point
  • 481
  • 7
  • 23
  • 2
    Keep in mind that`save-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. – Saddle Point 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 00:33

2 Answers2

4

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.

Dan
  • 32,584
  • 6
  • 98
  • 168
  • "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
1

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.*$\\)")
       sort-item-whole
       sort-item-partial)
  (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)
  (recenter)
  (sit-for 0.1)
  (isearch-dehighlight))
lawlist
  • 18,826
  • 5
  • 37
  • 118