branch: externals/auctex-label-numbers
commit 462f9bf626d3f6c951905d37c1f8aaddf50cbc3b
Author: Paul Nelson <[email protected]>
Commit: GitHub <[email protected]>
Add files via upload
---
tex-numbers.el | 224 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 224 insertions(+)
diff --git a/tex-numbers.el b/tex-numbers.el
new file mode 100644
index 0000000000..4eea0dcfa9
--- /dev/null
+++ b/tex-numbers.el
@@ -0,0 +1,224 @@
+;;; tex-numbers.el --- numbering for LaTeX previews and folds -*-
lexical-binding: t; -*-
+
+;; Copyright (C) 2024 Paul D. Nelson
+
+;; Author: Paul D. Nelson <[email protected]>
+;; Version: 0.0
+;; URL: https://github.com/ultronozm/czm-preview.el
+;; Package-Requires: ((emacs "26.1") (auctex))
+;; Keywords: tex
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This package augments the preview and folding features of AUCTeX:
+;; previews of labeled equations are numbered as in the compiled
+;; document, and the the macros \\ref, \\eqref, and \\label are folded
+;; with the corresponding numbers.
+;;
+;; Install via TODO.
+;;
+;; TEMPORARY NOTE: this package currently only works with the master
+;; branch of AUCTeX. The more elaborate package
+;; https://github.com/ultronozm/czm-tex-fold.el works with released
+;; versions, but relies on advice, etc.
+;;
+;; Activate via M-x tex-numbers-mode, or by adding to your init file:
+;;
+;; (use-package tex-numbers
+;; :hook
+;; (LaTeX-mode . tex-numbers-mode))
+;;
+;; The package provides an interface for retrieving label numbers in
+;; LaTeX documents. This interface is used to implement a global
+;; minor mode, `tex-numbers-mode', which enables the noted features.
+;;
+;; The label numbers are retrieved from the aux file of the compiled
+;; document. To update them, one should compile the document,
+;; regenerate the previews and refresh the folds.
+;;
+;; By customizing the variable `tex-numbers-label-to-number-function', one
+;; can specify a different way to retrieve label numbers, e.g., by
+;; querying an LSP server.
+
+;;; Code:
+
+(require 'tex)
+(require 'latex)
+(require 'tex-fold)
+(require 'preview)
+
+(defgroup tex-numbers nil
+ "Numbering for LaTeX previews and folds."
+ :group 'AUCTeX)
+
+(defvar tex-numbers-cache (make-hash-table :test 'equal)
+ "Cache of label numbers from aux files.
+The keys are aux file names. The values are hash tables, mapping label
+strings to label number strings.")
+
+(defun tex-numbers-update-cache (aux-file)
+ "Update the cache for AUX-FILE.
+Return the updated cache, or nil if the aux file does not exist."
+ (when (file-exists-p aux-file)
+ (with-temp-buffer
+ (insert-file-contents aux-file)
+ (let ((cache (make-hash-table :test 'equal))
+ (pattern "\\newlabel{\\([^}]+\\)}{{\\([^}]+\\)}"))
+ (save-excursion
+ (goto-char (point-min))
+ (while (re-search-forward pattern nil t)
+ (let ((label (match-string 1))
+ (number (match-string 2)))
+ (puthash label number cache))))
+ (puthash 'timestamp (current-time) cache)
+ (puthash aux-file cache tex-numbers-cache)
+ cache))))
+
+(defun tex-numbers-label-to-number-helper (label aux-file)
+ "Get the number of LABEL from the aux file AUX-FILE.
+Check the cache first, and update it if the aux file has changed.
+Return the label number as a string, or nil if the label cannot be
+found."
+ (let ((cache (gethash aux-file tex-numbers-cache)))
+ (if (or (not cache)
+ (time-less-p (gethash 'timestamp cache)
+ (nth 5 (file-attributes aux-file))))
+ (setq cache (tex-numbers-update-cache aux-file)))
+ (when cache
+ (gethash label cache))))
+
+(defcustom tex-numbers-label-to-number-function nil
+ "Function to retrieve label numbers.
+If non-nil, `tex-numbers-label-to-number' delegates to this function.
+The function should take a label string as its argument and return the
+corresponding label number as a string, or nil if that number cannot be
+retrieved."
+ :type '(choice (const :tag "Default" nil) function)
+ :group 'tex-numbers)
+
+(defun tex-numbers-label-to-number (label)
+ "Get number of LABEL for current tex buffer.
+If the buffer does not point to a file, or if the corresponding
+aux file does not exist, or if the label cannot be found, then
+return nil. Otherwise, return the label number as a string. If
+the label is found in an external document, prefix the string
+with \"X\"."
+ (if tex-numbers-label-to-number-function
+ (funcall tex-numbers-label-to-number-function label)
+ (or
+ (when-let* ((aux-file (TeX-master-file "aux")))
+ (tex-numbers-label-to-number-helper label aux-file))
+ ;; If we can't retrieve the label from the main file, then we look
+ ;; at any external documents.
+ (save-excursion
+ (save-restriction
+ (widen)
+ (goto-char (point-min))
+ (let (found)
+ (while (and (null found)
+ (re-search-forward "\\\\externaldocument{\\([^}]+\\)}"
nil t))
+ (let* ((filename (concat (match-string 1) ".aux")))
+ (setq found (tex-numbers-label-to-number-helper label
filename))))
+ (when found
+ (concat "X" found))))))))
+
+(defun tex-numbers-preview-preprocessor (str)
+ "Preprocess STR for preview by adding tags to labels.
+Uses `tex-numbers-label-to-number-function' to retrieve label numbers."
+ (let ((buf (current-buffer)))
+ (with-temp-buffer
+ (insert str)
+ (goto-char (point-min))
+ (while (re-search-forward "\\\\label{\\([^}]+\\)}" nil t)
+ (let ((label (match-string 1)))
+ (when-let ((number
+ (with-current-buffer buf
+ (tex-numbers-label-to-number label))))
+ (when (let ((comment-start-skip
+ "\\(\\(^\\|[^\\
+]\\)\\(\\\\\\\\\\)*\\)\\(%+[ ]*\\)"))
+ ;; HACK: texmathp expects to be run in LaTeX-mode,
+ ;; but here we are in a temporary buffer.
+ (texmathp))
+ (insert (format "\\tag{%s}" number))))))
+ (buffer-substring-no-properties (point-min) (point-max)))))
+
+(defun tex-numbers-ref-helper (label default)
+ "Helper function for `tex-numbers-ref-display'.
+Returns a fold display string for LABEL (retrieved via
+`tex-numbers-label-to-number-function'), or DEFAULT if the label number cannot
+be retrieved."
+ (format "[%s]" (or (tex-numbers-label-to-number label) default)))
+
+(defun tex-numbers-ref-display (label &rest _args)
+ "Fold display for a \\ref{LABEL} macro."
+ (tex-numbers-ref-helper label "r"))
+
+(defun tex-numbers-eqref-display (label &rest _args)
+ "Fold display for a \\eqref{LABEL} macro."
+ (tex-numbers-ref-helper label "e"))
+
+(defun tex-numbers-label-display (label &rest _args)
+ "Fold display for a \\label{LABEL} macro."
+ (tex-numbers-ref-helper label "l"))
+
+(defvar tex-numbers--saved-spec-list nil
+ "Saved values from `TeX-fold-macro-spec-list'.")
+
+(defcustom tex-numbers-macro-list '("ref" "eqref" "label")
+ "List of macros to fold with theorem or equation numbers.
+Each element describes a LaTeX macro that takes a label as its argument.
+There should be a corresponding function `tex-numbers-MACRO-display'
+that returns a fold display string for that macro."
+ :type '(repeat string)
+ :group 'tex-numbers)
+
+;;;###autoload
+(define-minor-mode tex-numbers-mode
+ "Toggle `tex-numbers' mode."
+ :global t
+ :lighter nil
+ (cond
+ (tex-numbers-mode
+ (setq preview-preprocess-function #'tex-numbers-preview-preprocessor)
+ (require 'tex-fold)
+ (dolist (macro tex-numbers-macro-list)
+ (let ((func (intern (format "tex-numbers-%s-display" macro))))
+ (dolist (spec TeX-fold-macro-spec-list)
+ (when (and (member macro (cadr spec))
+ (not (eq (car spec) func)))
+ (push (cons macro (car spec)) tex-numbers--saved-spec-list)
+ (setcdr spec (list (cl-remove macro (cadr spec) :test 'equal)))))
+ (add-to-list 'TeX-fold-macro-spec-list (list func (list macro)))))
+ (when TeX-fold-mode
+ (TeX-fold-mode 1)))
+ (t
+ (setq preview-preprocess-function nil)
+ (dolist (macro tex-numbers-macro-list)
+ (let ((func (intern (format "tex-numbers-%s-display" macro))))
+ (setq TeX-fold-macro-spec-list
+ (cl-remove-if (lambda (elem) (eq (car elem) func))
+ TeX-fold-macro-spec-list)))
+ (when-let ((saved (assoc macro tex-numbers--saved-spec-list)))
+ (dolist (spec TeX-fold-macro-spec-list)
+ (when (eq (car spec) (cdr saved))
+ (push macro (cadr spec))))))
+ (setq tex-numbers--saved-spec-list nil)
+ (when TeX-fold-mode
+ (TeX-fold-mode 1)))))
+
+(provide 'tex-numbers)
+;;; tex-numbers.el ends here