branch: feature/capf commit 83eddfa38ef073d78b6808cf4e79f50fa970ffaf Author: Arash Esbati <ar...@gnu.org> Commit: Arash Esbati <ar...@gnu.org>
latex-capf.el: Make it work --- latex-capf.el | 313 +++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 221 insertions(+), 92 deletions(-) diff --git a/latex-capf.el b/latex-capf.el index 9c8af3b8..aca92127 100644 --- a/latex-capf.el +++ b/latex-capf.el @@ -22,58 +22,81 @@ several lines in TeX." "Find out if point is within the arguments of any TeX-macro. The return value is - (\"name\" mac-or-env integer type) + (\"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'. -integer is the number of the argument where we started. +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, bound backwards directed -searches to this point. If it is nil, limit to the previous 15 -lines." - ;; Exit if we're not inside a list or inside a nested list: - (unless (/= (car (syntax-ppss)) 1) - (let ((bound (or bound (line-beginning-position -15))) - (env-or-mac 'mac) - cmd cnt cnt-opt type) +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 + (let ((forward-sexp-function nil)) + (up-list -1)) + ;; 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 (TeX-move-to-previous-arg bound) + (condition-case nil + (let ((forward-sexp-function nil)) + (backward-sexp) t) + (error nil))) + (when (eq (following-char) ?\[) + (cl-incf cnt-opt)) + (cl-incf cnt)) + ;; (setq env-or-mac-start (point)) + (when (and (or (= (following-char) ?\[) + (= (following-char) ?\{)) + (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)) - ;; Move back out of the current parenthesis (let ((forward-sexp-function nil)) (up-list -1)) - ;; 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 + (unless (= (following-char) ?\{) + (cl-incf cnt-distance)) (while (and (TeX-move-to-previous-arg bound) (condition-case nil (let ((forward-sexp-function nil)) - (backward-sexp) t) + (backward-sexp) + (/= (following-char) ?\{)) (error nil))) - (if (eq (following-char) ?\[) (cl-incf cnt-opt)) - (cl-incf cnt)) - (when (and (or (= (following-char) ?\[) - (= (following-char) ?\{)) - (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)) - (if (and cmd (not (string= cmd ""))) - (list (if (eq env-or-mac 'mac) - ;; Strip leading backslash from - ;; the macro - (substring cmd 1) - cmd) - env-or-mac cnt type) - 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) ;; First find out if we're looking for a key or a value: If we're @@ -130,74 +153,180 @@ lines." (lambda (_) (funcall func key-vals)))))))) +(defun LaTeX-completion-candidates-completing-read-multiple (collection) + (let ((end (point)) + beg list-beg) + (save-excursion + (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) (let ((end (point)) - beg key) )) + beg) + (save-excursion + (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-candidates-completing-read-multiple (table) - nil) +(defun LaTeX-completion-parse-args (entry) + (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 result) + (setq arg-list (cdr (assoc name (if (eq mac-or-env 'mac) + (TeX-symbol-list) + (LaTeX-environment-list))))) + ;; FIXME: Check for `TeX-arg-conditional' here and change + ;; `arg-list' accordingly + ;; + ;; 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)) + (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)) (defun LaTeX-completion-parse-arg (arg) "Prepare ARG suitable for further completion processing." - (when (and (or (vectorp arg) - (listp arg)) - (symbolp (elt arg 0)) - (fboundp (elt arg 0))) + (when (or (and (vectorp arg) + (fboundp (elt arg 0))) + (and (listp arg) + (fboundp (car arg))) + (and (symbolp arg) + (fboundp arg))) ;; Turn a vector into a list: (when (vectorp arg) (setq arg (append arg nil))) - (let ((head (car arg)) - (tail (cadr arg))) + ;; 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 ((symbolp elt) + ;; It is a variable name + (symbol-value elt)) + ;; It is a function call ... + ((and (listp elt) + (symbolp (car elt)) + (fboundp (car elt))) + ;; ... w/ or w/o args: + (if (= (length elt) 1) + (funcall (car elt)) + (eval elt t))) + ;; It is a plain list of strings: + (t elt))))) (cond ((eq head #'TeX-arg-key-val) (LaTeX-completion-candidates-key-val - (cond ((symbolp tail) - ;; It is a variable name - (symbol-value tail)) - ;; It is a function call ... - ((and (listp tail) - (symbolp (car tail)) - (fboundp (car tail))) - ;; ... w/ or w/o args: - (if (= (length tail) 1) - (funcall (car tail)) - (eval tail t))) - ;; It is a plain list of strings: - (t tail)))) - ) ))) + (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 (car arg) LaTeX-completion-function-map-alist) + (funcall (cdr (assq (car arg) LaTeX-completion-function-map-alist)))) + (t nil))))) + +(defvar LaTeX-completion-function-map-alist + '((TeX-arg-counter . LaTeX-counter-list) + (TeX-arg-pagestyle . LaTeX-pagestyle-list) + ;; ... + ) + "Alist mapping inserting funcs to their completion-candidates counterparts. +Each element is a cons with the name of the function which +queries and inserts something in the buffer as car and the name +function delievering completion candidates as cdr.") (defun LaTeX-arguments-completion-at-point () ;; Exit if not inside a list or a nested list or in a comment: - (unless (or (/= (car (syntax-ppss)) 1) - (TeX-in-comment)) - (let ((entry (LaTeX-what-macro)) - mac env arg) - (cond ((eq (nth 1 entry) 'mac) - ;; Feed 'arg' to `LaTeX-completion-parse-arg' only when - ;; we find one: - (and (setq mac (assoc (car entry) (TeX-symbol-list))) - (setq arg (nth (nth 2 entry) mac)) - (LaTeX-completion-parse-arg arg))) - ((eq (nth 1 entry) 'env) - ;; Feed 'arg' to `LaTeX-completion-parse-arg' only when - ;; we find one: - (and (setq env (assoc (car entry) (LaTeX-environment-list))) - ;; Shift the number of arg if `LaTeX-env-args' is present: - (setq arg (if (eq (cadr env) 'LaTeX-env-args) - (nth (1+ (nth 2 entry)) env) - (nth (nth 2 entry) env))) - (LaTeX-completion-parse-arg arg))) + (when (or (/= (car (syntax-ppss)) 0) + (not (TeX-in-comment))) + (let ((entry (LaTeX-what-macro))) + (cond ((or (and (eq (nth 1 entry) 'mac) + (assoc (car entry) (TeX-symbol-list))) + (and (eq (nth 1 entry) 'env) + (assoc (car entry) (LaTeX-environment-list)))) + ;; Check if we find the macro name inside + ;; `TeX-symbol-list' or the env name inside + ;; `LaTeX-environment-list' otherwise don't start the + ;; procedure: + (LaTeX-completion-parse-arg + (LaTeX-completion-parse-args entry))) ;; Any other constructs? (t nil))))) - -;; (defun LaTeX-completion-candidates-single (func) - -;; (let ((end (point)) -;; beg) -;; (cond ((eq func #'TeX-arg-pagestyle) -;; (save-excursion -;; (skip-chars-backward "a-zA-Z0-9") -;; (setq beg (point))) -;; (list beg end (completion-table-dynamic -;; (lambda (_) (LaTeX-pagestyle-list))))) -;; (t nil)) -;; ))