org-latex-preview: Improve env numbering calc

* lisp/org-latex-preview.el (org-latex-preview--get-numbered-environments,
org-latex-preview--environment-numbering-table): Make numbering
calculations more accurate, at the expense of a ~10x increase in the
time to calculate numbering in a document, mitigated by the use of
caching — resulting in a ~2x improvement compared to the simpler method
in subsequent runs.

(org-latex-preview--numbered-environments,
org-latex-preview--numbered-environments-single,
org-latex-preview--numbered-environments-multi,
org-latex-preview--numbered-environments-all): Split
`org-latex-preview--numbered-environments' into
`org-latex-preview--numbered-environments-single' and
`org-latex-preview--numbered-environments-multi', then rename
`org-latex-preview--numbered-environments' to
`org-latex-preview--numbered-environments-all' for the sake of clarity.

Determining the count of numbered equations in a LaTeX environment is
non-trivial.  Previously, a simple heuristic was used that counted the
number of lines ending with "\\" in particular environments, then
subtracted the number of "\nonumber" and "\tag{...}" instances.

However, lines can end with "\\" without contributing to the number of
equations in the environment, consider the "cases" and "bmatrix"
environments.  To improve the heuristic, we (with some care) remove
/all/ inner environments from the LaTeX maths environment before
processing.  Since this takes more effort, we split the numbered
environments into those we know will contain exactly one numbered
equation where we can avoid the extra processing, and those that require
the more sophisticated heuristics.

The new org-element caching API is very helpful for recovering the
performance cost of this extra computation in the long run.
This commit is contained in:
TEC 2024-01-04 23:17:14 +08:00
parent 2ff67428fa
commit 0090e25460
Signed by: tec
SSH Key Fingerprint: SHA256:eobz41Mnm0/iYWBvWThftS0ElEs1ftBr6jamutnXc/A
1 changed files with 118 additions and 32 deletions

View File

@ -1160,12 +1160,22 @@ NUMBER is the equation number that should be used, if applicable."
bg
number))))
(defconst org-latex-preview--numbered-environments
'("equation" "eqnarray" "math" "displaymath" ; latex.ltx
"align" "gather" "multiline" "flalign" "alignat" ; amsmath.sty
"xalignat" "xxalignat" "subequations" ; amsmath.sty
"dmath" "dseries" "dgroup" "darray" ; breqn.sty
(defconst org-latex-preview--numbered-environments-single
'("equation" "math" "displaymath" ; latex.ltx
"dmath" ; breqn.sty
"empheq") ; empheq.sty
"List of LaTeX environments that contain a single numbered equation.")
(defconst org-latex-preview--numbered-environments-multi
'("eqnarray" ; latex.ltx
"align" "alignat" "flalign" "gather" "multiline" ; amsmath.sty
"xalignat" "xxalignat" "subequations" ; amsmath.sty
"dseries" "dgroup" "darray") ; breqn.sty
"List of LaTeX environments that contain multiple numbered equations.")
(defconst org-latex-preview--numbered-environments-all
(append org-latex-preview--numbered-environments-single
org-latex-preview--numbered-environments-multi)
"List of LaTeX environments which produce numbered equations.")
(defun org-latex-preview--environment-numbering-table (&optional parse-tree)
@ -1174,25 +1184,101 @@ If the org-element cache is active or PARSE-TREE is provided, the
hash table will use `eq' equality, otherwise `equal' will be
used. When PARSE-TREE is provided, it is passed onto
`org-latex-preview--get-numbered-environments'."
(let ((table (make-hash-table :test (if (or parse-tree (org-element--cache-active-p))
#'eq #'equal)))
(let ((table (make-hash-table
:test (if (or parse-tree (org-element--cache-active-p))
#'eq #'equal)))
(counter 1))
(save-match-data
(dolist (element (org-latex-preview--get-numbered-environments
nil nil parse-tree))
(let ((content (org-element-property :value element)))
(puthash element counter table)
(if (string-match-p "\\`\\\\begin{[^}]*align" content)
(let ((beg (org-element-property :begin element))
(end (org-element-property :end element)))
(cl-incf counter (1+ (how-many "\\\\$" beg end)))
(cl-decf counter (how-many "\\nonumber" beg end))
(cl-decf counter (how-many "\\tag{" beg end)))
(unless (or (string-match-p "\\nonumber" content)
(string-match-p "\\tag{" content))
(cl-incf counter))))))
(puthash element counter table)
(cl-incf counter (org-latex-preview--count-numbered-equations element))))
table))
(defvar org-latex-preview--numbering-count-buffer nil)
(defun org-latex-preview--count-numbered-equations (element)
"Count the number of numbered equations within Org ELEMENT.
Note: this function changes the current match data."
(or (org-element-cache-get-key element :latex-numbered-equation-count)
(let ((count 0)
environment-name)
(with-current-buffer (org-element-property :buffer element)
(save-excursion
(goto-char (org-element-property :begin element))
(when (looking-at "\\\\begin{\\([^}]+\\)}")
(setq environment-name (match-string 1)))
(cond
((member environment-name org-latex-preview--numbered-environments-single)
(unless (or (search-forward "\\nonumber" (org-element-property :end element) t)
(search-forward "\\tag{" (org-element-property :end element) t))
(cl-incf count 1)))
((member environment-name org-latex-preview--numbered-environments-multi)
(cl-incf count (org-latex-preview--count-numbered-equations-multi
element environment-name))))))
(org-element-cache-store-key element :latex-numbered-equation-count count)
count)))
(defun org-latex-preview--count-numbered-equations-multi (element environment-name)
"Count the number of numbered equations within Org ELEMENT.
Element is assumed to be a LaTeX ENVIRONMENT-NAME environment.
Note: this function changes the current match data."
(let ((temp-buffer (or (and (buffer-live-p org-latex-preview--numbering-count-buffer)
org-latex-preview--numbering-count-buffer)
(setq org-latex-preview--numbering-count-buffer
(get-buffer-create " *Org LaTeX preview numbering calculation*"))))
(count-instances
(lambda (needle)
(let ((count 0))
(save-excursion
(while (search-forward needle nil t)
(cl-incf count))
count))))
(count 0))
(with-current-buffer temp-buffer
(erase-buffer)
(insert-buffer-substring
(org-element-property :buffer element)
(org-element-property :begin element) (org-element-property :end element))
(goto-char (point-min))
(org-skip-whitespace)
;; Remove the "outer" environment, so we can then
;; easily delete all inner environments.
(delete-region (point-min) (+ (point) 8 (length environment-name))) ; 8 = (length "\\begin{}")
(goto-char (point-max))
(when (search-backward (format "\\end{%s}" environment-name) nil t)
(delete-region (match-beginning 0) (match-end 0)))
;; Remove all "inner" environments. We do this since
;; they can contain false-positive indicators of numbered
;; equations.
(goto-char (point-min))
(while (re-search-forward "\\\\begin{\\([^}]+\\)}" nil t)
(let* ((start (match-beginning 0))
(max-end (point-max))
(env-name (match-string 1))
(begin-template (format "\\begin{%s}" env-name))
(end-template (format "\\end{%s}" env-name))
(env-depth 1))
(while (> env-depth 0)
(let ((next-begin (or (save-excursion (search-forward begin-template nil t)) max-end))
(next-end (or (save-excursion (search-forward end-template nil t)) max-end)))
(cond
((= next-begin next-end)
(setq env-depth 0))
((< next-end next-begin)
(cl-decf env-depth))
(t (cl-incf env-depth)))
(goto-char (min next-begin next-end))))
(delete-region start (match-end 0))))
;; Count all hard newlines, as they indicate the start of a new equation,
;; then decrement by the number of \nonumber and \tag-d instances.
(goto-char (point-min))
(cl-incf count (1+ (funcall count-instances "\\\\")))
(cl-decf count (funcall count-instances "\\nonumber"))
(cl-decf count (funcall count-instances "\\tag{"))
(erase-buffer))
count))
(defun org-latex-preview--get-numbered-environments (&optional beg end parse-tree)
"Find all numbered environments between BEG and END.
If PARSE-TREE is provided, it will be used insead of
@ -1208,22 +1294,22 @@ If PARSE-TREE is provided, it will be used insead of
(let* ((content (org-element-property :value datum))
(env (and (string-match "\\`\\\\begin{\\([^}]+\\)}" content)
(match-string 1 content))))
(and (member env org-latex-preview--numbered-environments)
(and (member env org-latex-preview--numbered-environments-all)
datum))))))
((org-element--cache-active-p)
(org-element-cache-map
(lambda (datum)
(and (<= (or beg (point-min)) (org-element-property :begin datum)
(org-element-property :end datum) (or end (point-max)))
(let* ((content (org-element-property :value datum))
(env (and (string-match "\\`\\\\begin{\\([^}]+\\)}" content)
(match-string 1 content))))
(and (member env org-latex-preview--numbered-environments)
datum))))
:granularity 'element
:restrict-elements '(latex-environment)
:from-pos beg
:to-pos (or end (point-max-marker))))
(lambda (datum)
(and (<= (or beg (point-min)) (org-element-property :begin datum)
(org-element-property :end datum) (or end (point-max)))
(let* ((content (org-element-property :value datum))
(env (and (string-match "\\`\\\\begin{\\([^}]+\\)}" content)
(match-string 1 content))))
(and (member env org-latex-preview--numbered-environments-all)
datum))))
:granularity 'element
:restrict-elements '(latex-environment)
:from-pos beg
:to-pos (or end (point-max-marker))))
(t
(org-element-map
(org-element-parse-buffer 'element)
@ -1234,7 +1320,7 @@ If PARSE-TREE is provided, it will be used insead of
(let* ((content (org-element-property :value datum))
(env (and (string-match "\\`\\\\begin{\\([^}]+\\)}" content)
(match-string 1 content))))
(and (member env org-latex-preview--numbered-environments)
(and (member env org-latex-preview--numbered-environments-all)
(save-excursion
(goto-char (org-element-property :begin datum))
(org-element-context))))))))))