Subject: Re: bug#47109: eldoc.el: Allow custom separator between documentations in the echo area

From: Mathieu Marques <[email-masked]>
Date: Thu, 18 Mar 2021 19:30:12 +0100

Hello again,

Thanks for the feedback.  I agree with the will of allowing more freedom.

I joined a replacement patch with the different route that this is now
taking,
namely the removal of `eldoc--format-doc-buffer' in favor of
`eldoc-render-documentation'.  Is that closer to what you had in mind?

There's an issue however with the `-hr` format function that won't work with
terminal-based Emacs due to `:strike-through` apparently not supported
there.  I
was wondering whether a simple mention of that limitation in the docstring
would
be enough?  Terminal-based users would still be able to use the default
format
function.  Regarding the `:extend` keyword not being supported on 26, I
think
a better way than conditionally add it would be to provide a second `-hr`
formatting function that relies on something that is supported on 26 ie.
overlay.  Although I have little to no experience with overlays so I'm not
sure
what would be the preferred way of making a horizontal divider.
Furthermore, it
shouldn't break when the render function is used to return a string.
-- 
Mathieu Marques
From b1b251d27c6bdf765ed70a79dca7741e6effa99c Mon Sep 17 00:00:00 2001
From: Mathieu Marques <[email-masked]>
Date: Fri, 12 Mar 2021 19:45:18 +0100
Subject: [PATCH] Add a responsible Eldoc render-to-target function

  * lisp/emacs-lisp/eldoc.el (eldoc-render-documentation):
  Replace eldoc--format-doc-buffer (bug#47109).
  (eldoc--format-doc-buffer): Removed.
  (eldoc-display-in-buffer): Use the new render function.
  (eldoc-display-in-echo-area): Use the new render function.
  (eldoc-documentation-format-function): New user option.
  (eldoc-documentation-format-concat): New format function.
  (eldoc-documentation-format-concat-hr): New format function.
---
 lisp/emacs-lisp/eldoc.el | 125 +++++++++++++++++++++++++--------------
 1 file changed, 81 insertions(+), 44 deletions(-)

diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el
index 485ea26379..64c622039f 100644
--- a/lisp/emacs-lisp/eldoc.el
+++ b/lisp/emacs-lisp/eldoc.el
@@ -129,6 +129,19 @@ window.  If the value is the symbol `maybe', then the echo area
 is only skipped if the documentation doesn't fit there."
   :type 'boolean)
 
+(defcustom eldoc-documentation-format-function #'eldoc-documentation-format-concat
+  "Function used to format documentation in the *eldoc* buffer.
+
+- `eldoc-documentation-format-concat': concatenates documentations together
+  joining with a newline character'.
+
+- `eldoc-documentation-format-concat-hr': concatenates documentations together
+  joining with a horizontal rule."
+  :type '(radio (function-item eldoc-documentation-format-concat)
+                (function-item eldoc-documentation-format-concat-hr)
+                function)
+  :version "28.1")
+
 (defface eldoc-highlight-function-argument
   '((t (:inherit bold)))
   "Face used for the argument at point in a function's argument list.
@@ -466,44 +479,71 @@ This holds the results of the last documentation request."
                                              (buffer-name)))
     (display-buffer (current-buffer))))
 
-(defun eldoc--format-doc-buffer (docs)
-  "Ensure DOCS are displayed in an *eldoc* buffer."
-  (with-current-buffer (if (buffer-live-p eldoc--doc-buffer)
-                           eldoc--doc-buffer
-                         (setq eldoc--doc-buffer
-                               (get-buffer-create " *eldoc*")))
-    (unless (eq docs eldoc--doc-buffer-docs)
-      (setq-local eldoc--doc-buffer-docs docs)
-      (let ((inhibit-read-only t)
-            (things-reported-on))
-        (erase-buffer) (setq buffer-read-only t)
-        (local-set-key "q" 'quit-window)
-        (cl-loop for (docs . rest) on docs
-                 for (this-doc . plist) = docs
-                 for thing = (plist-get plist :thing)
-                 when thing do
-                 (cl-pushnew thing things-reported-on)
-                 (setq this-doc
-                       (concat
-                        (propertize (format "%s" thing)
-                                    'face (plist-get plist :face))
-                        ": "
-                        this-doc))
-                 do (insert this-doc)
-                 when rest do (insert "\n")
-                 finally (goto-char (point-min)))
-        ;; Rename the buffer, taking into account whether it was
-        ;; hidden or not
-        (rename-buffer (format "%s*eldoc%s*"
-                               (if (string-match "^ " (buffer-name)) " " "")
-                               (if things-reported-on
-                                   (format " for %s"
-                                           (mapconcat
-                                            (lambda (s) (format "%s" s))
-                                            things-reported-on
-                                            ", "))
-                                 ""))))))
-  eldoc--doc-buffer)
+(defun eldoc-documentation-format-concat (items &optional separator)
+  "Return documentation ITEMS concatenated.
+Join with SEPARATOR which defaults to \n.
+See `eldoc-documentation-format-function'."
+  (mapconcat (lambda (item)
+               (pcase-let* ((`(,documentation . ,plist) item)
+                            (face (plist-get plist :face))
+                            (thing (plist-get plist :thing)))
+                 (concat (when thing
+                           (concat (propertize (format "%s" thing) 'face face) ": "))
+                         documentation)))
+             items
+             (or separator "\n")))
+
+(defun eldoc-documentation-format-concat-hr (items)
+  "Return ITEMS concatenated like `eldoc-documentation-format-concat' but join
+with a horizontal rule.
+See `eldoc-documentation-format-function'."
+  (let ((separator (concat
+                    "\n"
+                    (propertize "\n" 'face
+                                '(:extend t :inherit shadow :strike-through t)))))
+    (eldoc-documentation-format-concat items separator)))
+
+(defun eldoc--render-documentation-buffer (text buffer)
+  "Sub-routine to write documentation TEXT in BUFFER.  Return it afterwards."
+  (with-current-buffer buffer
+    (let ((inhibit-read-only t)
+          (things-reported-on))
+      (erase-buffer)
+      (setq-local buffer-read-only t)
+      (local-set-key "q" 'quit-window)
+      (insert text)
+      ;; Rename the buffer, taking into account whether it was hidden
+      (rename-buffer (format "%s*eldoc%s*"
+                             (if (string-match "^ " (buffer-name)) " " "")
+                             (if things-reported-on
+                                 (format " for %s"
+                                         (mapconcat
+                                          (lambda (s) (format "%s" s))
+                                          things-reported-on
+                                          ", "))
+                               "")))
+
+      ))
+  buffer)
+
+(defun eldoc-render-documentation (documentations &optional target)
+  "Render DOCUMENTATIONS onto TARGET.
+If optional TARGET is not provided, simply return the formatted string.  If it
+is t, render the documentation into the *eldoc* buffer `eldoc--doc-buffer'.
+Else, assume TARGET is a name that can be passed to `get-buffer-create'."
+  (unless (eq documentations eldoc--doc-buffer-docs)
+    (setq-local eldoc--doc-buffer-docs documentations)
+    (let ((buffer (when target
+                    (if (or (bufferp target) (stringp target))
+                        (get-buffer-create target)
+                      (setq eldoc--doc-buffer
+                            (get-buffer-create (if (buffer-live-p eldoc--doc-buffer)
+                                                   eldoc--doc-buffer
+                                                 " *eldoc*"))))))
+          (body (funcall eldoc-documentation-format-function documentations)))
+      (if buffer
+          (eldoc--render-documentation-buffer body buffer)
+        body))))
 
 (defun eldoc--echo-area-substring (available)
   "Given AVAILABLE lines, get buffer substring to display in echo area.
@@ -590,14 +630,11 @@ Honor `eldoc-echo-area-use-multiline-p' and
                ;; Else, given a positive number of logical lines, we
                ;; format the *eldoc* buffer, using as most of its
                ;; contents as we know will fit.
-               (with-current-buffer (eldoc--format-doc-buffer docs)
+               (with-current-buffer (eldoc-render-documentation docs t)
                  (save-excursion
                    (eldoc--echo-area-substring available))))
               (t ;; this is the "truncate brutally" situation
-               (let ((string
-                      (with-current-buffer (eldoc--format-doc-buffer docs)
-                        (buffer-substring (goto-char (point-min))
-                                          (line-end-position 1)))))
+               (let ((string (eldoc-render-documentation docs)))
                  (if (> (length string) width)  ; truncation to happen
                      (unless (eldoc--echo-area-prefer-doc-buffer-p t)
                        (truncate-string-to-width string width))
@@ -609,7 +646,7 @@ Honor `eldoc-echo-area-use-multiline-p' and
 (defun eldoc-display-in-buffer (docs interactive)
   "Display DOCS in a dedicated buffer.
 If INTERACTIVE is t, also display the buffer."
-  (eldoc--format-doc-buffer docs)
+  (eldoc-render-documentation docs t)
   (when interactive
     (eldoc-doc-buffer)))
 
-- 
2.30.1