Re-implement org-element-cache and add headline support

* lisp/org-element.el (org-element-with-disabled-cache): New macro.

(org-element-greater-elements): Add new org-data element.  It
functions like a virtual headline containing the whole buffer.  The
org-data properties are like headlie properties, but according to the
top-level drawer.  org-data's category is the buffer's category as
defined by top-level property drawer, #+CATEGORY keyworsd, and the
buffer file name.

(org-element--cache-element-properties, org-element-set-element): New
variable containing properties to be transferred when updating changed
element in cache in `org-element-set-element'.

(org-element--get-node-properties): Allow parsing node propreties in
top-level drawer when new optional argument is passed.  Respect
PROPERTY+ syntax.

(org-element--get-global-node-properties): New function.  It returns
node properties for top-level property drawer.

(org-element-org-data-parser, org-element-org-data-interpreter):
Implement the new org-data element.

(org-element-headline-parser, org-element-section-parser): Add new
:robust-begin and :robust-end
properties delimiting safe changes that do not modify headline
element.

(org-element--list-struct): Fix cache update when adding a headline
inside list.

(org-element--current-element): Implement cache support.  Record
parsing mode (:mode) and parsing granularity (:granularity) in the
element properties.

(org-element-parse-buffer, org-element--next-mode): Support new
org-data element.

(org-element--parse-elements): Record parsing granularity in the
returned tree

(org-element-use-cache): Enable cache by default.

(org-element-cache-persistent): New variable controlling cache
persistance across sessions.  Enabled by default.

(org-element--cache-self-verify,
org-element--cache-self-verify-frequency,
org-element--cache-diagnostics, org-element--cache-map-statistics,
org-element--cache-map-statistics-threshold,
org-element--cache-diagnostics-level,
org-element--cache-diagnostics-ring,
org-element--cache-diagnostics-ring-size): New variables controlling
cache diagnostics and self-diagnostics.  Greatly simplifies cache
debugging.

(org-element--cache, org-element--cache-sync-requests,
org-element--cache-sync-timer): Make cache buffer-local by default.

(org-element--headline-cache): Implement separate cache storing only
headlines and inlinetasks.

(org-element--cache-size, org-element--headline-cache-size): New
variables containing cache sizes.  This is much faster than
`avl-tree-size'.

(org-element--cache-sync-requests): Update docstring explaning the
request list structure.

(org-element--cache-sync-keys-value): New variable replacing
`org-element--cache-sync-keys' hash table.  The hash table was not
reliable because it was using elements as keys.  Upon any cached
element update/shift, the keys were invalidated making cache ordering
incorrect and breaking the cache badly.  Now, the cache keys are
stored as :org-element--cache-sync-key element property and the new
variable stores marker value indicating the current sync request
cycle.  See `org-element--cache-key' for more details.

(org-element--cache-change-tic): New variable controlling buffer
modification count that is registered in cache.  This variable allows
catching "stealth" edits.

(org-element--cache-non-modifying-commands): New variable listing
commands that will not be slown down if we fill cache on the fly.

(org-element--request-key, org-element--request-beg,
org-element--request-end, org-element--request-offset,
org-element--request-parent, org-element--request-phase): New macros.
They improve code readability (especially when using nameless-mode).

(org-element--format-element, org-element--cache-log-message,
org-element--cache-warn): New macros implementing generic logging
functionality.

(org-element--cache-key): Add section and org-data element support.
Change cache key storage from hash map to :org-element--cache-sync-key
element property + `org-element--cache-sync-keys-value'.  We use the
latter to group all the cache keys during a single cache request
sequence.  Once sync request is fully complete, the
`org-element--cache-sync-keys-value' is updated making all the old
sync keys obsolete (they will still be store as element properties).

(org-element--headline-cache-root): New function returning headline
cache root.

(org-element--cache-active-p): Prevent cache updates when
`inhibit-modification-hooks' is non-nil, unless non-nil optional
argument is provided.

(org-element--cache-find): Share cache between indirect buffers and
the base buffer.  We have to do it because after-change hooks for
indirect buffer are not called in the base buffer and vice versa.  Add
support for section and org-data elements.

(org-element--cache-put): Implement new approach for cache key
storage.  Add diagnostics.  Indicate cached elements using :cached
element property.  Support cache size calculation.

(org-element--cache-remove): Invalidate parent contents when removing
element.  Support cache size calculation.  Detect cache corruption due
to misordered elements.

(org-element--cache-shift-positions): Support :robust-begin and
:robust-end element properties.

(org-element--cache-sync): Add diagnostics.  Add detailed comments.
Prevent slowdown when large cache chunks need to be deleted forcing
O(N) complexity cutoff.  In phase 2, fix cases when next request
contains deleted cache key.  In phase 2, fix scenario when newly
inserted element intersects with existing elements in cache.  In phase
2, detect obsolete parents removed from cache.

(org-element--open-end-p): New function checking if an element can
have blank lines right after its :contents-end.

(org-element--parse-to): Do not alter match data.  Process complex
parsing mode changes correctly.  Support headlines in cache.  Support
org-data parsing.  Add detailed comments.  Add diagnostics.

(org-element--cache-sensitive-re): Make list lines sensitive.

(org-element--cache-change-warning): Update docstring.  Now, the
variable can have t, nil, and number values.  Numbers are used to
provide more details about changed headlines.

(org-element--cache-before-change, org-element--cache-after-change):
Handle headline hierarchy.  Properly handle cache in indirect
buffers.

(org-element--cache-after-change): Update docstring clarifying the
return values.  Add special handling for headline and org-data
elements updating them in-place instead of removing together with the
whole contents when possible.  Use :robust-begin/:robust-end element
properties to detect robust changes.

(org-element--cache-submit-request): Add detailed comments.  Correctly
handle cache in indirect buffers.  Delegate element modifications to
`org-element--cache-for-removal'.

