Browse Source

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
Ihor Radchenko 3 years ago
parent
commit
06f58e4759
3 changed files with 34 additions and 8 deletions
  1. 23 7
      lisp/org-element.el
  2. 1 0
      lisp/org.el
  3. 10 1
      testing/lisp/test-org.el

+ 23 - 7
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.

+ 1 - 0
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)

+ 10 - 1
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."