summaryrefslogtreecommitdiff
path: root/lisp/progmodes/elisp-mode.el
diff options
context:
space:
mode:
authorYuuki Harano <masm+github@masm11.me>2021-11-11 00:39:53 +0900
committerYuuki Harano <masm+github@masm11.me>2021-11-11 00:39:53 +0900
commit4dd1f56f29fc598a8339a345c2f8945250600602 (patch)
treeaf341efedffe027e533b1bcc0dbf270532e48285 /lisp/progmodes/elisp-mode.el
parent4c49ec7f865bdad1629d2f125f71f4e506b258f2 (diff)
parent810fa21d26453f898de9747ece7205dfe6de9d08 (diff)
downloademacs-4dd1f56f29fc598a8339a345c2f8945250600602.tar.gz
emacs-4dd1f56f29fc598a8339a345c2f8945250600602.tar.bz2
emacs-4dd1f56f29fc598a8339a345c2f8945250600602.zip
Merge branch 'master' of git.savannah.gnu.org:/srv/git/emacs into feature/pgtk
Diffstat (limited to 'lisp/progmodes/elisp-mode.el')
-rw-r--r--lisp/progmodes/elisp-mode.el396
1 files changed, 336 insertions, 60 deletions
diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el
index 7ed2d3d08cc..7da93a351a2 100644
--- a/lisp/progmodes/elisp-mode.el
+++ b/lisp/progmodes/elisp-mode.el
@@ -153,6 +153,41 @@ All commands in `lisp-mode-shared-map' are inherited by this map.")
:style toggle
:selected (bound-and-true-p eldoc-mode)]))
+(defun elisp-context-menu (menu click)
+ "Populate MENU with symbol help commands at CLICK."
+ (when (thing-at-mouse click 'symbol)
+ (define-key-after menu [elisp-separator] menu-bar-separator
+ 'middle-separator)
+
+ (let* ((string (thing-at-mouse click 'symbol t))
+ (symbol (when (stringp string) (intern string)))
+ (title (cond
+ ((not (symbolp symbol)) nil)
+ ((and (facep symbol) (not (fboundp symbol)))
+ "Face")
+ ((and (fboundp symbol)
+ (not (or (boundp symbol) (facep symbol))))
+ "Function")
+ ((and (boundp symbol)
+ (not (or (fboundp symbol) (facep symbol))))
+ "Variable")
+ ((or (fboundp symbol) (boundp symbol) (facep symbol))
+ "Symbol"))))
+ (when title
+ (define-key-after menu [info-lookup-symbol]
+ `(menu-item "Look up in Manual"
+ (lambda (_click) (interactive "e")
+ (info-lookup-symbol ',symbol))
+ :help ,(format "Find `%s' in relevant manual" symbol))
+ 'elisp-separator)
+ (define-key-after menu [describe-symbol]
+ `(menu-item (format "Describe %s" ,title)
+ (lambda (_click) (interactive "e")
+ (describe-symbol ',symbol))
+ :help ,(format "Display the documentation of `%s'" symbol))
+ 'elisp-separator))))
+ menu)
+
(defun emacs-lisp-byte-compile ()
"Byte compile the file containing the current buffer."
(interactive nil emacs-lisp-mode)
@@ -175,7 +210,7 @@ All commands in `lisp-mode-shared-map' are inherited by this map.")
(emacs-lisp--before-compile-buffer)
(require 'bytecomp)
(byte-recompile-file buffer-file-name nil 0)
- (load buffer-file-name))
+ (load (byte-compile-dest-file buffer-file-name)))
(declare-function native-compile "comp")
(defun emacs-lisp-native-compile-and-load ()
@@ -183,7 +218,7 @@ All commands in `lisp-mode-shared-map' are inherited by this map.")
Load the compiled code when finished.
Use `emacs-lisp-byte-compile-and-load' in combination with
-`native-comp-deferred-compilation' set to `t' to achieve asynchronous
+`native-comp-deferred-compilation' set to t to achieve asynchronous
native compilation."
(interactive nil emacs-lisp-mode)
(emacs-lisp--before-compile-buffer)
@@ -256,6 +291,9 @@ Commands:
Delete converts tabs to spaces as it moves back.
Blank lines separate paragraphs. Semicolons start comments.
+When editing Lisp data (as opposed to code), `lisp-data-mode' can
+be used instead.
+
\\{emacs-lisp-mode-map}"
:group 'lisp
(defvar project-vc-external-roots-function)
@@ -280,7 +318,8 @@ Blank lines separate paragraphs. Semicolons start comments.
#'elisp-completion-at-point nil 'local)
(add-hook 'flymake-diagnostic-functions #'elisp-flymake-checkdoc nil t)
(add-hook 'flymake-diagnostic-functions
- #'elisp-flymake-byte-compile nil t))
+ #'elisp-flymake-byte-compile nil t)
+ (add-hook 'context-menu-functions #'elisp-context-menu 10 t))
;; Font-locking support.
@@ -493,6 +532,53 @@ It can be quoted, or be inside a quoted form."
0))
((facep sym) (find-definition-noselect sym 'defface)))))
+(defvar obarray-cache nil
+ "If non-nil, a hash table of cached obarray-related information.
+The cache holds information specific to the current state of the
+Elisp obarray. If the obarray is modified by any means (such as
+interning or uninterning a symbol), this variable is set to nil.")
+
+(defun elisp--completion-local-symbols ()
+ "Compute collections of all Elisp symbols for completion purposes.
+The return value is compatible with the COLLECTION form described
+in `completion-at-point-functions' (which see)."
+ (cl-flet ((obarray-plus-shorthands ()
+ (let (retval)
+ (mapatoms
+ (lambda (s)
+ (push s retval)
+ (cl-loop
+ for (shorthand . longhand) in read-symbol-shorthands
+ for full-name = (symbol-name s)
+ when (string-prefix-p longhand full-name)
+ do (let ((sym (make-symbol
+ (concat shorthand
+ (substring full-name
+ (length longhand))))))
+ (put sym 'shorthand t)
+ (push sym retval)
+ retval))))
+ retval)))
+ (cond ((null read-symbol-shorthands) obarray)
+ ((and obarray-cache
+ (gethash (cons (current-buffer) read-symbol-shorthands)
+ obarray-cache)))
+ (obarray-cache
+ (puthash (cons (current-buffer) read-symbol-shorthands)
+ (obarray-plus-shorthands)
+ obarray-cache))
+ (t
+ (setq obarray-cache (make-hash-table :test #'equal))
+ (puthash (cons (current-buffer) read-symbol-shorthands)
+ (obarray-plus-shorthands)
+ obarray-cache)))))
+
+(defun elisp--shorthand-aware-fboundp (sym)
+ (fboundp (intern-soft (symbol-name sym))))
+
+(defun elisp--shorthand-aware-boundp (sym)
+ (boundp (intern-soft (symbol-name sym))))
+
(defun elisp-completion-at-point ()
"Function used for `completion-at-point-functions' in `emacs-lisp-mode'.
If the context at point allows only a certain category of
@@ -540,36 +626,41 @@ functions are annotated with \"<f>\" via the
;; the current form and use it to provide a more
;; specific completion table in more cases.
((eq fun-sym 'ignore-error)
- (list t obarray
+ (list t (elisp--completion-local-symbols)
:predicate (lambda (sym)
(get sym 'error-conditions))))
((elisp--expect-function-p beg)
- (list nil obarray
- :predicate #'fboundp
+ (list nil (elisp--completion-local-symbols)
+ :predicate
+ #'elisp--shorthand-aware-fboundp
:company-kind #'elisp--company-kind
:company-doc-buffer #'elisp--company-doc-buffer
:company-docsig #'elisp--company-doc-string
- :company-location #'elisp--company-location))
+ :company-location #'elisp--company-location
+ :company-deprecated #'elisp--company-deprecated))
(quoted
- (list nil obarray
+ (list nil (elisp--completion-local-symbols)
;; Don't include all symbols (bug#16646).
:predicate (lambda (sym)
- (or (boundp sym)
- (fboundp sym)
- (featurep sym)
- (symbol-plist sym)))
+ ;; shorthand-aware
+ (let ((sym (intern-soft (symbol-name sym))))
+ (or (boundp sym)
+ (fboundp sym)
+ (featurep sym)
+ (symbol-plist sym))))
:annotation-function
(lambda (str) (if (fboundp (intern-soft str)) " <f>"))
:company-kind #'elisp--company-kind
:company-doc-buffer #'elisp--company-doc-buffer
:company-docsig #'elisp--company-doc-string
- :company-location #'elisp--company-location))
+ :company-location #'elisp--company-location
+ :company-deprecated #'elisp--company-deprecated))
(t
(list nil (completion-table-merge
elisp--local-variables-completion-table
(apply-partially #'completion-table-with-predicate
- obarray
- #'boundp
+ (elisp--completion-local-symbols)
+ #'elisp--shorthand-aware-boundp
'strict))
:company-kind
(lambda (s)
@@ -578,7 +669,8 @@ functions are annotated with \"<f>\" via the
'variable))
:company-doc-buffer #'elisp--company-doc-buffer
:company-docsig #'elisp--company-doc-string
- :company-location #'elisp--company-location)))
+ :company-location #'elisp--company-location
+ :company-deprecated #'elisp--company-deprecated)))
;; Looks like a funcall position. Let's double check.
(save-excursion
(goto-char (1- beg))
@@ -606,11 +698,11 @@ functions are annotated with \"<f>\" via the
(ignore-errors
(forward-sexp 2)
(< (point) beg)))))
- (list t obarray
+ (list t (elisp--completion-local-symbols)
:predicate (lambda (sym) (get sym 'error-conditions))))
;; `ignore-error' with a list CONDITION parameter.
('ignore-error
- (list t obarray
+ (list t (elisp--completion-local-symbols)
:predicate (lambda (sym)
(get sym 'error-conditions))))
((and (or ?\( 'let 'let*)
@@ -620,18 +712,20 @@ functions are annotated with \"<f>\" via the
(up-list -1))
(forward-symbol -1)
(looking-at "\\_<let\\*?\\_>"))))
- (list t obarray
- :predicate #'boundp
+ (list t (elisp--completion-local-symbols)
+ :predicate #'elisp--shorthand-aware-boundp
:company-kind (lambda (_) 'variable)
:company-doc-buffer #'elisp--company-doc-buffer
:company-docsig #'elisp--company-doc-string
- :company-location #'elisp--company-location))
- (_ (list nil obarray
- :predicate #'fboundp
+ :company-location #'elisp--company-location
+ :company-deprecated #'elisp--company-deprecated))
+ (_ (list nil (elisp--completion-local-symbols)
+ :predicate #'elisp--shorthand-aware-fboundp
:company-kind #'elisp--company-kind
:company-doc-buffer #'elisp--company-doc-buffer
:company-docsig #'elisp--company-doc-string
:company-location #'elisp--company-location
+ :company-deprecated #'elisp--company-deprecated
))))))))
(nconc (list beg end)
(if (null (car table-etc))
@@ -654,6 +748,11 @@ functions are annotated with \"<f>\" via the
((facep sym) 'color)
(t 'text))))
+(defun elisp--company-deprecated (str)
+ (let ((sym (intern-soft str)))
+ (or (get sym 'byte-obsolete-variable)
+ (get sym 'byte-obsolete-info))))
+
(defun lisp-completion-at-point (&optional _predicate)
(declare (obsolete elisp-completion-at-point "25.1"))
(elisp-completion-at-point))
@@ -661,6 +760,7 @@ functions are annotated with \"<f>\" via the
;;; Xref backend
(declare-function xref-make "xref" (summary location))
+(declare-function xref-item-location "xref" (this))
(defun elisp--xref-backend () 'elisp)
@@ -696,21 +796,214 @@ Each function should return a list of xrefs, or nil; the first
non-nil result supersedes the xrefs produced by
`elisp--xref-find-definitions'.")
-(cl-defmethod xref-backend-definitions ((_backend (eql elisp)) identifier)
+(defun elisp--xref-list-index ()
+ "Return the list index of the form at point, moving to the start.
+If the buffer start was reached, return nil."
+ (let ((i 0))
+ (while (condition-case nil
+ (let ((pt (point)))
+ (backward-sexp)
+ (< (point) pt))
+ (scan-error nil))
+ (setq i (1+ i)))
+ (and (not (bobp)) i)))
+
+(defun elisp--xref-infer-namespace (pos)
+ "Find the likely namespace of the identifier at POS.
+Return one of `function', `variable' `maybe-variable', `feature', `face', or
+`any' (indicating any namespace). `maybe-variable' indicates a variable
+namespace but with lower confidence."
+ (save-excursion
+ (goto-char pos)
+ (cl-flet ((looking-at-sym ()
+ (let ((val (save-excursion
+ (ignore-errors (read (current-buffer))))))
+ (and (symbolp val) val))))
+ (cond
+ ((and (eq (char-before pos) ?\')
+ (eq (char-before (1- pos)) ?#))
+ ;; #'IDENT
+ 'function)
+ ((memq (char-before pos) '(?\' ?`))
+ ;; 'IDENT or `IDENT -- try to disambiguate.
+ (backward-char) ; Step over '
+ (let ((i (elisp--xref-list-index))
+ (sym (looking-at-sym)))
+ (cond
+ ((eql i 1)
+ (cond
+ ((memq sym '( featurep require provide))
+ 'feature)
+ ((memq sym
+ '(
+ ;; We are mostly interested in functions that take a
+ ;; function symbol as argument:
+ fboundp symbol-function fset
+ ;; ... but we include some common higher-order functions
+ ;; as well, even though the argument really should
+ ;; be #'-quoted:
+ function-get function-put
+ func-arity functionp
+ funcall funcall-interactively
+ apply mapcar mapc mapcan mapconcat
+ apply-partially
+ substitute-key-definition))
+ 'function)
+ ((memq sym
+ '(
+ ;; Functions taking a variable symbol as first argument.
+ ;; More of these could be added for greater precision.
+ boundp set symbol-value
+ special-variable-p local-variable-p
+ local-variable-if-set-p
+ make-variable-buffer-local
+ default-value set-default make-local-variable
+ buffer-local-value))
+ 'variable)
+ ((memq sym
+ '(
+ ;; FIXME: Add more functions taking a face
+ ;; symbol for greater precision.
+ facep face-name face-id))
+ 'face)
+ (t 'any)))
+ ((and (eql i 2)
+ (memq sym '( global-set-key local-set-key
+ substitute-key-definition
+ add-hook)))
+ 'function)
+ ((and (eql i 3)
+ (memq sym '( define-key add-function)))
+ 'function)
+ (t 'any))))
+ ((or (and (eq (char-before (1- pos)) ?,)
+ (eq (char-before pos) ?@))
+ (eq (char-before pos) ?,))
+ ;; ,IDENT or ,@IDENT
+ 'variable)
+ (t
+ ;; Unquoted name -- look at the context. General scheme:
+ ;; (K-HEAD ... (J-HEAD ... (I-HEAD ... IDENT
+ ;; ^ index K ^ index J ^ index I
+ (let* ((i (elisp--xref-list-index))
+ (i-head (looking-at-sym))
+ (i-paren (and i (eq (char-before) ?\()
+ (progn (backward-char) t)))
+ (i-quoted (and i-paren (memq (char-before) '(?\' ?`))))
+ (j (and i-paren (elisp--xref-list-index)))
+ (j-head (and j (looking-at-sym)))
+ (j-paren (and j (eq (char-before) ?\()
+ (progn (backward-char) t)))
+ (j-quoted (and j-paren (memq (char-before) '(?\' ?`))))
+ (k (and j-paren (elisp--xref-list-index)))
+ (k-head (and k (looking-at-sym)))
+ (k-paren (and k (eq (char-before) ?\()
+ (progn (backward-char) t)))
+ (k-quoted (and k-paren (memq (char-before) '(?\' ?`)))))
+ (cond
+ ((or i-quoted j-quoted k-quoted)
+ ;; '(... IDENT or '(... (... IDENT or '(... (... (... IDENT
+ 'any)
+ ((and (eql j 1)
+ (memq j-head '( let let* letrec dlet lambda)))
+ ;; (let (... IDENT
+ 'variable)
+ ((and (eql j 2)
+ (memq j-head '( defun defmacro defsubst
+ define-inline declare-function
+ defadvice
+ cl-defmethod cl-defgeneric)))
+ ;; (defun FUNC (... IDENT
+ 'variable)
+ ((eq j-head 'cond)
+ ;; (cond ... (... IDENT
+ 'variable)
+ ((and (eql k 1)
+ (memq k-head '( let let* letrec dlet )))
+ ;; (let (... (... IDENT
+ 'variable)
+ ((eql i 0)
+ ;; (IDENT ...
+ 'function)
+ ((functionp i-head)
+ ;; (FUNC ... IDENT
+ 'variable)
+ ((and (eql i 1)
+ (cond
+ ((memq i-head '( function
+ defun defmacro defsubst
+ define-inline declare-function
+ defadvice
+ cl-defmethod cl-defgeneric))
+ 'function)
+ ((memq i-head '( defvar defvar-local defconst defcustom))
+ 'variable)
+ ((eq i-head 'defface)
+ 'face))))
+ ((memq i-head '( if while and or when unless progn prog1
+ let let* lambda defun defsubst defvar defconst))
+ ;; arg to some common non-function forms
+ 'variable)
+ ;; Anything else: probably a variable, but since i-head may be
+ ;; a macro we cannot be sure.
+ (t 'maybe-variable))))))))
+
+(cl-defmethod xref-backend-identifier-at-point ((_backend (eql 'elisp)))
+ (let ((bounds (bounds-of-thing-at-point 'symbol)))
+ (and bounds
+ (let ((ident (buffer-substring-no-properties
+ (car bounds) (cdr bounds))))
+ ;; Use a property to transport the location of the identifier.
+ (propertize ident 'pos (car bounds))))))
+
+(cl-defmethod xref-backend-definitions ((_backend (eql 'elisp)) identifier)
(require 'find-func)
- ;; FIXME: use information in source near point to filter results:
- ;; (dvc-log-edit ...) - exclude 'feature
- ;; (require 'dvc-log-edit) - only 'feature
- ;; Semantic may provide additional information
- ;;
(let ((sym (intern-soft identifier)))
(when sym
- (elisp--xref-find-definitions sym))))
+ (let* ((pos (get-text-property 0 'pos identifier))
+ (namespace (if pos
+ (elisp--xref-infer-namespace pos)
+ 'any))
+ (defs (elisp--xref-find-definitions sym)))
+ (if (eq namespace 'maybe-variable)
+ (or (elisp--xref-filter-definitions defs 'variable sym)
+ (elisp--xref-filter-definitions defs 'any sym))
+ (elisp--xref-filter-definitions defs namespace sym))))))
+
+(defun elisp--xref-filter-definitions (definitions namespace symbol)
+ (if (eq namespace 'any)
+ (if (memq symbol minor-mode-list)
+ ;; The symbol is a minor mode. These should be defined by
+ ;; "define-minor-mode", which means the variable and the
+ ;; function are declared in the same place. So we return only
+ ;; the function, arbitrarily.
+ ;;
+ ;; There is an exception, when the variable is defined in C
+ ;; code, as for abbrev-mode.
+ (cl-loop for d in definitions
+ for loc = (xref-item-location d)
+ for file = (xref-elisp-location-file loc)
+ when (or (not (eq (xref-elisp-location-type loc) 'defvar))
+ (null file)
+ (string-prefix-p "src/" file))
+ collect d)
+ definitions)
+ (let ((expected-types
+ (pcase-exhaustive namespace
+ ('function '( nil defalias define-type
+ cl-defgeneric cl-defmethod))
+ ('variable '(defvar))
+ ('face '(defface))
+ ('feature '(feature)))))
+ (cl-loop for d in definitions
+ when (memq
+ (xref-elisp-location-type (xref-item-location d))
+ expected-types)
+ collect d))))
(defun elisp--xref-find-definitions (symbol)
;; The file name is not known when `symbol' is defined via interactive eval.
(let (xrefs)
-
(let ((temp elisp-xref-find-def-functions))
(while (and (null xrefs)
temp)
@@ -754,7 +1047,7 @@ non-nil result supersedes the xrefs produced by
;; First call to find-lisp-object-file-name for an object
;; defined in C; the doc strings from the C source have
;; not been loaded yet. Second call will return "src/*.c"
- ;; in file; handled by 't' case below.
+ ;; in file; handled by t case below.
(push (elisp--xref-make-xref nil symbol (help-C-file-name (symbol-function symbol) 'subr)) xrefs))
((and (setq doc (documentation symbol t))
@@ -774,6 +1067,8 @@ non-nil result supersedes the xrefs produced by
((setq generic (cl--generic symbol))
;; FIXME: move this to elisp-xref-find-def-functions, in cl-generic.el
+ ;; XXX: How are we going to support using newer xref
+ ;; with older versions of Emacs, though?
;; A generic function. If there is a default method, it
;; will appear in the method table, with no
@@ -796,7 +1091,7 @@ non-nil result supersedes the xrefs produced by
specializers))
(file (find-lisp-object-file-name met-name 'cl-defmethod)))
(dolist (item specializers)
- ;; default method has all 't' in specializers
+ ;; Default method has all t in specializers.
(setq non-default (or non-default (not (equal t item)))))
(when (and file
@@ -836,29 +1131,6 @@ non-nil result supersedes the xrefs produced by
;; return "src/*.c" in file; handled below.
(push (elisp--xref-make-xref 'defvar symbol (help-C-file-name symbol 'var)) xrefs))
- ((string= "src/" (substring file 0 4))
- ;; The variable is defined in a C source file; don't check
- ;; for define-minor-mode.
- (push (elisp--xref-make-xref 'defvar symbol file) xrefs))
-
- ((memq symbol minor-mode-list)
- ;; The symbol is a minor mode. These should be defined by
- ;; "define-minor-mode", which means the variable and the
- ;; function are declared in the same place. So we return only
- ;; the function, arbitrarily.
- ;;
- ;; There is an exception, when the variable is defined in C
- ;; code, as for abbrev-mode.
- ;;
- ;; IMPROVEME: If the user is searching for the identifier at
- ;; point, we can determine whether it is a variable or
- ;; function by looking at the source code near point.
- ;;
- ;; IMPROVEME: The user may actually be asking "do any
- ;; variables by this name exist"; we need a way to specify
- ;; that.
- nil)
-
(t
(push (elisp--xref-make-xref 'defvar symbol file) xrefs))
@@ -875,7 +1147,7 @@ non-nil result supersedes the xrefs produced by
(declare-function xref-apropos-regexp "xref" (pattern))
-(cl-defmethod xref-backend-apropos ((_backend (eql elisp)) pattern)
+(cl-defmethod xref-backend-apropos ((_backend (eql 'elisp)) pattern)
(apply #'nconc
(let ((regexp (xref-apropos-regexp pattern))
lst)
@@ -893,7 +1165,8 @@ non-nil result supersedes the xrefs produced by
(facep sym)))
'strict))
-(cl-defmethod xref-backend-identifier-completion-table ((_backend (eql elisp)))
+(cl-defmethod xref-backend-identifier-completion-table ((_backend
+ (eql 'elisp)))
elisp--xref-identifier-completion-table)
(cl-defstruct (xref-elisp-location
@@ -1050,7 +1323,7 @@ this command arranges for all errors to enter the debugger."
(defun last-sexp-setup-props (beg end value alt1 alt2)
"Set up text properties for the output of `elisp--eval-last-sexp'.
-BEG and END are the start and end of the output in current-buffer.
+BEG and END are the start and end of the output in current buffer.
VALUE is the Lisp value printed, ALT1 and ALT2 are strings for the
alternative printed representations that can be displayed."
(let ((map (make-sparse-keymap)))
@@ -1451,7 +1724,7 @@ Elisp eldoc behaviour. Consider variable docstrings and function
signatures only, in this order. If none applies, returns nil.
Changes to `eldoc-documentation-functions' and
`eldoc-documentation-strategy' are _not_ reflected here. As such
-it is preferrable to use ElDoc's interfaces directly.")
+it is preferable to use ElDoc's interfaces directly.")
(make-obsolete 'elisp-eldoc-documentation-function
"use ElDoc's interfaces instead." "28.1")
@@ -1862,5 +2135,8 @@ Runs in a batch-mode Emacs. Interactively use variable
(terpri)
(pp collected)))
+
+(put 'read-symbol-shorthands 'safe-local-variable #'consp)
+
(provide 'elisp-mode)
;;; elisp-mode.el ends here