org-fold: Fix edge case when revealing fragile folds breaks some Emacs commands

* lisp/org-fold-core.el (org-fold-core--fix-folded-region): Delay
revealing fragile regions to until the current command is executed.
(org-fold-core--region-delayed): New function to postpone folding to
the time when `post-command-hook' is executed.
(org-fold-core--region-delayed-list): New internal variable holding
delayed fold requests.
(org-fold-core--process-delayed): New function to be used to process
the delayed folds and cleanup `post-command-hook'.
*
testing/lisp/test-org-fold.el (test-org-fold/org-fold-reveal-broken-structure):
Update tests to account for the delayed unfolding.

Reported-by: Sebastian Miele <iota@whxvd.name>
Link: https://orgmode.org/list/875y04yq9s.fsf@localhost
This commit is contained in:
Ihor Radchenko 2024-01-09 16:15:43 +01:00
parent c9e5270bf1
commit f9702a09e7
No known key found for this signature in database
GPG Key ID: 6470762A7DA11D8B
2 changed files with 36 additions and 1 deletions

View File

@ -1311,6 +1311,33 @@ instead of text properties. The created overlays will be stored in
`(let ((org-fold-core--ignore-fragility-checks t))
(progn ,@body)))
(defvar org-fold-core--region-delayed-list nil
"List holding (MKFROM MKTO FLAG SPEC-OR-ALIAS) arguments to process.
The list is used by `org-fold-core--region-delayed'.")
(defun org-fold-core--region-delayed (from to flag &optional spec-or-alias)
"Call `org-fold-core-region' after current command.
Pass the same FROM, TO, FLAG, and SPEC-OR-ALIAS."
;; Setup delayed folding.
(add-hook 'post-command-hook #'org-fold-core--process-delayed)
(let ((frommk (make-marker))
(tomk (make-marker)))
(set-marker frommk from (current-buffer))
(set-marker tomk to (current-buffer))
(push (list frommk tomk flag spec-or-alias) org-fold-core--region-delayed-list)))
(defun org-fold-core--process-delayed ()
"Perform folding for `org-fold-core--region-delayed-list'."
(when org-fold-core--region-delayed-list
(mapc (lambda (args)
(when (< (nth 0 args) (nth 1 args))
(org-with-point-at (car args)
(apply #'org-fold-core-region args))))
;; Restore the initial folding order.
(nreverse org-fold-core--region-delayed-list))
;; Cleanup `post-command-hook'.
(remove-hook 'post-command-hook #'org-fold-core--process-delayed)
(setq org-fold-core--region-delayed-list nil)))
(defvar-local org-fold-core--last-buffer-chars-modified-tick nil
"Variable storing the last return value of `buffer-chars-modified-tick'.")
@ -1428,7 +1455,10 @@ property, unfold the region if the :fragile function returns non-nil."
(cons fold-begin fold-end)
spec))
;; Reveal completely, not just from the SPEC.
(org-fold-core-region fold-begin fold-end nil)))))
;; Do it only after command is finished -
;; some Emacs commands assume that
;; visibility is not altered by `after-change-functions'.
(org-fold-core--region-delayed fold-begin fold-end nil)))))
;; Move to next fold.
(setq pos (org-fold-core-next-folding-state-change spec pos local-to)))))))))))))

View File

@ -469,6 +469,7 @@ Text here"
(should (org-invisible-p))
(goto-char 1)
(org-delete-char 1)
(run-hooks 'post-command-hook)
(re-search-forward "Text")
(should-not (org-invisible-p)))
(org-test-with-temp-text
@ -480,6 +481,7 @@ Text here"
(goto-char 1)
(let ((last-command-event ?a))
(org-self-insert-command 1))
(run-hooks 'post-command-hook)
(re-search-forward "Text")
(should-not (org-invisible-p)))
(org-test-with-temp-text
@ -494,6 +496,7 @@ Text here"
(should (org-invisible-p))
(re-search-backward ":PROPERTIES:")
(delete-char 1)
(run-hooks 'post-command-hook)
(re-search-forward "ID")
(should-not (org-invisible-p)))
(org-test-with-temp-text
@ -508,6 +511,7 @@ Text here"
(should (org-invisible-p))
(re-search-forward ":END:")
(delete-char -1)
(run-hooks 'post-command-hook)
(re-search-backward "ID")
(should-not (org-invisible-p)))
(org-test-with-temp-text
@ -521,6 +525,7 @@ Text here"
(re-search-forward "end")
(should (org-invisible-p))
(delete-char -1)
(run-hooks 'post-command-hook)
(re-search-backward "2")
(should-not (org-invisible-p)))))