branch: main
commit aeb9f3d0505dfbb38610717df761c1a2abf16d9b
Author: Paul Nelson <[email protected]>
Commit: Paul Nelson <[email protected]>
Add signature support for macro folding
Support signature restrictions to limit the arguments consumed
when folding LaTeX macros (bug#78693).
* tex.el (TeX-find-macro-boundaries, TeX-find-macro-end)
(TeX-find-macro-end-helper): Add optional SIGNATURE argument to
restrict allowed arguments.
* tex-fold.el (TeX-fold--spec-type): New internal constant.
(TeX-fold-macro-spec-list, TeX-fold-math-spec-list): Add
signatures to default folding specifications. Update docstring
and type specification to document new signature format.
(TeX-fold-region-macro-or-env, TeX-fold-item): Extract signature
from spec and pass to 'TeX-fold-item-end'.
(TeX-fold-item-end): Add optional SIGNATURE parameter. Remove
special handling for math type, reverting d0a57d8d and
delegating instead to 'TeX-find-macro-end'.
(TeX-fold-stop-after-first-mandatory): New folding signature
predicate.
* latex.el (LaTeX-fold-math-spec-list): Add "signature 0" to
each entry, reflecting that it is a macro with no arguments.
* doc/auctex.texi (Folding): Document the new feature.
---
doc/auctex.texi | 17 ++++++
latex.el | 3 +-
tex-fold.el | 165 +++++++++++++++++++++++++++++++++-----------------------
tex.el | 126 ++++++++++++++++++++++++++++---------------
4 files changed, 198 insertions(+), 113 deletions(-)
diff --git a/doc/auctex.texi b/doc/auctex.texi
index d0d2e41c..99454cff 100644
--- a/doc/auctex.texi
+++ b/doc/auctex.texi
@@ -2931,6 +2931,23 @@ as a replacement for the macro. Such functions
typically return a string,
but may also return the symbol @code{abort} to indicate that the macro
should not be folded.
+Each specifier may instead be a cons cell @code{(@var{spec} . @var{sig})},
+where @var{spec} is as above and @var{sig} controls how many arguments are
+considered part of the macro to fold:
+@itemize @bullet
+@item
+@code{nil}: no limit (default).
+@item
+Integer @var{n}: stop after @var{n} total arguments.
+@item
+Cons @code{(@var{p} . @var{q})}: stop after @var{p} optional and @var{q}
+mandatory arguments.
+@item
+Predicate function: called repeatedly with the list of argument blocks
+encountered thus far. Returns non-@code{nil} to terminate scanning early.
+See the doc string of @code{TeX-find-macro-boundaries} for details.
+@end itemize
+
The placeholder is made by copying the text from the buffer together with
its properties, i.e.@: its face as well. If fontification has not
happened when this is done (e.g.@: because of lazy font locking) the
diff --git a/latex.el b/latex.el
index 757172d7..a8ed0127 100644
--- a/latex.el
+++ b/latex.el
@@ -6729,7 +6729,8 @@ char."
"Accents")))
submenu)))
(when (and (stringp tex-token) (integerp uchar) noargp)
- `(,(char-to-string uchar) (,tex-token)))))
+ ;; Each of these macros accepts 0 total arguments.
+ `((,(char-to-string uchar) . 0) (,tex-token)))))
`((nil "to" "" 8594)
(nil "gets" "" 8592)
,@LaTeX-math-default)))
diff --git a/tex-fold.el b/tex-fold.el
index 5ff9a6b1..20c8d27a 100644
--- a/tex-fold.el
+++ b/tex-fold.el
@@ -72,65 +72,86 @@ macros, `math' for math macros and `comment' for comments."
(const :tag "Math Macros" math)
(const :tag "Comments" comment)))
+(defconst TeX-fold--spec-type
+ '(choice
+ (string :tag "Display String")
+ (integer :tag "Number of argument" :value 1)
+ (function :tag "Function to execute")
+ (cons :tag "Spec with signature"
+ (choice (string :tag "Display String")
+ (integer :tag "Number of argument" :value 1)
+ (function :tag "Function to execute"))
+ (choice (const :tag "No restriction" nil)
+ (integer :tag "Max total arguments")
+ (cons :tag "Max optional and mandatory"
+ (integer :tag "Max optional arguments")
+ (integer :tag "Max mandatory arguments"))
+ (function :tag "Predicate function"))))
+ "Type spec used by TeX-fold defcustoms.")
+
(defcustom TeX-fold-macro-spec-list
- '(("[f]" ("footnote" "marginpar"))
- (TeX-fold-cite-display ("cite" "Cite"))
- (TeX-fold-textcite-display ("textcite" "Textcite"))
- (TeX-fold-parencite-display ("parencite" "Parencite"))
- (TeX-fold-footcite-display ("footcite" "footcitetext"))
- ("[l]" ("label"))
- ("[r]" ("ref" "pageref" "eqref" "footref"))
- ("[i]" ("index" "glossary"))
- ("[1]:||*" ("item"))
- ("..." ("dots"))
- ("(C)" ("copyright"))
- ("(R)" ("textregistered"))
- ("TM" ("texttrademark"))
- (TeX-fold-alert-display ("alert"))
- (TeX-fold-textcolor-display ("textcolor"))
+ '((("[f]" . (1 . 1)) ("footnote" "marginpar"))
+ ((TeX-fold-cite-display . (2 . 1)) ("cite" "Cite"))
+ ((TeX-fold-textcite-display . (2 . 1)) ("textcite" "Textcite"))
+ ((TeX-fold-parencite-display . (2 . 1)) ("parencite" "Parencite"))
+ ((TeX-fold-footcite-display . (2 . 1)) ("footcite" "footcitetext"))
+ (("[l]" . TeX-fold-stop-after-first-mandatory) ("label"))
+ (("[r]" . 1) ("ref" "pageref" "eqref" "footref"))
+ (("[i]" . (1 . 1)) ("index" "glossary"))
+ (("[1]:||*" . (1 . 0)) ("item"))
+ (("..." . 0) ("dots"))
+ (("(C)" . 0) ("copyright"))
+ (("(R)" . 0) ("textregistered"))
+ (("TM" . 0) ("texttrademark"))
+ ((TeX-fold-alert-display . 1) ("alert"))
+ ((TeX-fold-textcolor-display . (1 . 2)) ("textcolor"))
(TeX-fold-begin-display ("begin"))
- (TeX-fold-end-display ("end"))
+ ((TeX-fold-end-display . 1) ("end"))
(1 ("part" "chapter" "section" "subsection" "subsubsection"
"paragraph" "subparagraph"
"part*" "chapter*" "section*" "subsection*" "subsubsection*"
- "paragraph*" "subparagraph*"
- "emph" "textit" "textsl" "textmd" "textrm" "textsf" "texttt"
- "textbf" "textsc" "textup")))
+ "paragraph*" "subparagraph*"))
+ ((1 . (0 . 1)) ("emph" "textit" "textsl" "textmd" "textrm" "textsf"
"texttt"
+ "textbf" "textsc" "textup")))
"List of replacement specifiers and macros to fold.
-The first element of each item can be a string, an integer or a
-function symbol. The second element is a list of macros to fold
-without the leading backslash.
-
-If the first element is a string, it will be used as a display
-replacement for the whole macro. Numbers in braces, brackets,
-parens or angle brackets will be replaced by the respective macro
-argument. For example \"{1}\" will be replaced by the first
-mandatory argument of the macro. One can also define
-alternatives within the specifier which are used if an argument
-is not found. Alternatives are separated by \"||\". They are
-most useful with optional arguments. As an example, the default
-specifier for \\item is \"[1]:||*\" which means that if there is
-an optional argument, its value is shown followed by a colon. If
-there is no optional argument, only an asterisk is used as the
-display string.
-
-If the first element is an integer, the macro will be replaced by
-the respective macro argument.
-
-If the first element is a function symbol, the function will be
-called with all mandatory arguments of the macro and the result
-of the function call will be used as a replacement for the macro.
-Such functions typically return a string, but may also return the
-symbol `abort' to indicate that the macro should not be folded.
-
-Setting this variable does not take effect immediately. Use
-Customize or reset the mode."
- :type '(repeat (group (choice (string :tag "Display String")
- (integer :tag "Number of argument" :value 1)
- (function :tag "Function to execute"))
+The first element is of the form SPEC or (SPEC . SIG), where SPEC can be
+a string, an integer or a function symbol and SIG is described below.
+The second element is a list of macros to fold without the leading
+backslash.
+
+If SPEC is a string, it will be used as a display replacement for the
+whole macro. Numbers in braces, brackets, parens or angle brackets will
+be replaced by the respective macro argument. For example \"{1}\" will
+be replaced by the first mandatory argument of the macro. One can also
+define alternatives within the specifier which are used if an argument
+is not found. Alternatives are separated by \"||\". They are most
+useful with optional arguments. As an example, the default specifier
+for \\item is \"[1]:||*\" which means that if there is an optional
+argument, its value is shown followed by a colon. If there is no
+optional argument, only an asterisk is used as the display string.
+
+If SPEC is an integer, the macro will be replaced by the respective
+macro argument.
+
+If SPEC is a function symbol, the function will be called with all
+mandatory arguments of the macro and the result of the function call
+will be used as a replacement for the macro. Such functions typically
+return a string, but may also return the symbol `abort' to indicate that
+the macro should not be folded.
+
+SIG optionally restricts how many macro arguments are consumed. It
+should be of the form required by the SIGNATURE argument of
+`TeX-find-macro-boundaries'. For example, if SIGNATURE is an integer n,
+then at most n total arguments are consumed, while if it is a cons
+cell (p . q), then at most p optional and q mandatory arguments are
+allowed.
+
+Setting this variable does not take effect immediately. Use Customize
+or reset the mode."
+ :type `(repeat (group ,TeX-fold--spec-type
(repeat :tag "Macros" (string))))
- :package-version '(auctex . "14.0.8"))
+ :package-version '(auctex . "14.1.1"))
(defvar-local TeX-fold-macro-spec-list-internal nil
"Internal list of display strings and macros to fold.
@@ -156,9 +177,7 @@ and <mode-prefix>-fold-env-spec-list.")
(defcustom TeX-fold-math-spec-list nil
"List of display strings and math macros to fold."
- :type '(repeat (group (choice (string :tag "Display String")
- (integer :tag "Number of argument" :value 1)
- (function :tag "Function to execute"))
+ :type `(repeat (group ,TeX-fold--spec-type
(repeat :tag "Math Macros" (string)))))
(defvar-local TeX-fold-math-spec-list-internal nil
@@ -440,9 +459,14 @@ for macros and `math' for math macros."
(string (char-after
(match-end 0)))))))
(let* ((item-start (match-beginning 0))
- (display-string-spec (cadr (assoc item-name
- fold-list)))
- (item-end (TeX-fold-item-end item-start type))
+ (spec-sig? (cadr (assoc item-name fold-list)))
+ ;; spec-sig? is of the form SPEC or (SPEC . SIG).
+ (display-string-spec (if (consp spec-sig?)
+ (car spec-sig?)
+ spec-sig?))
+ (sig (when (consp spec-sig?)
+ (cdr spec-sig?)))
+ (item-end (TeX-fold-item-end item-start type sig))
(ov (TeX-fold-make-overlay item-start item-end type
display-string-spec)))
(TeX-fold-hide-item ov))))))))))
@@ -539,7 +563,7 @@ Return non-nil if an item was found and folded, nil
otherwise."
TeX-fold-math-spec-list-internal)
(t TeX-fold-macro-spec-list-internal)))
fold-item
- (display-string-spec
+ (spec-sig?
(or (catch 'found
(while fold-list
(setq fold-item (car fold-list))
@@ -552,7 +576,13 @@ Return non-nil if an item was found and folded, nil
otherwise."
(if (eq type 'env)
TeX-fold-unspec-env-display-string
TeX-fold-unspec-macro-display-string))))
- (item-end (TeX-fold-item-end item-start type))
+ ;; spec-sig? is of the form SPEC or (SPEC . SIG).
+ (display-string-spec (if (consp spec-sig?)
+ (car spec-sig?)
+ spec-sig?))
+ (sig (when (consp spec-sig?)
+ (cdr spec-sig?)))
+ (item-end (TeX-fold-item-end item-start type sig))
(ov (TeX-fold-make-overlay item-start item-end type
display-string-spec)))
(TeX-fold-hide-item ov))))))
@@ -907,10 +937,12 @@ display property."
(overlay-put ov 'display display-string))
ov))
-(defun TeX-fold-item-end (start type)
+(defun TeX-fold-item-end (start type &optional signature)
"Return the end of an item of type TYPE starting at START.
TYPE can be either `env' for environments, `macro' for macros or
-`math' for math macros."
+`math' for math macros.
+Optional SIGNATURE, as in `TeX-find-macro-boundaries', restricts the
+allowed arguments of LaTeX macros."
(save-excursion
(cond ((and (eq type 'env)
(eq major-mode 'ConTeXt-mode))
@@ -926,15 +958,9 @@ TYPE can be either `env' for environments, `macro' for
macros or
(goto-char (1+ start))
(LaTeX-find-matching-end)
(point))
- ((eq type 'math)
- (goto-char (1+ start))
- (if (zerop (skip-chars-forward "A-Za-z@"))
- (forward-char)
- (skip-chars-forward "*"))
- (point))
(t
(goto-char start)
- (TeX-find-macro-end)))))
+ (TeX-find-macro-end signature)))))
(defun TeX-fold-overfull-p (ov-start ov-end display-string)
"Return t if an overfull line will result after adding an overlay.
@@ -1095,6 +1121,9 @@ breaks will be replaced by spaces."
(dolist (ov overlays)
(TeX-fold-hide-item ov)))))
+(defun TeX-fold-stop-after-first-mandatory (args)
+ "Return nil when final element of ARGS starts with \"{\"."
+ (and args (string-prefix-p "{" (car (last args)))))
;;; Removal
diff --git a/tex.el b/tex.el
index 18438a81..286e33c2 100644
--- a/tex.el
+++ b/tex.el
@@ -5790,11 +5790,23 @@ If LIMIT is non-nil, do not search further up than this
position
in the buffer."
(TeX-find-balanced-brace -1 depth limit))
-(defun TeX-find-macro-boundaries (&optional lower-bound)
+(defun TeX-find-macro-boundaries (&optional lower-bound signature)
"Return a cons containing the start and end of a macro.
If LOWER-BOUND is given, do not search backward further than this
point in buffer. Arguments enclosed in brackets or braces are
-considered part of the macro."
+considered part of the macro.
+
+If SIGNATURE is given, restrict the total number of arguments. If
+SIGNATURE is an integer N, allow at most N total arguments. If
+SIGNATURE is a cons cell (P . Q), allow at most P optional and Q
+mandatory arguments.
+
+Finally, SIGNATURE may be a function, called before each new argument is
+consumed with a single list argument consisting of the argument blocks
+encountered thus far. For example, with point before
+\"\\begin{equation}\", it is called first with the empty list () and
+then with the single element list (\"{equation}\"). If SIGNATURE
+returns non-nil, then no further macro arguments are consumed."
;; FIXME: Pay attention to `texmathp-allow-detached-args' and
;; `reftex-allow-detached-macro-args'.
;; Should we handle cases like \"{o} and \\[3mm] (that is, a macro
@@ -5839,16 +5851,17 @@ considered part of the macro."
;; Search forward for the end of the macro.
(when start-point
(save-excursion
- (goto-char (TeX-find-macro-end-helper start-point))
+ (goto-char (TeX-find-macro-end-helper start-point signature))
(if (< orig-point (point))
(cons start-point (point))
nil))))))
-(defun TeX-find-macro-end-helper (start)
+(defun TeX-find-macro-end-helper (start &optional signature)
"Find the end of a macro given its START.
START is the position just before the starting token of the macro.
If the macro is followed by square brackets or curly braces,
-those will be considered part of it."
+those will be considered part of it. SIGNATURE, as in
+`TeX-find-macro-boundaries', restricts how many arguments are allowed."
(save-excursion
(save-match-data
(catch 'found
@@ -5856,43 +5869,67 @@ those will be considered part of it."
(if (zerop (skip-chars-forward "A-Za-z@"))
(forward-char)
(skip-chars-forward "*"))
- (while (not (eobp))
- (cond
- ;; Skip over pairs of square brackets
- ((or (looking-at "[ \t]*\n?[ \t]*\\(\\[\\)") ; Be conservative:
Consider
+ (let* ((max-tot (and (integerp signature) signature))
+ (max-opt (and (consp signature) (car signature)))
+ (max-req (and (consp signature) (cdr signature)))
+ (num-opt 0)
+ (num-req 0)
+ (sig-pred (when (functionp signature) signature))
+ last-arg-start last-args)
+ (while (not (eobp))
+ (when (or (and max-tot (>= (+ num-opt num-req) max-tot))
+ (and sig-pred
+ (progn
+ (when last-arg-start
+ (let ((arg (buffer-substring-no-properties
+ last-arg-start (point))))
+ (setq last-args (nconc last-args (list
arg)))))
+ (funcall sig-pred last-args))))
+ (throw 'found (point)))
+ (cond
+ ;; Skip over pairs of square brackets
+ ((or (looking-at "[ \t]*\n?[ \t]*\\(\\[\\)") ; Be conservative:
Consider
; only consecutive lines.
- (and (looking-at (concat "[ \t]*" TeX-comment-start-regexp))
- (save-excursion
- (forward-line 1)
- (looking-at "[ \t]*\\(\\[\\)"))))
- (goto-char (match-beginning 1))
- ;; Imitate `font-latex-find-matching-close', motivated by
- ;; examples like \begin{enumerate}[a{]}].
- (let ((syntax (TeX-search-syntax-table ?\[ ?\]))
- (parse-sexp-ignore-comments
- (not (derived-mode-p 'docTeX-mode))))
- (modify-syntax-entry ?\{ "|" syntax)
- (modify-syntax-entry ?\} "|" syntax)
- (modify-syntax-entry ?\\ "/" syntax)
- (condition-case nil
- (with-syntax-table syntax
- (forward-sexp))
- (scan-error (throw 'found (point))))))
- ;; Skip over pairs of curly braces
- ((or (looking-at "[ \t]*\n?[ \t]*{") ; Be conservative: Consider
+ (and (looking-at (concat "[ \t]*" TeX-comment-start-regexp))
+ (save-excursion
+ (forward-line 1)
+ (looking-at "[ \t]*\\(\\[\\)"))))
+ (when (and max-opt (>= num-opt max-opt))
+ (throw 'found (point)))
+ (cl-incf num-opt)
+ (goto-char (match-beginning 1))
+ (setq last-arg-start (point))
+ ;; Imitate `font-latex-find-matching-close', motivated by
+ ;; examples like \begin{enumerate}[a{]}].
+ (let ((syntax (TeX-search-syntax-table ?\[ ?\]))
+ (parse-sexp-ignore-comments
+ (not (derived-mode-p 'docTeX-mode))))
+ (modify-syntax-entry ?\{ "|" syntax)
+ (modify-syntax-entry ?\} "|" syntax)
+ (modify-syntax-entry ?\\ "/" syntax)
+ (condition-case nil
+ (with-syntax-table syntax
+ (forward-sexp))
+ (scan-error (throw 'found (point))))))
+ ;; Skip over pairs of curly braces
+ ((or (looking-at "[ \t]*\n?[ \t]*{") ; Be conservative: Consider
; only consecutive lines.
- (and (looking-at (concat "[ \t]*" TeX-comment-start-regexp))
- (save-excursion
- (forward-line 1)
- (looking-at "[ \t]*{"))))
- (goto-char (match-end 0))
- (goto-char (or (TeX-find-closing-brace)
- ;; If we cannot find a regular end, use the
- ;; next whitespace.
- (save-excursion (skip-chars-forward "^ \t\n")
- (point)))))
- (t
- (throw 'found (point)))))
+ (and (looking-at (concat "[ \t]*" TeX-comment-start-regexp))
+ (save-excursion
+ (forward-line 1)
+ (looking-at "[ \t]*{"))))
+ (when (and max-req (>= num-req max-req))
+ (throw 'found (point)))
+ (cl-incf num-req)
+ (goto-char (match-end 0))
+ (setq last-arg-start (1- (point)))
+ (goto-char (or (TeX-find-closing-brace)
+ ;; If we cannot find a regular end, use the
+ ;; next whitespace.
+ (save-excursion (skip-chars-forward "^ \t\n")
+ (point)))))
+ (t
+ (throw 'found (point))))))
;; Make sure that this function does not return nil, even
;; when the above `while' loop is totally skipped. (bug#35638)
(throw 'found (point))))))
@@ -5904,11 +5941,12 @@ in buffer. Arguments enclosed in brackets or braces are
considered part of the macro."
(car (TeX-find-macro-boundaries limit)))
-(defun TeX-find-macro-end ()
+(defun TeX-find-macro-end (&optional signature)
"Return the end of a macro.
-Arguments enclosed in brackets or braces are considered part of
-the macro."
- (cdr (TeX-find-macro-boundaries)))
+Arguments enclosed in brackets or braces are considered part of the
+macro. SIGNATURE, as in `TeX-find-macro-boundaries', restricts how many
+arguments are allowed."
+ (cdr (TeX-find-macro-boundaries nil signature)))
(defun TeX-search-forward-unescaped (string &optional bound noerror)
"Search forward from point for unescaped STRING.