org-element-cache: Provide Elisp API to store user data inside the cache

* lisp/org-element.el (org-element-cache-store-key):
(org-element-cache-get-key): New functions to store and retrieve data
associated with cached nodes.  The data is stored inside
:fragile-cache and :robust-cache node properties.
(org-element--cache-shift-positions): Clear :fragile-cache when the
contents boundaries are changed.  Optimize when OFFSET is 0.
* lisp/org-element.el (org-element--cache-process-request): Call
`org-element--cache-shift-positions' even for 0 offset to account for
:fragile-cache.
* etc/ORG-NEWS (New API functions to store data within
~org-element-cache~): Announce the new API.
* testing/lisp/test-org-element.el (test-org-element/cache-get-key):
New test.
This commit is contained in:
Ihor Radchenko 2024-01-04 14:14:57 +01:00
parent 3aac00e45d
commit d6117f895c
No known key found for this signature in database
GPG Key ID: 6470762A7DA11D8B
3 changed files with 133 additions and 26 deletions

View File

@ -778,6 +778,23 @@ Completion is enabled for links to man pages added using ~org-insert-link~:
library should be loaded first.
** New functions and changes in function arguments
*** New API functions to store data within ~org-element-cache~
Elisp programs can now store data inside Org element cache.
The data will remain stored as long as the Org buffer text associated
with the cached elements remains unchanged.
Two options are available:
- Store the data until any text within element boundaries is changed
- Store the data, but ignore any changes inside element contents that
do not affect the high-level element structure. For example,
changes inside subheadings can be ignored for the data stored
inside parent heading element.
The new functions are: ~org-element-cache-store-key~ and
~org-element-cache-get-key~.
*** ~org-fold-hide-drawer-all~ is now interactive
~org-fold-hide-drawer-all~ is now a command, accepting two optional

View File

@ -6304,7 +6304,8 @@ Properties are modified by side-effect."
;; Shift `:structure' property for the first plain list only: it
;; is the only one that really matters and it prevents from
;; shifting it more than once.
(when (and (or (not props) (memq :structure props))
(when (and (not (zerop offset))
(or (not props) (memq :structure props))
(org-element-type-p element 'plain-list)
(not (org-element-type-p
;; Cached elements cannot have deferred `:parent'.
@ -6314,25 +6315,28 @@ Properties are modified by side-effect."
(dolist (item structure)
(cl-incf (car item) offset)
(cl-incf (nth 6 item) offset))))
;; Clear :fragile cache when contents is changed.
(when props (org-element-put-property element :fragile-cache nil))
;; Do not use loop for inline expansion to work during compile time.
(when (or (not props) (memq :begin props))
(cl-incf (org-element-begin element) offset))
(when (or (not props) (memq :end props))
(cl-incf (org-element-end element) offset))
(when (or (not props) (memq :post-affiliated props))
(cl-incf (org-element-post-affiliated element) offset))
(when (and (or (not props) (memq :contents-begin props))
(org-element-contents-begin element))
(cl-incf (org-element-contents-begin element) offset))
(when (and (or (not props) (memq :contents-end props))
(org-element-contents-end element))
(cl-incf (org-element-contents-end element) offset))
(when (and (or (not props) (memq :robust-begin props))
(org-element-property :robust-begin element))
(cl-incf (org-element-property :robust-begin element) offset))
(when (and (or (not props) (memq :robust-end props))
(org-element-property :robust-end element))
(cl-incf (org-element-property :robust-end element) offset)))
(unless (zerop offset)
(when (or (not props) (memq :begin props))
(cl-incf (org-element-begin element) offset))
(when (or (not props) (memq :end props))
(cl-incf (org-element-end element) offset))
(when (or (not props) (memq :post-affiliated props))
(cl-incf (org-element-post-affiliated element) offset))
(when (and (or (not props) (memq :contents-begin props))
(org-element-contents-begin element))
(cl-incf (org-element-contents-begin element) offset))
(when (and (or (not props) (memq :contents-end props))
(org-element-contents-end element))
(cl-incf (org-element-contents-end element) offset))
(when (and (or (not props) (memq :robust-begin props))
(org-element-property :robust-begin element))
(cl-incf (org-element-property :robust-begin element) offset))
(when (and (or (not props) (memq :robust-end props))
(org-element-property :robust-end element))
(cl-incf (org-element-property :robust-end element) offset))))
(defvar org-element--cache-interrupt-C-g t
"When non-nil, allow the user to abort `org-element--cache-sync'.
@ -6714,13 +6718,12 @@ completing the request."
(setf (org-element--request-parent request) parent)
(throw 'org-element--cache-interrupt nil))
;; Shift element.
(unless (zerop offset)
(when (>= org-element--cache-diagnostics-level 3)
(org-element--cache-log-message "Shifting positions (𝝙%S) in %S::%S"
offset
(org-element-property :org-element--cache-sync-key data)
(org-element--format-element data)))
(org-element--cache-shift-positions data offset))
(when (>= org-element--cache-diagnostics-level 3)
(org-element--cache-log-message "Shifting positions (𝝙%S) in %S::%S"
offset
(org-element-property :org-element--cache-sync-key data)
(org-element--format-element data)))
(org-element--cache-shift-positions data offset)
(let ((begin (org-element-begin data)))
;; Update PARENT and re-parent DATA, only when
;; necessary. Propagate new structures for lists.
@ -7761,6 +7764,45 @@ the cache persistence in the buffer."
(add-hook 'clone-indirect-buffer-hook
#'org-element--cache-setup-change-functions)))))
;;;###autoload
(defun org-element-cache-store-key (epom key value &optional robust)
"Store KEY with VALUE associated with EPOM - point, marker, or element.
The key can be retrieved as long as the element (provided or at point)
contents is not modified.
If optional argument ROBUST is non-nil, the key will be retained even
when the contents (children) of current element are modified. Only
non-robust element modifications (affecting the element properties
other then begin/end boundaries) will invalidate the key then."
(let ((element (org-element-at-point epom))
(property (if robust :robust-cache :fragile-cache)))
(let ((key-store (org-element-property property element)))
(unless (hash-table-p key-store)
(setq key-store (make-hash-table :test #'equal))
(org-element-put-property element property key-store))
(puthash key value key-store))))
;;;###autoload
(defun org-element-cache-get-key (epom key &optional default)
"Get KEY associated with EPOM - point, marker, or element.
Return DEFAULT when KEY is not associated with EPOM.
The key can be retrieved as long as the element (provided or at point)
contents is not modified."
(let ((element (org-element-at-point epom)))
(let ((key-store1 (org-element-property :fragile-cache element))
(key-store2 (org-element-property :robust-cache element)))
(let ((val1 (if (hash-table-p key-store1)
(gethash key key-store1 'not-found)
'not-found))
(val2 (if (hash-table-p key-store2)
(gethash key key-store2 'not-found)
'not-found)))
(cond
((and (eq 'not-found val1)
(eq 'not-found val2))
default)
((eq 'not-found val1) val2)
((eq 'not-found val2) val1))))))
;;;###autoload
(defun org-element-cache-refresh (pos)
"Refresh cache at position POS."

View File

@ -5337,6 +5337,54 @@ modified by side effect, influencing the original values."
org-element--cache-diagnostics-level))))
(should (memq var org-element-ignored-local-variables))))))
(ert-deftest test-org-element/cache-get-key ()
"Test `org-element-cache-get-key' and `org-element-cache-store-key'."
(org-test-with-temp-text
"* Heading
Paragraph
with text <point>
Another paragraph."
(org-element-cache-store-key
(org-element-lineage (org-element-at-point) '(headline))
:robust-key 'val-robust 'robust)
(org-element-cache-store-key
(org-element-lineage (org-element-at-point) '(headline))
:fragile-key 'val-fragile)
(insert "and more text.")
(should (eq 'val-robust
(org-element-cache-get-key
(org-element-lineage (org-element-at-point) '(headline))
:robust-key)))
(should (eq 'not-found
(org-element-cache-get-key
(org-element-lineage (org-element-at-point) '(headline))
:fragile-key 'not-found))))
;; No length change in the altered.
(org-test-with-temp-text
"* Heading
Paragraph
<point>with text
Another paragraph."
(org-element-cache-store-key
(org-element-lineage (org-element-at-point) '(headline))
:robust-key 'val-robust 'robust)
(org-element-cache-store-key
(org-element-lineage (org-element-at-point) '(headline))
:fragile-key 'val-fragile)
(search-forward "with")
(org-combine-change-calls (match-beginning 0) (match-end 0)
(replace-match "asdf"))
(should (eq 'val-robust
(org-element-cache-get-key
(org-element-lineage (org-element-at-point) '(headline))
:robust-key)))
(should (eq 'not-found
(org-element-cache-get-key
(org-element-lineage (org-element-at-point) '(headline))
:fragile-key 'not-found)))))
(provide 'test-org-element)
;;; test-org-element.el ends here