(org-element--cache-verify-element): New function for cache
self-verification.

(org-element--cache-persist-before-write,
org-element--cache-persist-before-read,
org-element--cache-persist-after-read): Implement cache persistance.

(org-element-cache-reset): Correctly handle cache in indirect
buffers.  Support cache persistance.  Support new cache size
calculation and new cache key schema.

(org-element-cache-map): New function analagous to `org-element-map',
but much faster.  The function overperforms org-ql written by Adam
Porter aka alphapapa [1] and reuses some ideas from there (namely,
fast element skipping via regexps).

[1] https://github.com/alphapapa/org-ql/

(org-element-at-point): The returned elements are now guaranteed to
have correct parents up to org-data.  New optional argument
CACHED-ONLY limits element search to current cache---if element is not
in cache and current command is not in cache
`org-element--cache-non-modifying-commands', the cache is not updated
and the function returns nil.  Also, support cache verification.

(org-element-at-point-no-context): New function.  It is analogous of
older `org-element-at-point' with no guarantee that :parent properties
are correct beyond direct parent heading.  This function does not
update cache and can be useful when cache updates should be avoided
for performance reasons.

* lisp/ob-core.el (org-babel-where-is-src-block-result): Support
section and org-data elements in cache.

* lisp/org-macro.el (org-macro-replace-all,
org-macro--find-keyword-value): Support org-element-cache.

* lisp/org-table.el (orgtbl-to-generic): Support org-element-cache.

* lisp/org.el (org-mode): Add cache persistance.

(org-up-element): Preserve old behaviour when error is returned for
section and org-data element.

*
testing/lisp/test-org-archive.el (test-org-archive/update-status-cookie):
Fix test when cache is active.

* testing/lisp/test-org-colview.el (test-org-colview/columns-update):
Fix test.

* testing/lisp/test-org-element.el (test-org-element/extract-element):
Add suport for new org-data element.

* testing/lisp/test-org-element.el (test-org-element/parent-property):
Fix equality check.  Parents returned by cache and `org-element-map'
may not be `eq' now.  Just `equal'.

* testing/lisp/test-org-element.el (test-org-element/context): Support
section and headline parents.
This commit is contained in:
Ihor Radchenko 2021-10-16 21:17:10 +08:00
parent 6933c1ad78
commit fc80d052db
No known key found for this signature in database
GPG Key ID: 6470762A7DA11D8B
8 changed files with 2032 additions and 670 deletions

View File

