branch: main commit aeb9f3d0505dfbb38610717df761c1a2abf16d9b Author: Paul Nelson <ultr...@gmail.com> Commit: Paul Nelson <ultr...@gmail.com>
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.