summaryrefslogtreecommitdiff
path: root/lisp/emacs-lisp/lisp.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/emacs-lisp/lisp.el')
-rw-r--r--lisp/emacs-lisp/lisp.el192
1 files changed, 157 insertions, 35 deletions
diff --git a/lisp/emacs-lisp/lisp.el b/lisp/emacs-lisp/lisp.el
index 22fb6ad1809..b37a811b8d5 100644
--- a/lisp/emacs-lisp/lisp.el
+++ b/lisp/emacs-lisp/lisp.el
@@ -1,4 +1,4 @@
-;;; lisp.el --- Lisp editing commands for Emacs
+;;; lisp.el --- Lisp editing commands for Emacs -*- lexical-binding:t -*-
;; Copyright (C) 1985-1986, 1994, 2000-2013 Free Software Foundation,
;; Inc.
@@ -46,6 +46,12 @@ This affects `insert-parentheses' and `insert-pair'."
:group 'lisp)
(defvar forward-sexp-function nil
+ ;; FIXME:
+ ;; - for some uses, we may want a "sexp-only" version, which only
+ ;; jumps over a well-formed sexp, rather than some dwimish thing
+ ;; like jumping from an "else" back up to its "if".
+ ;; - for up-list, we could use the "sexp-only" behavior as well
+ ;; to treat the dwimish halfsexp as a form of "up-list" step.
"If non-nil, `forward-sexp' delegates to this function.
Should take the same arguments and behave similarly to `forward-sexp'.")
@@ -53,7 +59,8 @@ Should take the same arguments and behave similarly to `forward-sexp'.")
"Move forward across one balanced expression (sexp).
With ARG, do it that many times. Negative arg -N means
move backward across N balanced expressions.
-This command assumes point is not in a string or comment."
+This command assumes point is not in a string or comment.
+Calls `forward-sexp-function' to do the work, if that is non-nil."
(interactive "^p")
(or arg (setq arg 1))
(if forward-sexp-function
@@ -65,7 +72,8 @@ This command assumes point is not in a string or comment."
"Move backward across one balanced expression (sexp).
With ARG, do it that many times. Negative arg -N means
move forward across N balanced expressions.
-This command assumes point is not in a string or comment."
+This command assumes point is not in a string or comment.
+Uses `forward-sexp' to do the work."
(interactive "^p")
(or arg (setq arg 1))
(forward-sexp (- arg)))
@@ -256,9 +264,9 @@ is called as a function to find the defun's beginning."
;; convention, fallback on the old implementation.
(wrong-number-of-arguments
(if (> arg 0)
- (dotimes (i arg)
+ (dotimes (_ arg)
(funcall beginning-of-defun-function))
- (dotimes (i (- arg))
+ (dotimes (_ (- arg))
(funcall end-of-defun-function))))))
((or defun-prompt-regexp open-paren-in-column-0-is-defun-start)
@@ -436,7 +444,7 @@ it marks the next defun after the ones already marked."
(beginning-of-defun))
(re-search-backward "^\n" (- (point) 1) t)))))
-(defun narrow-to-defun (&optional arg)
+(defun narrow-to-defun (&optional _arg)
"Make text outside current defun invisible.
The defun visible is the one that contains point or follows point.
Optional ARG is ignored."
@@ -618,9 +626,10 @@ character."
;; "Unbalanced parentheses", but those may not be so
;; accurate/helpful, e.g. quotes may actually be
;; mismatched.
- (error "Unmatched bracket or quote"))))
+ (user-error "Unmatched bracket or quote"))))
(defun field-complete (table &optional predicate)
+ (declare (obsolete completion-in-region "24.4"))
(let ((minibuffer-completion-table table)
(minibuffer-completion-predicate predicate)
;; This made sense for lisp-complete-symbol, but for
@@ -645,6 +654,7 @@ considered. If the symbol starts just after an open-parenthesis, only
symbols with function definitions are considered. Otherwise, all
symbols with function definitions, values or properties are
considered."
+ (declare (obsolete completion-at-point "24.4"))
(interactive)
(let* ((data (lisp-completion-at-point predicate))
(plist (nthcdr 3 data)))
@@ -654,10 +664,96 @@ considered."
(completion-in-region (nth 0 data) (nth 1 data) (nth 2 data)
(plist-get plist :predicate))))))
-
-(defun lisp-completion-at-point (&optional predicate)
+(defun lisp--local-variables-1 (vars sexp)
+ "Return the 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)))
+ (lisp--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))
+ (lisp--local-variables-1 vars (car (last body)))))
+ (`(lambda ,_) (setq sexp nil))
+ (`(lambda ,args . ,body)
+ (lisp--local-variables-1
+ (append args vars) (car (last body))))
+ (`(condition-case ,_ ,e) (lisp--local-variables-1 vars e))
+ (`(condition-case ,v ,_ . ,catches)
+ (lisp--local-variables-1
+ (cons v vars) (cdr (car (last catches)))))
+ (`(,_ . ,_)
+ (lisp--local-variables-1 vars (car (last sexp))))
+ (`lisp--witness--lisp (or vars '(nil)))
+ (_ nil)))
+ (setq sexp (ignore-errors (butlast sexp)))))
+ res))
+
+(defun lisp--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 (car (read-from-string
+ (concat txt "lisp--witness--lisp" closer))))
+ (macroexpand-advice (lambda (expander form &rest args)
+ (condition-case nil
+ (apply expander form args)
+ (error form))))
+ (sexp
+ (unwind-protect
+ (progn
+ (advice-add 'macroexpand :around macroexpand-advice)
+ (macroexpand-all sexp))
+ (advice-remove 'macroexpand macroexpand-advice)))
+ (vars (lisp--local-variables-1 nil sexp)))
+ (delq nil
+ (mapcar (lambda (var)
+ (and (symbolp var)
+ (not (string-match (symbol-name var) "\\`[&_]"))
+ ;; Eliminate uninterned vars.
+ (intern-soft var)
+ var))
+ vars))))))
+
+(defvar lisp--local-variables-completion-table
+ ;; Use `defvar' rather than `defconst' since defconst would purecopy this
+ ;; value, which would doubly fail: it would fail because purecopy can't
+ ;; handle the recursive bytecode object, and it would fail because it would
+ ;; move `lastpos' and `lastvars' to pure space where they'd be immutable!
+ (let ((lastpos nil) (lastvars nil))
+ (letrec ((hookfun (lambda ()
+ (setq lastpos nil)
+ (remove-hook 'post-command-hook hookfun))))
+ (completion-table-dynamic
+ (lambda (_string)
+ (save-excursion
+ (skip-syntax-backward "_w")
+ (let ((newpos (cons (point) (current-buffer))))
+ (unless (equal lastpos newpos)
+ (add-hook 'post-command-hook hookfun)
+ (setq lastpos newpos)
+ (setq lastvars
+ (mapcar #'symbol-name (lisp--local-variables))))))
+ lastvars)))))
+
+(defun lisp-completion-at-point (&optional _predicate)
"Function used for `completion-at-point-functions' in `emacs-lisp-mode'."
- ;; FIXME: the `end' could be after point?
(with-syntax-table emacs-lisp-mode-syntax-table
(let* ((pos (point))
(beg (condition-case nil
@@ -666,25 +762,6 @@ considered."
(skip-syntax-forward "'")
(point))
(scan-error pos)))
- (predicate
- (or predicate
- (save-excursion
- (goto-char beg)
- (if (not (eq (char-before) ?\())
- (lambda (sym) ;why not just nil ? -sm
- (or (boundp sym) (fboundp sym)
- (symbol-plist sym)))
- ;; Looks like a funcall position. Let's double check.
- (if (condition-case nil
- (progn (up-list -2) (forward-char 1)
- (eq (char-after) ?\())
- (error nil))
- ;; If the first element of the parent list is an open
- ;; paren we are probably not in a funcall position.
- ;; Maybe a `let' varlist or something.
- nil
- ;; Else, we assume that a function name is expected.
- 'fboundp)))))
(end
(unless (or (eq beg (point-max))
(member (char-syntax (char-after beg)) '(?\" ?\( ?\))))
@@ -694,12 +771,57 @@ considered."
(forward-sexp 1)
(when (>= (point) pos)
(point)))
- (scan-error pos)))))
+ (scan-error pos))))
+ (funpos (eq (char-before beg) ?\()) ;t if in function position.
+ (table-etc
+ (if (not funpos)
+ ;; FIXME: We could look at the first element of the list and
+ ;; use it to provide a more specific completion table in some
+ ;; cases. E.g. filter out keywords that are not understood by
+ ;; the macro/function being called.
+ (list nil (completion-table-in-turn
+ lisp--local-variables-completion-table
+ obarray) ;Could be anything.
+ :annotation-function
+ (lambda (str) (if (fboundp (intern-soft str)) " <f>")))
+ ;; Looks like a funcall position. Let's double check.
+ (save-excursion
+ (goto-char (1- beg))
+ (let ((parent
+ (condition-case nil
+ (progn (up-list -1) (forward-char 1)
+ (let ((c (char-after)))
+ (if (eq c ?\() ?\(
+ (if (memq (char-syntax c) '(?w ?_))
+ (read (current-buffer))))))
+ (error nil))))
+ (pcase parent
+ ;; FIXME: Rather than hardcode special cases here,
+ ;; we should use something like a symbol-property.
+ (`declare
+ (list t (mapcar (lambda (x) (symbol-name (car x)))
+ (delete-dups
+ (append
+ macro-declarations-alist
+ defun-declarations-alist)))))
+ ((and (or `condition-case `condition-case-unless-debug)
+ (guard (save-excursion
+ (ignore-errors
+ (forward-sexp 2)
+ (< (point) beg)))))
+ (list t obarray
+ :predicate (lambda (sym) (get sym 'error-conditions))))
+ (_ (list nil obarray #'fboundp))))))))
(when end
- (list beg end obarray
- :predicate predicate
- :annotation-function
- (unless (eq predicate 'fboundp)
- (lambda (str) (if (fboundp (intern-soft str)) " <f>"))))))))
+ (let ((tail (if (null (car table-etc))
+ (cdr table-etc)
+ (cons
+ (if (memq (char-syntax (or (char-after end) ?\s))
+ '(?\s ?>))
+ (cadr table-etc)
+ (apply-partially 'completion-table-with-terminator
+ " " (cadr table-etc)))
+ (cddr table-etc)))))
+ `(,beg ,end ,@tail))))))
;;; lisp.el ends here