;;; Completion at point for Elisp
-(defun elisp--local-variables-1 (vars sexp)
- "Return VARS locally bound around the witness, or nil if not found."
- (let (res)
- (while
- (unless
- (setq res
- (pcase sexp
- (`(,(or 'let 'let*) ,bindings)
- (let ((vars vars))
- (when (eq 'let* (car sexp))
- (dolist (binding (cdr (reverse bindings)))
- (push (or (car-safe binding) binding) vars)))
- (elisp--local-variables-1
- vars (car (cdr-safe (car (last bindings)))))))
- (`(,(or 'let 'let*) ,bindings . ,body)
- (let ((vars vars))
- (dolist (binding bindings)
- (push (or (car-safe binding) binding) vars))
- (elisp--local-variables-1 vars (car (last body)))))
- (`(lambda ,_args)
- ;; FIXME: Look for the witness inside `args'.
- (setq sexp nil))
- (`(lambda ,args . ,body)
- (elisp--local-variables-1
- (let ((args (if (listp args) args)))
- ;; FIXME: Exit the loop if witness is in args.
- (append (remq '&optional (remq '&rest args)) vars))
- (car (last body))))
- (`(condition-case ,_ ,e) (elisp--local-variables-1 vars e))
- (`(condition-case ,v ,_ . ,catches)
- (elisp--local-variables-1
- (cons v vars) (cdr (car (last catches)))))
- (`(quote . ,_)
- ;; FIXME: Look for the witness inside sexp.
- (setq sexp nil))
- ;; FIXME: Handle `cond'.
- (`(,_ . ,_)
- (elisp--local-variables-1 vars (car (last sexp))))
- ('elisp--witness--lisp (or vars '(nil)))
- (_ nil)))
- ;; We didn't find the witness in the last element so we try to
- ;; backtrack to the last-but-one.
- (setq sexp (ignore-errors (butlast sexp)))))
- res))
-
-(defvar warning-minimum-log-level)
-
-(defvar elisp--local-macroenv
- `((cl-eval-when . ,(lambda (&rest args) `(progn . ,(cdr args))))
- (eval-when-compile . ,(lambda (&rest args) `(progn . ,args)))
- (eval-and-compile . ,(lambda (&rest args) `(progn . ,args))))
- "Environment to use while tentatively expanding macros.
-This is used to try and avoid the most egregious problems linked to the
-use of `macroexpand-all' as a way to find the \"underlying raw code\".")
+(defvar scope--local)
-(defvar elisp--macroexpand-untrusted-warning t)
-
-(defun elisp--safe-macroexpand-all (sexp)
- (if (not (trusted-content-p))
- ;; FIXME: We should try and do better here, either using a notion
- ;; of "safe" macros, or with `bwrap', or ...
- (progn
- (when elisp--macroexpand-untrusted-warning
- (setq-local elisp--macroexpand-untrusted-warning nil) ;Don't spam!
- (let ((inhibit-message t)) ;Only log.
- (message "Completion of local vars is disabled in %s (untrusted content)"
- (buffer-name))))
- sexp)
- (let ((macroexpand-advice
- (lambda (expander form &rest args)
- (condition-case err
- (apply expander form args)
- (error
- (message "Ignoring macroexpansion error: %S" err) form)))))
- (unwind-protect
- ;; Silence any macro expansion errors when
- ;; attempting completion at point (bug#58148).
- (let ((inhibit-message t)
- (macroexp-inhibit-compiler-macros t)
- (warning-minimum-log-level :emergency))
- (advice-add 'macroexpand-1 :around macroexpand-advice)
- (macroexpand-all sexp elisp--local-macroenv))
- (advice-remove 'macroexpand-1 macroexpand-advice)))))
-
-(defun elisp--local-variables ()
- "Return a list of locally let-bound variables at point."
- (save-excursion
- (skip-syntax-backward "w_")
- (let* ((ppss (syntax-ppss))
- (txt (buffer-substring-no-properties (or (car (nth 9 ppss)) (point))
- (or (nth 8 ppss) (point))))
- (closer ()))
- (dolist (p (nth 9 ppss))
- (push (cdr (syntax-after p)) closer))
- (setq closer (apply #'string closer))
- (let* ((sexp (condition-case nil
- (car (read-from-string
- (concat txt "elisp--witness--lisp" closer)))
- ((invalid-read-syntax end-of-file) nil)))
- (vars (elisp--local-variables-1
- nil (elisp--safe-macroexpand-all sexp))))
- (delq nil
- (mapcar (lambda (var)
- (and (symbolp var)
- (not (string-match (symbol-name var) "\\`[&_]"))
- ;; Eliminate uninterned vars.
- (intern-soft var)
- var))
- vars))))))
-
-(defun elisp--expect-function-p (pos)
- "Return non-nil if the symbol at position POS is expected to be a function."
- (or
- (and (eq (char-before pos) ?')
- (eq (char-before (1- pos)) ?#))
- (save-excursion
- (let ((parent (nth 1 (syntax-ppss pos))))
- (when parent
- (goto-char parent)
- (and
- (looking-at (concat "(\\(cl-\\)?"
- (regexp-opt '("declare-function"
- "function" "defadvice"
- "callf" "callf2"
- "defsetf"))
- "[ \t\r\n]+"))
- (eq (match-end 0) pos)))))))
-
-(defun elisp--form-quoted-p (pos)
- "Return non-nil if the form at POS is not evaluated.
-It can be quoted, or be inside a quoted form."
- ;; FIXME: Do some macro expansion maybe.
- (save-excursion
- (let ((state (syntax-ppss pos)))
- (or (nth 8 state) ; Code inside strings usually isn't evaluated.
- (let ((nesting (cons (point) (reverse (nth 9 state))))
- res)
- (while (and nesting (not res))
- (goto-char (pop nesting))
- (cond
- ((or (eq (char-after) ?\[)
- (progn
- (skip-chars-backward " ")
- (memq (char-before) '(?' ?` ?‘))))
- (setq res t))
- ((eq (char-before) ?,)
- (setq nesting nil))))
- res)))))
+(defun elisp-local-variables (&optional pos)
+ "Return list of local variable names in scope at POS."
+ (let (all res)
+ (save-excursion
+ (if pos (goto-char pos) (setq pos (point)))
+ (beginning-of-defun)
+ (scope (lambda (_type beg len _id &optional _def)
+ (when (<= beg pos (+ beg len))
+ (setq all (nconc (mapcar #'car scope--local) all))))))
+ (dolist (sym all)
+ (let* ((var sym)) (unless (memq var res) (setq res (cons var res)))))
+ res))
;; FIXME: Support for Company brings in features which straddle eldoc.
;; We should consolidate this, so that major modes can provide all that
(cond
((fboundp symbol) (describe-function symbol))
((boundp symbol) (describe-variable symbol))
- ((featurep symbol) (describe-package symbol))
+ ((featurep symbol) (describe-library symbol))
((facep symbol) (describe-face symbol))
(t (signal 'user-error nil)))
(help-buffer))))))
(when (<= beg pos (+ beg len))
(throw 'sym-type type))))
nil))
- ((variable constant) (let ((local-vars (elisp--local-variables)))
+ ((variable constant) (let ((local-vars (elisp-local-variables)))
(lambda (sym) (or (elisp--shorthand-aware-boundp sym)
(memq sym local-vars)))))
((function macro special-form top-level) #'elisp--shorthand-aware-fboundp)
(substring file 3))
file)))
-(defun elisp-load-path-roots ()
- (if (boundp 'package-user-dir)
- (cons package-user-dir load-path)
- load-path))
+(defun elisp-load-path-roots () load-path)
;;; Elisp Interaction mode