org-element: Optimize cache

* lisp/org-element.el (org-element--cache-for-removal): New function.
(org-element--cache-submit-request): Do not synchronize cache when
changes can be merged with next request.

This shortcut is particularly useful when many changes happen in the
same area, which is expensive to parse (e.g., a large list).
This commit is contained in:
Nicolas Goaziou 2014-06-18 00:11:44 +02:00
parent b2f200f0a1
commit 8e49c823fd
1 changed files with 85 additions and 68 deletions

View File

@ -5491,28 +5491,17 @@ that range. See `after-change-functions' for more information."
;; Activate a timer to process the request during idle time. ;; Activate a timer to process the request during idle time.
(org-element--cache-set-timer (current-buffer))))) (org-element--cache-set-timer (current-buffer)))))
(defun org-element--cache-submit-request (beg end offset) (defun org-element--cache-for-removal (beg end offset)
"Submit a new cache synchronization request for current buffer. "Return first element to remove from cache.
BEG and END are buffer positions delimiting the minimal area
where cache data should be removed. OFFSET is the size of the BEG and END are buffer positions delimiting buffer modifications.
change, as an integer." OFFSET is the size of the changes.
;; Make sure buffer positions in cache are correct until END. This
;; also ensures that pending cache requests have their phases Returned element is usually the first element in cache containing
;; properly ordered. We need to provide OFFSET as optional any position between BEG and END. As an exception, greater
;; parameter since current modifications are not known yet to the elements around the changes that are robust to contents
;; otherwise correct part of the cache (i.e, before the first modifications are preserved and updated according to the
;; request). changes."
(org-element--cache-sync (current-buffer) end offset)
(let ((first-element
;; Find the position of the first element in cache to remove.
;;
;; Partially modified elements will be removed during request
;; processing. As an exception, greater elements around the
;; changes that are robust to contents modifications are
;; preserved.
;;
;; We look just before BEG because an element ending at BEG
;; needs to be removed too.
(let* ((elements (org-element--cache-find (1- beg) 'both)) (let* ((elements (org-element--cache-find (1- beg) 'both))
(before (car elements)) (before (car elements))
(after (cdr elements))) (after (cdr elements)))
@ -5525,45 +5514,73 @@ change, as an integer."
property-drawer quote-block special-block)) property-drawer quote-block special-block))
(<= (org-element-property :contents-begin up) beg) (<= (org-element-property :contents-begin up) beg)
(> (org-element-property :contents-end up) end)) (> (org-element-property :contents-end up) end))
;; UP is a greater element that is wrapped around ;; UP is a robust greater element containing changes.
;; the changes. We only need to extend its ;; We only need to extend its ending boundaries and
;; ending boundaries and those of all its ;; those of all its parents.
;; parents.
(while up (while up
(org-element--cache-shift-positions (org-element--cache-shift-positions
up offset '(:contents-end :end)) up offset '(:contents-end :end))
(setq up (org-element-property :parent up))) (setq up (org-element-property :parent up)))
(setq before up))) (setq before up)))
;; We're at top level element containing ELEMENT: if ;; We're at top level element containing ELEMENT: if it's
;; it's altered by buffer modifications, it is first ;; altered by buffer modifications, it is first element in
;; element in cache to be removed. Otherwise, that ;; cache to be removed. Otherwise, that first element is the
;; first element is the following one. ;; following one.
(if (< (org-element-property :end before) beg) after before)))))) (if (< (org-element-property :end before) beg) after before)))))
(defun org-element--cache-submit-request (beg end offset)
"Submit a new cache synchronization request for current buffer.
BEG and END are buffer positions delimiting the minimal area
where cache data should be removed. OFFSET is the size of the
change, as an integer."
(let ((next (car org-element--cache-sync-requests)))
(if (and next
(zerop (aref next 5))
(let ((offset (aref next 3)))
(and (>= (+ (aref next 2) offset) end)
(<= (+ (aref next 1) offset) end))))
;; Current changes can be merged with first sync request: we
;; can save a partial cache synchronization.
(progn
(incf (aref next 2) offset)
(incf (aref next 3) offset)
(when (> (aref next 1) beg)
(let ((first (org-element--cache-for-removal beg end offset)))
(when first
(aset next 0 (org-element--cache-key first))
(aset next 1 (org-element-property :begin first))))))
;; Ensure cache is correct up to END. Also make sure that NEXT,
;; if any, is no longer a 0-phase request, thus ensuring that
;; phases are properly ordered. We need to provide OFFSET as
;; optional parameter since current modifications are not known
;; yet to the otherwise correct part of the cache (i.e, before
;; the first request).
(org-element--cache-sync (current-buffer) end offset)
(let ((first (org-element--cache-for-removal beg end offset)))
(cond (cond
;; Changes happened before the first known element. Shift the ;; Changes happened before the first known element. Shift
;; rest of the cache. ;; the rest of the cache.
((and first-element (> (org-element-property :begin first-element) end)) ((and first (> (org-element-property :begin first) end))
(push (vector (org-element--cache-key first-element) nil nil offset nil 2) (push (vector (org-element--cache-key first) nil nil offset nil 2)
org-element--cache-sync-requests)) org-element--cache-sync-requests))
;; There is at least an element to remove. Find position past ;; There is at least an element to remove. Find position
;; every element containing END. ;; past every element containing END.
(first-element (first
(if (> (org-element-property :end first-element) end) (if (> (org-element-property :end first) end)
(setq end (org-element-property :end first-element)) (setq end (org-element-property :end first))
(let ((element (org-element--cache-find end))) (let ((element (org-element--cache-find end)))
(setq end (org-element-property :end element)) (setq end (org-element-property :end element))
(let ((up element)) (let ((up element))
(while (and (setq up (org-element-property :parent up)) (while (and (setq up (org-element-property :parent up))
(>= (org-element-property :begin up) beg)) (>= (org-element-property :begin up) beg))
(setq end (org-element-property :end up)))))) (setq end (org-element-property :end up))))))
(push (vector (org-element--cache-key first-element) (push (vector (org-element--cache-key first)
(org-element-property :begin first-element) (org-element-property :begin first)
end offset nil 0) end offset nil 0)
org-element--cache-sync-requests)) org-element--cache-sync-requests))
;; No element to remove. No need to re-parent either. Simply ;; No element to remove. No need to re-parent either.
;; shift additional elements, if any, by OFFSET. ;; Simply shift additional elements, if any, by OFFSET.
(org-element--cache-sync-requests (org-element--cache-sync-requests (incf (aref next 3) offset)))))))
(incf (aref (car org-element--cache-sync-requests) 2) offset)))))
;;;; Public Functions ;;;; Public Functions