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

From: Gregory Heytings <[email-masked]>
Date: Fri, 06 Jan 2023 09:11:02 +0000

>>> 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.
>
> SGTM.  With which cases did you test this?
>
> Adding Stefan, in case he has any comments.
>

Mainly with the original recipe and some variants of it.

I attach a slightly improved version of the patch.
From 841ad680dbbfa3a4a95a3b09a7409fcd9ce6cdd7 Mon Sep 17 00:00:00 2001
From: Gregory Heytings <[email-masked]>
Date: Fri, 6 Jan 2023 08:54:34 +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 | 111 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 110 insertions(+), 1 deletion(-)

diff --git a/lisp/help-mode.el b/lisp/help-mode.el
index bf64d032b6..15fd23c1cd 100644
--- a/lisp/help-mode.el
+++ b/lisp/help-mode.el
@@ -252,6 +252,91 @@ '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 symbol of a function.
+The current buffer must be visiting the file in which the
+function was defined (see `symbol-file').
+If the function in which FUN was likely defined 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
+                      (progn
+                      (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))
+                    (when (catch 'found
+                            (while (re-search-forward
+                                    (concat "\\_<"
+                                            (regexp-quote result)
+                                            "\\_>"))
+                              (when (string= (lisp-current-defun-name)
+                                             result)
+                                (throw 'found t)))
+                            (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 +366,31 @@ 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)))
+          (let ((help-buffer-under-preparation t))
+            (help-setup-xref (list #'help-function-def--button-function
+                                   fun file)
+		             (called-interactively-p 'interactive))
+	    (with-help-window (help-buffer)
+	      (insert (substitute-command-keys
+                       (format "Function %s could not be found in `%s'.\n\n"
+                              fun (file-name-nondirectory file))))
+              (setq help-mode--current-data (list :symbol fun
+                                                  :file file))
+	      (save-excursion
+	        (re-search-backward
+                 (substitute-command-keys "`\\([^`']+\\)'")
+                 nil t)
+	        (help-xref-button 1 'help-function-def fun file))
+              (when probable-definition-place
+                (insert (substitute-command-keys
+                         (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