From 0090e2546097e573c5320d3e00e0ae178861a503 Mon Sep 17 00:00:00 2001 From: TEC Date: Thu, 4 Jan 2024 23:17:14 +0800 Subject: [PATCH] org-latex-preview: Improve env numbering calc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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. --- lisp/org-latex-preview.el | 150 ++++++++++++++++++++++++++++++-------- 1 file changed, 118 insertions(+), 32 deletions(-) diff --git a/lisp/org-latex-preview.el b/lisp/org-latex-preview.el index 940a9be38..34325c71a 100644 --- a/lisp/org-latex-preview.el +++ b/lisp/org-latex-preview.el @@ -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))))))))))