org-refile: Fix edge case when we refile on top of the same subtree

* lisp/org-refile.el (org-refile): Use dedicated marker (move after
insertion) to store position of the heading being refiled.  Otherwise,
if refiled heading is inserted at the same point, `save-excursion'
would restore point before the inserted heading instead of keeping it
at the original heading.
* testing/lisp/test-org.el (test-org/refile): At test.

Reported-by: /u/madclassix
This commit is contained in:
Ihor Radchenko 2023-01-12 13:19:02 +03:00
parent d0758eabfe
commit 0d5951a9b0
No known key found for this signature in database
GPG Key ID: 6470762A7DA11D8B
2 changed files with 69 additions and 49 deletions

View File

@ -541,58 +541,67 @@ prefix argument (`C-u C-u C-u C-c C-w')."
(org-kill-new (buffer-substring region-start region-end))
(org-save-markers-in-region region-start region-end))
(org-copy-subtree 1 nil t))
(with-current-buffer (setq nbuf (or (find-buffer-visiting file)
(find-file-noselect file)))
(setq reversed (org-notes-order-reversed-p))
(org-with-wide-buffer
(if pos
(progn
(goto-char pos)
(setq level (org-get-valid-level (funcall outline-level) 1))
(goto-char
(if reversed
(or (outline-next-heading) (point-max))
(or (save-excursion (org-get-next-sibling))
(org-end-of-subtree t t)
(point-max)))))
(setq level 1)
(if (not reversed)
(goto-char (point-max))
(goto-char (point-min))
(or (outline-next-heading) (goto-char (point-max)))))
(unless (bolp) (newline))
(org-paste-subtree level nil nil t)
;; Record information, according to `org-log-refile'.
;; Do not prompt for a note when refiling multiple
;; headlines, however. Simply add a time stamp.
(cond
((not org-log-refile))
(regionp
(org-map-region
(lambda () (org-add-log-setup 'refile nil nil 'time))
(point)
(+ (point) (- region-end region-start))))
(t
(org-add-log-setup 'refile nil nil org-log-refile)))
(and org-auto-align-tags
(let ((org-loop-over-headlines-in-active-region nil))
(org-align-tags)))
(let ((bookmark-name (plist-get org-bookmark-names-plist
:last-refile)))
(when bookmark-name
(with-demoted-errors "Bookmark set error: %S"
(bookmark-set bookmark-name))))
;; If we are refiling for capture, make sure that the
;; last-capture pointers point here
(when (bound-and-true-p org-capture-is-refiling)
(let ((bookmark-name (plist-get org-bookmark-names-plist
:last-capture-marker)))
(let ((origin (point-marker)))
;; Handle special case when we refile to exactly same
;; location with tree promotion/demotion. Point marker
;; saved by `org-width-wide-buffer' (`save-excursion')
;; will then remain before the inserted subtree in
;; unexpected location.
(set-marker-insertion-type origin t)
(with-current-buffer (setq nbuf (or (find-buffer-visiting file)
(find-file-noselect file)))
(setq reversed (org-notes-order-reversed-p))
(org-with-wide-buffer
(if pos
(progn
(goto-char pos)
(setq level (org-get-valid-level (funcall outline-level) 1))
(goto-char
(if reversed
(or (outline-next-heading) (point-max))
(or (save-excursion (org-get-next-sibling))
(org-end-of-subtree t t)
(point-max)))))
(setq level 1)
(if (not reversed)
(goto-char (point-max))
(goto-char (point-min))
(or (outline-next-heading) (goto-char (point-max)))))
(unless (bolp) (newline))
(org-paste-subtree level nil nil t)
;; Record information, according to `org-log-refile'.
;; Do not prompt for a note when refiling multiple
;; headlines, however. Simply add a time stamp.
(cond
((not org-log-refile))
(regionp
(org-map-region
(lambda () (org-add-log-setup 'refile nil nil 'time))
(point)
(+ (point) (- region-end region-start))))
(t
(org-add-log-setup 'refile nil nil org-log-refile)))
(and org-auto-align-tags
(let ((org-loop-over-headlines-in-active-region nil))
(org-align-tags)))
(let ((bookmark-name (plist-get org-bookmark-names-plist
:last-refile)))
(when bookmark-name
(with-demoted-errors "Bookmark set error: %S"
(bookmark-set bookmark-name))))
(move-marker org-capture-last-stored-marker (point)))
(deactivate-mark)
(run-hooks 'org-after-refile-insert-hook)))
;; If we are refiling for capture, make sure that the
;; last-capture pointers point here
(when (bound-and-true-p org-capture-is-refiling)
(let ((bookmark-name (plist-get org-bookmark-names-plist
:last-capture-marker)))
(when bookmark-name
(with-demoted-errors "Bookmark set error: %S"
(bookmark-set bookmark-name))))
(move-marker org-capture-last-stored-marker (point)))
(deactivate-mark)
(run-hooks 'org-after-refile-insert-hook)))
;; Go back to ORIGIN.
(goto-char origin))
(unless org-refile-keep
(if regionp
(delete-region (point) (+ (point) (- region-end region-start)))

View File

@ -6953,6 +6953,17 @@ Paragraph<point>"
(org-refile-targets `((nil :level . 1))))
(mapcar #'car (org-refile-get-targets)))))))
(ert-deftest test-org/refile ()
"Test `org-refile' specifications."
;; Test edge case when we refile heading into the same location.
(should
(equal
"* H1
** H2\n"
(org-test-with-temp-text-in-file "* H1
* H2<point>"
(org-refile nil nil `("H1" ,(buffer-file-name) nil 1))
(buffer-string)))))
;;; Sparse trees