[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/auctex 35ec84ba6f 07/48: Add capf for LaTeX marco/envir
From: |
Tassilo Horn |
Subject: |
[elpa] externals/auctex 35ec84ba6f 07/48: Add capf for LaTeX marco/environment arguments |
Date: |
Fri, 18 Nov 2022 14:27:42 -0500 (EST) |
branch: externals/auctex
commit 35ec84ba6faf078b114d5df62513d81900cf3b6c
Author: Arash Esbati <arash@gnu.org>
Commit: Arash Esbati <arash@gnu.org>
Add capf for LaTeX marco/environment arguments
* doc/changes.texi: Announce the new feature.
* doc/auctex.texi (Completion): Document the main new function.
* latex.el: Add functions for completion-at-point inside
marco/environment arguments in LaTeX buffers.
(LaTeX-common-initialization): Append the entry point
`LaTeX--arguments-completion-at-point' to
`completion-at-point-functions'.
---
doc/auctex.texi | 13 +-
doc/changes.texi | 6 +
latex.el | 470 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 488 insertions(+), 1 deletion(-)
diff --git a/doc/auctex.texi b/doc/auctex.texi
index d323e6f272..5f8c725714 100644
--- a/doc/auctex.texi
+++ b/doc/auctex.texi
@@ -1326,7 +1326,7 @@ define and register custom @code{completion-at-point}
functions and when the
user invokes @code{completion-at-point} (usually bound to
@kbd{M-@key{TAB}}), all such registered functions are consulted for
checking for possible completions. Modern completion UIs like
-@i{company-mode} support this completion-at-point facility.
+@i{company-mode} or @i{corfu} support this completion-at-point facility.
@defun TeX--completion-at-point
@AUCTeX{}'s completion-at-point function which is automatically added to
@@ -1339,6 +1339,17 @@ It offers the same completion candidates as would
like @i{company-mode}.
@end defun
+@defun LaTeX--arguments-completion-at-point
+@AUCTeX{}'s completion-at-point function inside arguments which is
+automatically added to @code{completion-at-point-functions} in @LaTeX{}
+buffers.
+
+It offers the completion candidates stored in the variables
+@code{TeX-symbol-list} and @code{LaTeX-environment-list} for single
+candidate, multiple candidates separated by commas, or key-value
+candidates separated by commas and/or equal signs.
+@end defun
+
A more direct way to insert a macro is with @code{TeX-insert-macro},
bound to @kbd{C-c C-m} which is equivalent to @kbd{C-c @key{RET}}. It
has the advantage over completion that it knows about the argument of
diff --git a/doc/changes.texi b/doc/changes.texi
index debfec688b..67e9e458d4 100644
--- a/doc/changes.texi
+++ b/doc/changes.texi
@@ -11,6 +11,12 @@
@heading News since last release
@itemize @bullet
+@item
+@AUCTeX{} supports completion-at-point of macro and environment arguments
+in @LaTeX{} buffers. The responsible function recognizes the argument
+position and extracts the corresponding candidates from the variables
+@code{TeX-symbol-list} and @code{LaTeX-environment-list}.
+
@item
@AUCTeX{} underlines the argument of macros which produce underlined text
in the final product with @code{font-latex-underline-face}. The
diff --git a/latex.el b/latex.el
index 901ddfd7d0..0c83a3a30b 100644
--- a/latex.el
+++ b/latex.el
@@ -7184,6 +7184,471 @@ page without enough text on it. ")
Used as buffer local value of `TeX-error-description-list-local'.
See its doc string for detail.")
+
+;;; LaTeX Capf for macro/environment arguments:
+
+;; tex.el defines the function `TeX--completion-at-point' which
+;; provides completion at point for (La)TeX macros. Here we define
+;; `LaTeX--arguments-completion-at-point' which is the entry point for
+;; completion at point when inside a macro or environment argument.
+;; The general idea is:
+;;
+;; - Find out in which argument of macro/env the point is; this is
+;; done by the function `LaTeX-what-macro'.
+;;
+;; - Match the result against the information available in
+;; `TeX-symbol-list' or `LaTeX-environment-list' by the function
+;; `LaTeX-completion-parse-args'.
+;;
+;; - If there is a match, pass it to `LaTeX-completion-parse-arg'
+;; (note the missing `s') which parses the match and runs the
+;; corresponding function to calculate the candidates. These are the
+;; functions `LaTeX-completion-candidates-key-val',
+;; `LaTeX-completion-candidates-completing-read-multiple', and
+;; `LaTeX-completion-candidates-completing-read'.
+;;
+;; Two mapping variables `LaTeX-completion-function-map-alist-keyval'
+;; and `LaTeX-completion-function-map-alist-cr' are provided in order
+;; to allow a redirection of the entry in `TeX-symbol-list' or
+;; `LaTeX-environment-list' to another function.
+
+(defvar LaTeX-completion-macro-delimiters
+ '((?\[ . ?\])
+ (?\{ . ?\})
+ (?\( . ?\))
+ (?\< . ?\>))
+ "List of characters delimiting mandatory and optional arguments.
+Each element in the list is cons with opening char as car and the
+closing char as cdr.")
+
+(defun LaTeX-completion-macro-delimiters (&optional which)
+ "Return elements of the variable `LaTeX-completion-macro-delimiters'.
+If the optional WHICH is the symbol `open', return the car's of
+each element in the variable `LaTeX-completion-macro-delimiters'.
+If it is the symbol `close', return the cdr's. If omitted or
+nil, return all elements."
+ (cond ((eq which 'open)
+ (mapcar #'car LaTeX-completion-macro-delimiters))
+ ((eq which 'close)
+ (mapcar #'cdr LaTeX-completion-macro-delimiters))
+ (t
+ (append
+ (mapcar #'car LaTeX-completion-macro-delimiters)
+ (mapcar #'cdr LaTeX-completion-macro-delimiters)))))
+
+(defun LaTeX-move-to-previous-arg (&optional bound)
+ "Move backward to the closing parenthesis of the previous argument.
+Closing parenthesis is in this context all characters which can
+be used to delimit an argument. Currently, these are the
+following characters:
+
+ } ] ) >
+
+This happens under the assumption that we are in front of a macro
+argument. This function understands the splitting of macros over
+several lines in TeX."
+ (cond
+ ;; Just to be quick:
+ ((memql (preceding-char) (LaTeX-completion-macro-delimiters 'close)))
+ ;; Do a search:
+ ((re-search-backward
+ "[]})>][ \t]*[\n\r]?\\([ \t]*%[^\n\r]*[\n\r]\\)*[ \t]*\\=" bound t)
+ (goto-char (1+ (match-beginning 0)))
+ t)
+ (t nil)))
+
+(defun LaTeX-what-macro (&optional bound)
+ "Find out if point is within the arguments of any TeX-macro.
+The return value is
+
+ (\"name\" mac-or-env total-num type opt-num opt-distance)
+
+\"name\" is the name of the macro (without backslash) or
+ environment as a string.
+mac-or-env is one of the symbols `mac' or `env'.
+total-num is the total number of the argument before the point started.
+type is one of the symbols `mandatory' or `optional'.
+opt-num is the number of optional arguments before the point started.
+opt-distance the number of optional arguments after the last mandatory.
+
+If the optional BOUND is an integer, limit backward searches to
+this point. If nil, limit to the previous 15 lines."
+ (let ((bound (or bound (line-beginning-position -15)))
+ (env-or-mac 'mac)
+ cmd cnt cnt-opt type result ;; env-or-mac-start
+ (cnt-distance 0))
+ (save-excursion
+ (save-restriction
+ (narrow-to-region (max (point-min) bound) (point-max))
+ ;; Move back out of the current parenthesis
+ (with-syntax-table (apply #'TeX-search-syntax-table
+ (LaTeX-completion-macro-delimiters))
+ (condition-case nil
+ (let ((forward-sexp-function nil))
+ (up-list -1))
+ (error nil))
+ ;; Set the initial value of argument counter
+ (setq cnt 1)
+ ;; Note that we count also the right opt. or man. arg:
+ (setq cnt-opt (if (= (following-char) ?\{) 0 1))
+ ;; Record if we're inside a mand. or opt. argument
+ (setq type (if (= (following-char) ?\{) 'mandatory 'optional))
+ ;; Move back over any touching sexps
+ (while (and (LaTeX-move-to-previous-arg bound)
+ (condition-case nil
+ (let ((forward-sexp-function nil))
+ (backward-sexp) t)
+ (error nil)))
+ (unless (= (following-char) ?\{)
+ (cl-incf cnt-opt))
+ (cl-incf cnt)))
+ ;; (setq env-or-mac-start (point))
+ (when (and (memql (following-char) ;; '(?\[ ?\{ ?\( ?<)
+ (LaTeX-completion-macro-delimiters 'open))
+ (re-search-backward "\\\\[*a-zA-Z]+\\=" nil t))
+ (setq cmd (TeX-match-buffer 0))
+ (when (looking-at "\\\\begin{\\([^}]+\\)}")
+ (setq cmd (TeX-match-buffer 1))
+ (setq env-or-mac 'env)
+ (cl-decf cnt))
+ (when (and cmd (not (string= cmd "")))
+ (setq result (list (if (eq env-or-mac 'mac)
+ ;; Strip leading backslash from
+ ;; the macro
+ (substring cmd 1)
+ cmd)
+ env-or-mac cnt type cnt-opt))))))
+ ;; If we were inside an optional argument after a mandatory one,
+ ;; we have to find out the number of optional arguments before
+ ;; the mandatory one.
+ (when (and (eq (nth 3 result) 'optional)
+ (/= 0 (- (nth 2 result) (nth 4 result))))
+ (save-excursion
+ (save-restriction
+ (narrow-to-region (max (point-min) bound) (point-max))
+ (with-syntax-table (apply #'TeX-search-syntax-table
+ (LaTeX-completion-macro-delimiters))
+ (let ((forward-sexp-function nil))
+ (up-list -1))
+ (unless (= (following-char) ?\{)
+ (cl-incf cnt-distance))
+ (while (and (LaTeX-move-to-previous-arg bound)
+ (condition-case nil
+ (let ((forward-sexp-function nil))
+ (backward-sexp)
+ (/= (following-char) ?\{))
+ (error nil)))
+ (cl-incf cnt-distance))))))
+ ;; Check if we really have a result before adding something new:
+ (when result
+ (append result (list cnt-distance)))))
+
+(defun LaTeX-completion-candidates-key-val (key-vals)
+ "Return completion candidates from KEY-VALS based on buffer position.
+KEY-VALS is an alist of key-values pairs."
+ (let ((end (point))
+ (func (lambda (kv &optional k)
+ (if k
+ (cadr (assoc k kv))
+ kv)))
+ beg key)
+ (save-excursion
+ (re-search-backward "[[{(<,=]" (line-beginning-position 0) t))
+ (if (string= (match-string 0) "=")
+ ;; We have to look for a value:
+ (save-excursion
+ ;; Matching the value is easy, just grab everything before the
+ ;; '=' and ...
+ (re-search-backward "=\\([^=]*\\)" (line-beginning-position) t)
+ ;; ... then move forward over any tabs and spaces:
+ (save-excursion
+ (forward-char)
+ (skip-chars-forward " \t" end)
+ (setq beg (point)))
+ ;; Matching the key is less fun: `re-search-backward'
+ ;; doesn't travel enough, so we have to use
+ ;; `skip-chars-backward' and limit the search to the
+ ;; beginning of the previous line:
+ (skip-chars-backward "^,[{" (line-beginning-position 0))
+ ;; Make sure we're not looking at a comment:
+ (when (looking-at-p (concat "[ \t]*" TeX-comment-start-regexp))
+ (forward-line))
+ ;; Now pick up the key, if available:
+ (setq key (string-trim
+ (buffer-substring-no-properties (point)
+ (match-beginning 0))
+ "[ \t\n\r%]+" "[ \t\n\r%]+"))
+ ;; This caters also for the case where nothing is typed yet:
+ (list beg end (completion-table-dynamic
+ (lambda (_)
+ (funcall func key-vals key)))))
+ ;; We have to look for a key:
+ (save-excursion
+ ;; Find the beginning
+ (skip-chars-backward "^,[{" (line-beginning-position 0))
+ ;; Make sure we're not looking at a comment:
+ (when (looking-at-p (concat "[ \t]*" TeX-comment-start-regexp))
+ (forward-line))
+ ;; Now go until the first char or number which would be the
+ ;; start of the key:
+ (skip-chars-forward "^a-zA-Z0-9" end)
+ (setq beg (point))
+ ;; This caters also for the case where nothing is typed yet:
+ (list beg end (completion-table-dynamic
+ (lambda (_)
+ (funcall func key-vals))))))))
+
+(defun LaTeX-completion-candidates-completing-read-multiple (collection)
+ "Return completion candidates from COLLECTION based on buffer position.
+COLLECTION is an list of strings."
+ (let ((end (point))
+ beg list-beg)
+ (save-excursion
+ (with-syntax-table (apply #'TeX-search-syntax-table
+ (LaTeX-completion-macro-delimiters))
+ (up-list -1))
+ (setq list-beg (1+ (point))))
+ (save-excursion
+ (unless (search-backward "," list-beg t)
+ (goto-char list-beg))
+ (skip-chars-forward "^a-zA-Z0-9" end)
+ (setq beg (point)))
+ (list beg end (completion-table-dynamic
+ (lambda (_)
+ collection)))))
+
+(defun LaTeX-completion-candidates-completing-read (collection)
+ "Return completion candidates from COLLECTION based on buffer position.
+COLLECTION is an list of strings."
+ (let ((end (point))
+ beg)
+ (save-excursion
+ (with-syntax-table (apply #'TeX-search-syntax-table
+ (LaTeX-completion-macro-delimiters))
+ (up-list -1))
+ (forward-char)
+ (skip-chars-forward "^a-zA-Z0-9" end)
+ (setq beg (point)))
+ (list beg end (completion-table-dynamic
+ (lambda (_)
+ collection)))))
+
+(defun LaTeX-completion-parse-args (entry)
+ "Return the match of buffer position ENTRY with AUCTeX macro definitions.
+ENTRY is generated by the function `LaTeX-what-macro'. This
+function matches the current buffer position (i.e., which macro
+argument) with the corresponding definition in `TeX-symbol-list'
+or `LaTeX-environment-list' and returns it."
+ (let* ((name (nth 0 entry))
+ (mac-or-env (nth 1 entry))
+ (total-num (nth 2 entry))
+ (type (nth 3 entry))
+ (opt-num (nth 4 entry))
+ (opt-dis (nth 5 entry))
+ (mand-num (- total-num opt-num))
+ (cnt 0)
+ (again t)
+ arg-list
+ arg
+ result)
+ (setq arg-list (cdr (assoc name (if (eq mac-or-env 'mac)
+ (TeX-symbol-list)
+ (LaTeX-environment-list)))))
+
+ ;; Check if there is a `LaTeX-env-args' in the `arg-list' and
+ ;; remove it:
+ (when (and (eq mac-or-env 'env)
+ (eq (car arg-list) #'LaTeX-env-args))
+ (pop arg-list))
+
+ ;; Check for `TeX-arg-conditional' here and change `arg-list'
+ ;; accordingly
+ (when (assq 'TeX-arg-conditional arg-list)
+ (while (and arg-list
+ (setq arg (car arg-list)))
+ (if (and (listp arg) (eq (car arg) 'TeX-arg-conditional))
+ (setq result (append (reverse (if (eval (nth 1 arg) t)
+ (nth 2 arg)
+ (nth 3 arg)))
+ result))
+ (push arg result))
+ (pop arg-list))
+ (setq arg-list (nreverse result)))
+
+ ;; Now parse the `arg-list':
+ (cond ((and (eq type 'optional)
+ (= opt-dis 0))
+ ;; Optional arg without mandatory one before: This case is
+ ;; straight and we just pick the correct one out of the
+ ;; list:
+ (setq result (nth (1- total-num) arg-list)))
+
+ ;; Mandatory arg: Loop over the arg-list and drop all
+ ;; vectors at the list beginning:
+ ((eq type 'mandatory)
+ (while (vectorp (car arg-list))
+ (pop arg-list))
+ ;; The next entry must be a mandatory arg. If we're
+ ;; looking for the first mandatory argument, just pick the
+ ;; first element. Otherwise loop further over the list and
+ ;; count for the correct arg:
+ (if (= mand-num 1)
+ (setq result (car arg-list))
+ (while again
+ (cond ((vectorp (car arg-list))
+ (pop arg-list)
+ (setq again t))
+ ((= (cl-incf cnt) mand-num)
+ (setq again nil)
+ (setq result (car arg-list)))
+ (t
+ ;; Be a little conservative against infloops.
+ (if arg-list
+ (progn (setq again t)
+ (pop arg-list))
+ (setq again nil)))))))
+
+ ;; Optional arg after mandatory one(s): This isn't fun :-(
+ ((and (eq type 'optional)
+ (/= opt-dis 0))
+ (setq again t)
+ (setq cnt 0)
+ ;; The idea is: Look for non-vectors and count the number
+ ;; of mandatory argument in `mand-num'.
+ (while again
+ (cond ((and (not (vectorp (car arg-list)))
+ (/= (cl-incf cnt) mand-num))
+ (pop arg-list)
+ (setq again t))
+ ((and (not (vectorp (car arg-list)))
+ ;; Don't incf mand-num again; is done in the
+ ;; clause above:
+ (= cnt mand-num))
+ (setq again nil))
+ ;; If the clauses above fail, we can safely drop
+ ;; vectors:
+ ((vectorp (car arg-list))
+ (pop arg-list)
+ (setq again t))
+ (t
+ (setq again nil))))
+ (setq result (nth opt-dis arg-list)))
+ (t nil))
+ result))
+
+(defvar LaTeX-completion-function-map-alist-keyval
+ '((LaTeX-enumitem-env-with-opts . LaTeX-enumitem-key-val-options))
+ "Alist mapping style funcs to completion-candidates counterparts.
+Each element is a cons with the name of the function used in an
+AUCTeX style file which queries and inserts something in the
+buffer as car and the name function delievering completion
+candidates as cdr. This list contains only mapping for functions
+which perform key=val completions. See also
+`LaTeX-completion-function-map-alist-cr'.")
+
+(defvar LaTeX-completion-function-map-alist-cr
+ '((TeX-arg-counter . LaTeX-counter-list)
+ (TeX-arg-pagestyle . LaTeX-pagestyle-list) )
+ "Alist mapping style funcs to completion-candidates counterparts.
+Each element is a cons with the name of the function used in an
+AUCTeX style file which queries and inserts something in the
+buffer as car and the name function delievering completion
+candidates as cdr. This list contains only mapping for functions
+which perform completing-read. See also
+`LaTeX-completion-function-map-alist-keyval'.")
+
+(defun LaTeX-completion-parse-arg (arg)
+ "Parse ARG and call the correct candidates completion function.
+ARG is the entry for the current argument in buffer stored in
+`TeX-symbol-list' or `LaTeX-environment-list'."
+ (when (or (and (vectorp arg)
+ (symbolp (elt arg 0))
+ (fboundp (elt arg 0)))
+ (and (listp arg)
+ (symbolp (car arg))
+ (fboundp (car arg)))
+ (and (symbolp arg)
+ (fboundp arg)))
+ ;; Turn a vector into a list:
+ (when (vectorp arg)
+ (setq arg (append arg nil)))
+ ;; Turn a single function symbol into a list:
+ (unless (listp arg)
+ (setq arg (list arg)))
+ (let* ((head (car arg))
+ (tail (cadr arg))
+ (fun1 (lambda (elt)
+ (cond ((and (listp elt)
+ (symbolp (car elt))
+ (fboundp (car elt)))
+ ;; It is a function call:
+ (funcall (car elt)))
+ ;; It is a function object
+ ((functionp elt)
+ (funcall elt))
+ ;; It is a variable name
+ ((and (symbolp elt)
+ (boundp elt))
+ (symbol-value elt))
+ ;; It is a plain list of strings:
+ (t elt)))))
+ (cond ((eq head #'TeX-arg-key-val)
+ (LaTeX-completion-candidates-key-val
+ (funcall fun1 tail)))
+
+ ((eq head #'TeX-arg-completing-read-multiple)
+ (LaTeX-completion-candidates-completing-read-multiple
+ (funcall fun1 tail)))
+
+ ((eq head #'TeX-arg-completing-read)
+ (LaTeX-completion-candidates-completing-read
+ (funcall fun1 tail)))
+
+ ((assq head LaTeX-completion-function-map-alist-keyval)
+ (LaTeX-completion-candidates-key-val
+ (funcall fun1 (cdr (assq head
LaTeX-completion-function-map-alist-keyval)))))
+
+ ((assq head LaTeX-completion-function-map-alist-cr)
+ (LaTeX-completion-candidates-completing-read
+ (funcall fun1 (cdr (assq head
LaTeX-completion-function-map-alist-cr)))))
+
+ (t nil)))))
+
+(defun LaTeX-completion-find-argument-boundries (&rest args)
+ "Find the boundries of the current LaTeX argument.
+ARGS are characters passed to the function
+`TeX-search-syntax-table'. If ARGS are omitted, all characters
+defined in the variable `LaTeX-completion-macro-delimiters' are
+taken."
+ (save-restriction
+ (narrow-to-region (line-beginning-position -20)
+ (line-beginning-position 20))
+ (let ((args (or args (LaTeX-completion-macro-delimiters))))
+ (condition-case nil
+ (with-syntax-table (apply #'TeX-search-syntax-table args)
+ (scan-lists (point) 1 1))
+ (error nil)))))
+
+(defun LaTeX--arguments-completion-at-point ()
+ "Capf for arguments of LaTeX macros and environments.
+Completion for macros starting with `\\' is provided by the
+function `TeX--completion-at-point' which should come first in
+`completion-at-point-functions'."
+ ;; Exit if not inside an argument or in a comment:
+ (when (and (LaTeX-completion-find-argument-boundries)
+ (not (nth 4 (syntax-ppss))))
+ (let ((entry (LaTeX-what-macro)))
+ (cond ((or (and entry
+ (eq (nth 1 entry) 'mac)
+ (assoc (car entry) (TeX-symbol-list)))
+ (and entry
+ (eq (nth 1 entry) 'env)
+ (assoc (car entry) (LaTeX-environment-list))))
+ (LaTeX-completion-parse-arg
+ (LaTeX-completion-parse-args entry)))
+ ;; Any other constructs?
+ (t nil)))))
+
;;; Mode
(defgroup LaTeX-macro nil
@@ -7419,6 +7884,11 @@ function would return non-nil and `(match-string 1)'
would return
(LaTeX-indent-commands-regexp-make)
+ ;; Standard Emacs completion-at-point support. We append the entry
+ ;; in order to let `TeX--completion-at-point' be first in the list:
+ (add-hook 'completion-at-point-functions
+ #'LaTeX--arguments-completion-at-point t t)
+
(set (make-local-variable 'LaTeX-item-list) '(("description" .
LaTeX-item-argument)
("thebibliography" .
LaTeX-item-bib)
("array" . LaTeX-item-array)
- [elpa] externals/auctex c8d638ac9d 40/48: Use `functionp' to recognize a function, (continued)
- [elpa] externals/auctex c8d638ac9d 40/48: Use `functionp' to recognize a function, Tassilo Horn, 2022/11/18
- [elpa] externals/auctex 835e01ddb6 36/48: Extend the argument list of `TeX-arg-string', Tassilo Horn, 2022/11/18
- [elpa] externals/auctex e83a4b87b3 25/48: Use the correct function to retrieve the key=vals, Tassilo Horn, 2022/11/18
- [elpa] externals/auctex a617944b4b 08/48: Fix style/xspace.el, Tassilo Horn, 2022/11/18
- [elpa] externals/auctex 50c0fb3d31 46/48: * style/afterpage.el ("afterpage"): Fontify the only macro., Tassilo Horn, 2022/11/18
- [elpa] externals/auctex 82a23a93ae 43/48: Add some variables containing font declarations, Tassilo Horn, 2022/11/18
- [elpa] externals/auctex 93430d7ab0 48/48: Merge remote-tracking branch 'origin/master' into externals/auctex, Tassilo Horn, 2022/11/18
- [elpa] externals/auctex 7642155b73 03/48: Don't let-bind `inhibit-point-motion-hooks' to t, Tassilo Horn, 2022/11/18
- [elpa] externals/auctex 6cbeff713b 47/48: Simplify implementation of style/fancyhdr.el, Tassilo Horn, 2022/11/18
- [elpa] externals/auctex 3d8d93e4fd 41/48: ; Make code more resilient, Tassilo Horn, 2022/11/18
- [elpa] externals/auctex 35ec84ba6f 07/48: Add capf for LaTeX marco/environment arguments,
Tassilo Horn <=