Subject: bug#60568: [FR] 30.0.50; Help buffers and function bodies for generated functions

From: Gregory Heytings <[email-masked]>
Date: Thu, 05 Jan 2023 20:44:21 +0000

>>> Well, searching for the regexp "defun .*org-babel-execute:" would have 
>>> done that, right?
>>
>> In this specific case, yes.  But in general?
>
> I hope that in a more general case we could use something similar, since 
> every definition of a function should start from "defun " followed by 
> some part of its name, right?
>

Not always, I can imagine functions defined with fset for example.

What about the attached patch?  It seems to work well.
From 38d0941bb87c0b46f27eb4cf496f24bba071d982 Mon Sep 17 00:00:00 2001
From: Gregory Heytings <[email-masked]>
Date: Thu, 5 Jan 2023 20:41:37 +0000
Subject: [PATCH] Try to find where dynamically defined functions were defined

* lisp/help-mode.el
(help-function-def--find-probable-definition-place): New function.
(help-function-def--button-function): Use it.  See bug#60568.
---
 lisp/help-mode.el | 94 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 93 insertions(+), 1 deletion(-)

diff --git a/lisp/help-mode.el b/lisp/help-mode.el
index bf64d032b6..542c477789 100644
--- a/lisp/help-mode.el
+++ b/lisp/help-mode.el
@@ -252,6 +252,87 @@ 'help-customize-face
 		   (customize-face v))
   'help-echo (purecopy "mouse-2, RET: customize face"))
 
+(defun help-function-def--find-probable-definition-place (fun)
+  "Find the function in which FUN was likely defined.
+FUN is the body of a function, returned by `symbol-function'.
+The current buffer must be visiting the file in which the
+function was defined (see `symbol-file').
+If the function is found, return a cons with its name and its
+beginning position.
+Otherwise, return nil."
+  (save-excursion
+    ;; Build a list of strings with the symbols (and strings) of FUN.
+    (let ((names
+           (mapcar (lambda (el)
+                     (concat
+                      "\\_<"
+                      (regexp-quote (format "%s" el))
+                      "\\_>"))
+                   (flatten-tree (symbol-function fun))))
+          results)
+      (delete-dups names)
+      (when names
+        ;; Build an alist with all functions in which each symbol is
+        ;; found.
+        (dolist (el names)
+          ;; Exclude symbols that are 3 characters or less.
+          (when (> (length el) 9)
+            (let (result)
+              (goto-char (point-min))
+              (while (re-search-forward el nil t)
+                (push (lisp-current-defun-name) result))
+              (delete-dups result)
+              (when result
+                (push (cons el result) results)))))
+        (when results
+          ;; First check if one of the symbols is found in a single
+          ;; function.
+          (let ((el (catch 'found
+                      (mapcar
+                       (lambda (el)
+                         (if (= (length el) 2)
+                             (throw 'found (car el))))
+                       results))))
+            (if (stringp el)
+                ;; If one such function was found, it's the function
+                ;; we are after.
+                (progn
+                  (goto-char (point-min))
+                  (re-search-forward el nil t)
+                  (beginning-of-defun)
+                  (cons (lisp-current-defun-name) (point)))
+              ;; Otherwise, find which function contains the most
+              ;; symbols in FUN.
+              (let (places)
+                (dolist (el results)
+                  (dolist (e (cdr el))
+                    (push e places)))
+                (let* ((sorted-places (sort places #'string<))
+                       (ptr sorted-places)
+                       (count 1)
+                       (max 0)
+                       result)
+                  (while ptr
+                    (if (string= (car ptr) (cadr ptr))
+                        (setq count (1+ count))
+                      (when (> count max)
+                        (setq max count)
+                        (setq result (car ptr)))
+                      (setq count 1))
+                    (setq ptr (cdr ptr)))
+                  (when result
+                    ;; Determine the beginning position of that
+                    ;; function.
+                    (goto-char (point-min))
+                    (catch 'found
+                      (while (re-search-forward
+                              (concat "\\_<" (regexp-quote result) "\\_>"))
+                        (when (string= (lisp-current-defun-name)
+                                       result)
+                          (throw 'found nil))))
+                    (beginning-of-defun)
+                    (cons result (point))))))))))))
+
 (defun help-function-def--button-function (fun &optional file type)
   (or file
       (setq file (find-lisp-object-file-name fun type)))
@@ -281,7 +362,18 @@ help-function-def--button-function
             (unless (= (point) position)
               (push-mark nil t))
             (goto-char position))
-        (message "Unable to find location in file")))))
+        (let ((probable-definition-place
+               (help-function-def--find-probable-definition-place fun)))
+          (when probable-definition-place
+            (goto-char (cdr probable-definition-place)))
+	  (with-help-window (help-buffer)
+	    (insert (format "Function %s could not be found in %s.\n\n"
+                            fun (file-name-nondirectory file)))
+            (when probable-definition-place
+              (insert (format "It was probably defined by %s.\n\n"
+                              (car probable-definition-place))))
+	    (insert "Function definition:\n\n")
+	    (insert (pp-to-string (symbol-function fun)))))))))
 
 (define-button-type 'help-function-def
   :supertype 'help-xref
-- 
2.39.0