@ -2055,8 +2055,11 @@ to HASH."
((or `inline-babel-call `inline-src-block)
;; Results for inline objects are located right after them.
;; There is no RESULTS line to insert either.
(let ((limit (org-element-property
:contents-end (org-element-property :parent context))))
(let ((limit (pcase (org-element-type (org-element-property :parent context))
(`section (org-element-property
:end (org-element-property :parent context)))
(_ (org-element-property
:contents-end (org-element-property :parent context))))))
(goto-char (org-element-property :end context))
(skip-chars-forward " \t\n" limit)
(throw :found
@ -2089,8 +2092,11 @@ to HASH."
;; No possible anonymous results at the very end of
;; buffer or outside CONTEXT parent.
((eq (point)
(or (org-element-property
:contents-end (org-element-property :parent context))
(or (pcase (org-element-type (org-element-property :parent context))
((or `section `org-data) (org-element-property
:end (org-element-property :parent context)))
(_ (org-element-property
:contents-end (org-element-property :parent context))))
(point-max))))
;; Check if next element is an anonymous result below
;; the current block.

File diff suppressed because it is too large Load Diff

View File

@ -239,6 +239,13 @@ a definition in TEMPLATES."
(goto-char (match-beginning 0))
(org-element-macro-parser))))))
(when macro
;; `:parent' property might change as we modify buffer.
;; We do not care about it when checking for circular
;; dependencies. So, setting `:parent' to nil making sure
;; that actual macro element (if org-element-cache is
;; active) is unchanged.
(setq macro (cl-copy-list macro))
(org-element-put-property macro :parent nil)
(let* ((key (org-element-property :key macro))
(value (org-macro-expand macro templates))
(begin (org-element-property :begin macro))
@ -338,7 +345,7 @@ in the buffer."
(result nil))
(catch :exit
(while (re-search-forward regexp nil t)
(let ((element (org-element-at-point)))
(let ((element (org-with-point-at (match-beginning 0) (org-element-keyword-parser (line-end-position) (list (match-beginning 0))))))
(when (eq 'keyword (org-element-type element))
(let ((value (org-element-property :value element)))
(if (not collect) (throw :exit value)

View File

@ -5697,6 +5697,7 @@ This may be either a string or a function of two arguments:
((consp e)
(princ "| ") (dolist (c e) (princ c) (princ " |"))
(princ "\n")))))
(org-element-cache-reset)
;; Add back-end specific filters, but not user-defined ones. In
;; particular, make sure to call parse-tree filters on the
;; table.

View File

@ -4879,6 +4879,9 @@ The following commands are available:
(org-setup-comments-handling)
;; Initialize cache.
(org-element-cache-reset)
(when (and org-element-cache-persistent
org-element-use-cache)
(org-persist-read 'org-element--cache (current-buffer)))
;; Beginning/end of defun
(setq-local beginning-of-defun-function 'org-backward-element)
(setq-local end-of-defun-function
@ -21243,7 +21246,9 @@ Move to the previous element at the same level, when possible."
(unless (org-up-heading-safe) (user-error "No surrounding element"))
(let* ((elem (org-element-at-point))
(parent (org-element-property :parent elem)))
(if parent (goto-char (org-element-property :begin parent))
(if (and parent
(not (memq (org-element-type parent) '(section org-data))))
(goto-char (org-element-property :begin parent))
(if (org-with-limited-levels (org-before-first-heading-p))
(user-error "No surrounding element")
(org-with-limited-levels (org-back-to-heading)))))))

View File

@ -30,7 +30,7 @@
(forward-line)
(org-archive-subtree)
(forward-line -1)
(org-element-property :title (org-element-at-point)))))
(org-element-property :raw-value (org-element-at-point)))))
;; Test org-archive-subtree with one child.
(should
(equal
@ -39,7 +39,7 @@
(forward-line)
(org-archive-subtree)
(forward-line -1)
(org-element-property :title (org-element-at-point)))))
(org-element-property :raw-value (org-element-at-point)))))
;; Test org-archive-to-archive-sibling with two children.
(should
(equal
@ -47,7 +47,7 @@
(org-test-with-temp-text "* Top [%]\n<point>** TODO One\n** DONE Two"
(org-archive-to-archive-sibling)
(forward-line -1)
(org-element-property :title (org-element-at-point)))))
(org-element-property :raw-value (org-element-at-point)))))
;; Test org-archive-to-archive-sibling with two children.
(should
(equal
@ -55,7 +55,7 @@
(org-test-with-temp-text "* Top [%]\n<point>** DONE Two"
(org-archive-to-archive-sibling)
(forward-line -1)
(org-element-property :title (org-element-at-point))))))
(org-element-property :raw-value (org-element-at-point))))))
(ert-deftest test-org-archive/datetree ()
"Test `org-archive-subtree' with a datetree target."

View File

@ -991,6 +991,7 @@
(let ((org-columns-default-format "%A{min}")
(org-columns-ellipses "..")
(org-inlinetask-min-level 15))
(org-element-update-syntax)
(org-columns))
(get-char-property (point-min) 'org-columns-value)))))
;; Handle `org-columns-modify-value-for-display-function', even with

View File

@ -195,12 +195,12 @@ Some other text
"Test `org-element-extract-element' specifications."
;; Extract a greater element.
(should
(equal '(org-data nil)
(org-test-with-temp-text "* Headline"
(let* ((tree (org-element-parse-buffer))
(element (org-element-map tree 'headline 'identity nil t)))
(org-element-extract-element element)
tree))))
(eq 'org-data
(org-test-with-temp-text "* Headline"
(let* ((tree (org-element-parse-buffer))
(element (org-element-map tree 'headline 'identity nil t)))
(org-element-extract-element element)
(org-element-type tree)))))
;; Extract an element.
(should-not
(org-element-map
@ -3599,8 +3599,8 @@ Text
(parent (org-element-property
:parent (org-element-map tree 'italic 'identity nil t))))
(should parent)
(should (eq parent
(org-element-map tree 'headline 'identity nil t))))))
(should (equal parent
(org-element-map tree 'headline 'identity nil t))))))
@ -3858,7 +3858,7 @@ Text
;; `org-element-at-point' or `org-element-context', the list is
;; limited to the current section.
(should
(equal '(paragraph center-block)
(equal '(paragraph center-block section headline)
(org-test-with-temp-text
"* H1\n** H2\n#+BEGIN_CENTER\n*bold<point>*\n#+END_CENTER"
(mapcar #'car (org-element-lineage (org-element-context))))))
@ -3883,7 +3883,7 @@ Text
(org-element-lineage (org-element-context) '(example-block))))
;; Test WITH-SELF optional argument.
(should
(equal '(bold paragraph center-block)
(equal '(bold paragraph center-block section headline)
(org-test-with-temp-text
"* H1\n** H2\n#+BEGIN_CENTER\n*bold<point>*\n#+END_CENTER"
(mapcar #'car (org-element-lineage (org-element-context) nil t)))))