From f9702a09e79da6fbb0e3e3d01b0e2e1145c2e70f Mon Sep 17 00:00:00 2001 From: Ihor Radchenko Date: Tue, 9 Jan 2024 16:15:43 +0100 Subject: [PATCH] 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 Link: https://orgmode.org/list/875y04yq9s.fsf@localhost --- lisp/org-fold-core.el | 32 +++++++++++++++++++++++++++++++- testing/lisp/test-org-fold.el | 5 +++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/lisp/org-fold-core.el b/lisp/org-fold-core.el index 3f5bd2121..11de2bff6 100644 --- a/lisp/org-fold-core.el +++ b/lisp/org-fold-core.el @@ -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))))))))))))) diff --git a/testing/lisp/test-org-fold.el b/testing/lisp/test-org-fold.el index 22f17977f..9f15f0a38 100644 --- a/testing/lisp/test-org-fold.el +++ b/testing/lisp/test-org-fold.el @@ -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)))))