auctex-diffs
[Top][All Lists]
Advanced

[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)




reply via email to

[Prev in Thread] Current Thread [Next in Thread]