]> git.eshelyaron.com Git - emacs.git/commitdiff
Improve "find definition" in *Help* buffers
authorDaniel Martín <mardani29@yahoo.es>
Sun, 27 Dec 2020 08:04:56 +0000 (09:04 +0100)
committerLars Ingebrigtsen <larsi@gnus.org>
Sun, 27 Dec 2020 08:04:56 +0000 (09:04 +0100)
* lisp/emacs-lisp/find-func.el (find-function-search-for-symbol): If
our regexp algorithm could not find a location for the symbol
definition, resort to find-function--search-by-expanding-macros.
* test/lisp/emacs-lisp/find-func-tests.el: Add a automatic test for a
function and variable generated by a macro.
* etc/NEWS: Advertise the improved functionality (bug#45443).

etc/NEWS
lisp/emacs-lisp/find-func.el
test/lisp/emacs-lisp/find-func-tests.el

index 9ae8cc91d638759078ade4d11e5e5b560a8649fc..4f072df31c5e8a489cac641cccd87ad55a81451e 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -114,6 +114,12 @@ choosing a group, or clicking a button in the "*Help*" buffers when
 looking at the doc string of a function that belongs to one of these
 groups.
 
+---
+** Improved "find definition" feature of *Help* buffers.
+Now clicking on the link to find the definition of functions generated
+by 'cl-defstruct', or variables generated by 'define-derived-mode',
+for example, will go to the exact place where they are defined.
+
 ** New variable 'redisplay-skip-initial-frame' to enable batch redisplay tests.
 Setting it to nil forces the redisplay to do its job even in the
 initial frame used in batch mode.
index 074e7db295bfd67e19fc3b315a79520c1a500ec4..7796a72ecfd96921ab00c1ae634273f91b044e03 100644 (file)
@@ -389,7 +389,70 @@ The search is done in the source for library LIBRARY."
                   (progn
                     (beginning-of-line)
                     (cons (current-buffer) (point)))
-                (cons (current-buffer) nil)))))))))
+                ;; If the regexp search didn't find the location of
+                ;; the symbol (for example, because it is generated by
+                ;; a macro), try a slightly more expensive search that
+                ;; expands macros until it finds the symbol.
+                (cons (current-buffer)
+                      (find-function--search-by-expanding-macros
+                       (current-buffer) symbol type))))))))))
+
+(defun find-function--try-macroexpand (form)
+  "Try to macroexpand FORM in full or partially.
+This is a best-effort operation in which if macroexpansion fails,
+this function returns FORM as is."
+  (ignore-errors
+    (or
+     (macroexpand-all form)
+     (macroexpand-1 form)
+     form)))
+
+(defun find-function--any-subform-p (form pred)
+  "Walk FORM and apply PRED to its subexpressions.
+Return t if any PRED returns t."
+  (cond
+   ((not (consp form)) nil)
+   ((funcall pred form) t)
+   (t
+    (cl-destructuring-bind (left-child . right-child) form
+      (or
+       (find-function--any-subform-p left-child pred)
+       (find-function--any-subform-p right-child pred))))))
+
+(defun find-function--search-by-expanding-macros (buf symbol type)
+  "Expand macros in BUF to search for the definition of SYMBOL of TYPE."
+  (catch 'found
+    (with-current-buffer buf
+      (save-excursion
+        (goto-char (point-min))
+        (condition-case nil
+            (while t
+              (let ((form (read (current-buffer)))
+                    (expected-symbol-p
+                      (lambda (form)
+                        (cond
+                         ((null type)
+                          ;; Check if a given form is a `defalias' to
+                          ;; SYM, the function name we are searching
+                          ;; for.  All functions in Emacs Lisp
+                          ;; ultimately expand to a `defalias' form
+                          ;; after several steps of macroexpansion.
+                          (and (eq (car-safe form) 'defalias)
+                               (equal (car-safe (cdr form))
+                                      `(quote ,symbol))))
+                         ((eq type 'defvar)
+                          ;; Variables generated by macros ultimately
+                          ;; expand to `defvar'.
+                          (and (eq (car-safe form) 'defvar)
+                               (eq (car-safe (cdr form)) symbol)))
+                         (t nil)))))
+                (when (find-function--any-subform-p
+                       (find-function--try-macroexpand form)
+                       expected-symbol-p)
+                  ;; We want to return the location at the beginning
+                  ;; of the macro, so move back one sexp.
+                  (throw 'found (progn (backward-sexp) (point))))))
+          (end-of-file nil))))))
 
 (defun find-function-library (function &optional lisp-only verbose)
   "Return the pair (ORIG-FUNCTION . LIBRARY) for FUNCTION.
index d77eb6757ff7ea7e80cef64a0db4503f27d8173e..03df4bb9ff4af6b3c73cfe68dcaa905c55b7cf22 100644 (file)
                      (concat data-directory (kbd "n x / TAB RET"))
                    (read-library-name)))))
 
+;; Avoid a byte-compilation warning that may confuse people reading
+;; the result of the following test.
+(declare-function compilation--message->loc nil "compile")
+
+(ert-deftest find-func-tests--locate-macro-generated-symbols () ;bug#45443
+  (should (cdr (find-function-search-for-symbol
+                #'compilation--message->loc nil "compile")))
+  (should (cdr (find-function-search-for-symbol
+                'c-mode-hook 'defvar "cc-mode"))))
+
 (provide 'find-func-tests)
 ;;; find-func-tests.el ends here