From 06f58e47594e208d290973fd488bb2bf88bfa9d8 Mon Sep 17 00:00:00 2001 From: Ihor Radchenko Date: Sat, 1 Jan 2022 14:10:28 +0800 Subject: [PATCH] org-element-cache-map: Fix when FUNC deletes current element * lisp/org-element.el (org-element-cache-map-continue-from): New variable forcing `org-element-cache-map' to continue from a custom point in buffer. (org-element-cache-map): Add support for `org-element-cache-map-continue-from'. Update docstring accordingly. Also, make sure that mapping terminates correctly when FUNC deletes all elements in buffer. * testing/lisp/test-org.el (test-org/map-entries): Add test. Fixes https://orgmode.org/list/CADywB5KOJ1p0NpvA=iX-ybHsO=huGA8qL3xMpUTETmS2qp7_ng@mail.gmail.com --- lisp/org-element.el | 30 +++++++++++++++++++++++------- lisp/org.el | 1 + testing/lisp/test-org.el | 11 ++++++++++- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/lisp/org-element.el b/lisp/org-element.el index be48bf500..e0450924e 100644 --- a/lisp/org-element.el +++ b/lisp/org-element.el @@ -7118,6 +7118,10 @@ buffers." (defvar warning-minimum-log-level) ; Defined in warning.el (defvar org-element-cache-map--recurse nil) +(defvar org-element-cache-map-continue-from nil + "Position from where mapping should continue. +This variable can be set by called function, especially when the +function modified the buffer.") ;;;###autoload (cl-defun org-element-cache-map (func &key (granularity 'headline+inlinetask) restrict-elements next-re fail-re from-pos (to-pos (point-max-marker)) after-element limit-count @@ -7127,8 +7131,14 @@ GRANULARITY. Collect non-nil return values into result list. FUNC should accept a single argument - the element. -FUNC can safely modify the buffer, but doing so may reduce -performance. +FUNC can modify the buffer, but doing so may reduce performance. If +buffer is modified, the mapping will continue from an element starting +after the last mapped element. If the last mapped element is deleted, +the subsequent element will be skipped as it cannot be distinguished +deterministically from a changed element. If FUNC is expected to +delete the element, it should directly set the value of +`org-element-cache-map-continue-from' to force `org-element-cache-map' +continue from the right point in buffer. If some elements are not yet in cache, they will be added. @@ -7264,7 +7274,7 @@ the cache." (setq start (match-beginning 0)) (setq start (max (or start -1) (or (org-element-property :begin data) -1) - (org-element-property :begin (element-match-at-point))))) + (or (org-element-property :begin (element-match-at-point)) -1)))) (when (>= start to-pos) (cache-walk-abort))) (cache-walk-abort)))) ;; Find expected begin position of an element after @@ -7507,6 +7517,7 @@ the cache." ;; DATA matches restriction. FUNC may ;; ;; Call FUNC. FUNC may move point. + (setq org-element-cache-map-continue-from nil) (if org-element--cache-map-statistics (progn (setq before-time (float-time)) @@ -7523,6 +7534,8 @@ the cache." (setq last-match (car result)) ;; If FUNC moved point forward, update ;; START. + (when org-element-cache-map-continue-from + (goto-char org-element-cache-map-continue-from)) (when (> (point) start) (move-start-to-next-match nil)) ;; Drop nil. @@ -7541,7 +7554,6 @@ the cache." (eq cache-size (cache-size))) ;; START may no longer be valid, update ;; it to beginning of real element. - (when start (goto-char start)) ;; Upon modification, START may lay ;; inside an element. We want to move ;; it to real beginning then despite @@ -7553,11 +7565,15 @@ the cache." ;; Make sure that we continue from an ;; element past already processed ;; place. - (when (<= start (org-element-property :begin data)) + (when (and (<= start (org-element-property :begin data)) + (not org-element-cache-map-continue-from)) (goto-char start) (setq data (element-match-at-point)) - (goto-char (next-element-start)) - (move-start-to-next-match next-element-re)) + ;; If DATA is nil, buffer is + ;; empty. Abort. + (when data + (goto-char (next-element-start)) + (move-start-to-next-match next-element-re))) (org-element-at-point to-pos) (cache-walk-restart)) ;; Reached LIMIT-COUNT. Abort. diff --git a/lisp/org.el b/lisp/org.el index ce4e08eab..e2f315a4c 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -11641,6 +11641,7 @@ headlines matching this string." (goto-char (1- (org-element-property :end el)))))) ;; Get the correct position from where to continue (when org-map-continue-from + (setq org-element-cache-map-continue-from org-map-continue-from) (goto-char org-map-continue-from)) ;; Return nil. nil) diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el index 056ea7d87..97a84f571 100644 --- a/testing/lisp/test-org.el +++ b/testing/lisp/test-org.el @@ -2405,7 +2405,16 @@ SCHEDULED: <2014-03-04 tue.>" (equal '(22) (org-test-with-temp-text "* H1 :yes:\n* H2 :no:\n* H3 :yes:no:" (let (org-odd-levels-only) - (org-map-entries #'point "yes&no")))))) + (org-map-entries #'point "yes&no"))))) + ;; Setting `org-map-continue-from' + (should + (string= "" + (org-test-with-temp-text "* H1\n* H2\n* H3n* H4" + (org-map-entries + (lambda () + (org-cut-subtree) + (setq org-map-continue-from (point)))) + (buffer-string))))) (ert-deftest test-org/edit-headline () "Test `org-edit-headline' specifications."