]> git.eshelyaron.com Git - emacs.git/commitdiff
Better handle asynchronous Eldoc sources
authorJoão Távora <joaotavora@gmail.com>
Mon, 25 May 2020 15:39:40 +0000 (16:39 +0100)
committerJoão Távora <joaotavora@gmail.com>
Wed, 8 Jul 2020 10:25:33 +0000 (11:25 +0100)
This is a backward compatible redesign of significant parts of the
eldoc.el library.

Previously, Eldoc clients (major/minor modes setting its documentation
gathering variables) needed to directly call eldoc-message, an
internal function, to display the docstring to the user.  When more
asynchronous sources are involved, this is hard to do or even breaks
down.

Now, an Eldoc backend may return any non-nil, non-string value and
call a callback afterwards.  This restores power to Eldoc over how
(and crucially also when) to display the docstrings to the user.

Among other things, this fixes so called "doc blinking", or the very
short-lived display of a lower priority Eldoc message.  This would
happen if a particular producer of documentation finishes shortly
before a higher priority one, like in the LSP engine Eglot as reported
by Andrii Kolomoiets <andreyk.mad@gmail.com> and Dmitry Gutov
<dgutov@yandex.ru>.

Gathering docstrings is now delegated to the variable
eldoc-documentation-strategy, which is the new name for the
now-obsolete eldoc-documentation-function, and still accepts the
so-called "old protocol".  Examples of the new strategies enabled are
codified in functions such as eldoc-documentation-enthusiast,
eldoc-documentation-compose-eagerly, along with the existing
eldoc-documentation-compose and eldoc-documentation-default.

The work of displaying and formatting docstrings is shifted almost
fully to Eldoc itself and is delegated to the internal function
eldoc--handle-docs.  Among other improvements, it handles most of
eldoc-echo-area-use-multiline-p and outputs documentation to a
temporary *eldoc* buffer.

The manual and NEWS are updated to mention the new Eldoc features.

* lisp/emacs-lisp/eldoc.el (eldoc-documentation-functions):
Overhaul docstring.
(eldoc-documentation-compose, eldoc-documentation-default): Handle
non-nil, non-string values of elements of
eldoc-documentation-functions.  Use eldoc--handle-multiline.
(eldoc-print-current-symbol-info): Honour non-nil, non-string
values returned by eldoc-documentation-callback.
(eldoc--make-callback): Now also a function.
(eldoc-documentation-default, eldoc-documentation-compose): Tweak docstring.
(eldoc-documentation-enthusiast, eldoc-documentation-compose-eagerly):
New functions.
(eldoc-echo-area-use-multiline-p): Add new semantics.
(eldoc--handle-docs): Handle some of eldoc-echo-area-use-multiline-p.
(eldoc-doc-buffer): New command.
(eldoc-prefer-doc-buffer): New defcustom.
(eldoc--enthusiasm-curbing-timer): New variable.
(eldoc-documentation-strategy): Rename from eldoc-documentation-function.
(eldoc--supported-p): Use eldoc-documentation-strategy
(eldoc-highlight-function-argument)
(eldoc-argument-case, global-eldoc-mode)
(turn-on-eldoc-mode): Mention eldoc-documentation-strategy.
(eldoc-message-function): Mention eldoc--message.
(eldoc-message): Made obsolete.
(eldoc--message): New helper.

* lisp/hexl.el (hexl-print-current-point-info): Adjust to new
eldoc-documentation-functions protocol.

* lisp/progmodes/cfengine.el (cfengine3-documentation-function):
Adjust to new eldoc-documentation-functions protocol.

* lisp/progmodes/elisp-mode.el
(elisp-eldoc-documentation-function): Adjust to new
eldoc-documentation-functions protocol.

* lisp/progmodes/octave.el (octave-eldoc-function): Adjust to new
eldoc-documentation-functions protocol.

* lisp/progmodes/python.el (python-eldoc-function): Adjust to new
eldoc-documentation-functions protocol.

(eldoc-print-current-symbol-info): Rework with cl-labels.

* doc/emacs/programs.texi (Lisp Doc): Mention
eldoc-documentation-strategy.

* doc/lispref/modes.texi (Major Mode Conventions): Mention
eldoc-documentation-functions.

* etc/NEWS: Mention eldoc-documentation-strategy.

doc/emacs/programs.texi
doc/lispref/modes.texi
etc/NEWS
lisp/emacs-lisp/eldoc.el
lisp/hexl.el
lisp/progmodes/cfengine.el
lisp/progmodes/elisp-mode.el
lisp/progmodes/octave.el
lisp/progmodes/python.el

index 865a3a67d56f7cd4bb12a1dc769d643b1b9df3eb..2757c84b53af23adfd36dc26c6e6be2e19694078 100644 (file)
@@ -1273,17 +1273,19 @@ Eldoc mode, which is turned on by default, and affects buffers whose
 major mode sets the variables described below.  Use @w{@kbd{M-x
 global-eldoc-mode}} to turn it off globally.
 
-@vindex eldoc-documentation-function
+@vindex eldoc-documentation-strategy
 @vindex eldoc-documentation-functions
   These variables can be used to configure ElDoc mode:
 
 @table @code
-@item eldoc-documentation-function
+@item eldoc-documentation-strategy
 This variable holds the function which is used to retrieve
 documentation for the item at point from the functions in the hook
 @code{eldoc-documentation-functions}.  By default,
-@code{eldoc-documentation-function} returns the first documentation
-string produced by the @code{eldoc-documentation-functions} hook.
+@code{eldoc-documentation-strategy} returns the first documentation
+string produced by the @code{eldoc-documentation-functions} hook, but
+it may be customized to compose those functions' results in other
+ways.
 
 @item eldoc-documentation-functions
 This abnormal hook holds documentation functions.  It acts as a
index eaee56f0a32c7646a6ee71ce542d153b5e5c7e21..17e96079ab9a32ef0426f23d3ceabe91430d188e 100644 (file)
@@ -469,9 +469,10 @@ variable @code{imenu-generic-expression}, for the two variables
 @code{imenu-create-index-function} (@pxref{Imenu}).
 
 @item
-The mode can specify a local value for
-@code{eldoc-documentation-function} to tell ElDoc mode how to handle
-this mode.
+The mode can tell Eldoc mode how to retrieve different types of
+documentation for whatever is at point, by adding one or more
+buffer-local entries to the special hook
+@code{eldoc-documentation-functions}.
 
 @item
 The mode can specify how to complete various keywords by adding one or
index fc5c215d2a73feb213beebed7ffef1fdd6c728ed..f5b9e5ed152861e871b40d8cae8eb42257eb2eec 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -252,9 +252,11 @@ doc string functions.  This makes the results of all doc string
 functions accessible to the user through the existing single function hook
 'eldoc-documentation-function'.
 
-*** 'eldoc-documentation-function' is now a user option.
-Modes should use the new hook instead of this user option to register
-their backends.
+*** New user option 'eldoc-documentation-strategy'
+The built-in choices available for this user option let users compose
+the results of 'eldoc-documentation-functions' in various ways.  The
+user option replaces 'eldoc-documentation-function', which is now
+obsolete.
 
 ** Eshell
 
index ef5dbf8103fa6cc0eb8a926a5de8b715864952a1..bcf3a84d31996bdc2617db8f9e2515e4ae30387c 100644 (file)
@@ -47,6 +47,8 @@
 
 ;;; Code:
 
+(eval-when-compile (require 'cl-lib))
+
 (defgroup eldoc nil
   "Show function arglist or variable docstring in echo area."
   :group 'lisp
@@ -77,38 +79,49 @@ Actually, any name of a function which takes a string as an argument and
 returns another string is acceptable.
 
 Note that this variable has no effect, unless
-`eldoc-documentation-function' handles it explicitly."
+`eldoc-documentation-strategy' handles it explicitly."
   :type '(radio (function-item upcase)
                (function-item downcase)
                 function))
 (make-obsolete-variable 'eldoc-argument-case nil "25.1")
 
 (defcustom eldoc-echo-area-use-multiline-p 'truncate-sym-name-if-fit
-  "Allow long ElDoc messages to resize echo area display.
-If value is t, never attempt to truncate messages; complete symbol name
-and function arglist or 1-line variable documentation will be displayed
-even if echo area must be resized to fit.
-
-If value is any non-nil value other than t, symbol name may be truncated
-if it will enable the function arglist or documentation string to fit on a
-single line without resizing window.  Otherwise, behavior is just like
-former case.
-
-If value is nil, messages are always truncated to fit in a single line of
-display in the echo area.  Function or variable symbol name may be
-truncated to make more of the arglist or documentation string visible.
-
-Note that this variable has no effect, unless
-`eldoc-documentation-function' handles it explicitly."
-  :type '(radio (const :tag "Always" t)
-                (const :tag "Never" nil)
-                (const :tag "Yes, but truncate symbol names if it will\
- enable argument list to fit on one line" truncate-sym-name-if-fit)))
+  "Allow long ElDoc doc strings to resize echo area display.
+If value is t, never attempt to truncate messages, even if the
+echo area must be resized to fit.
+
+If value is a number (integer or floating point), it has the
+semantics of `max-mini-window-height', constraining the resizing
+for Eldoc purposes only.
+
+Any resizing respects `max-mini-window-height'.
+
+If value is any non-nil symbol other than t, the part of the doc
+string that represents the symbol's name may be truncated if it
+will enable the rest of the doc string to fit on a single line,
+without resizing the echo area.
+
+If value is nil, a doc string is always truncated to fit in a
+single line of display in the echo area."
+  :type '(radio (const   :tag "Always" t)
+                (float   :tag "Fraction of frame height" 0.25)
+                (integer :tag "Number of lines" 5)
+                (const   :tag "Never" nil)
+                (const   :tag "Yes, but ask major-mode to truncate
+ symbol names if it will\ enable argument list to fit on one
+ line" truncate-sym-name-if-fit)))
+
+(defcustom eldoc-prefer-doc-buffer nil
+  "Prefer Eldoc's documentation buffer if it is showing in some frame.
+If this variable's value is t and a piece of documentation needs
+to be truncated to fit in the echo area, do so only if Eldoc's
+documentation buffer is not already showing."
+  :type 'boolean)
 
 (defface eldoc-highlight-function-argument
   '((t (:inherit bold)))
   "Face used for the argument at point in a function's argument list.
-Note that this face has no effect unless the `eldoc-documentation-function'
+Note that this face has no effect unless the `eldoc-documentation-strategy'
 handles it explicitly.")
 
 ;;; No user options below here.
@@ -150,7 +163,7 @@ directly.  Instead, use `eldoc-add-command' and `eldoc-remove-command'.")
 This is used to determine if `eldoc-idle-delay' is changed by the user.")
 
 (defvar eldoc-message-function #'eldoc-minibuffer-message
-  "The function used by `eldoc-message' to display messages.
+  "The function used by `eldoc--message' to display messages.
 It should receive the same arguments as `message'.")
 
 (defun eldoc-edit-message-commands ()
@@ -203,7 +216,7 @@ expression point is on." :lighter eldoc-minor-mode-string
   :init-value t
   ;; For `read--expression', the usual global mode mechanism of
   ;; `change-major-mode-hook' runs in the minibuffer before
-  ;; `eldoc-documentation-function' is set, so `turn-on-eldoc-mode'
+  ;; `eldoc-documentation-strategy' is set, so `turn-on-eldoc-mode'
   ;; does nothing.  Configure and enable eldoc from
   ;; `eval-expression-minibuffer-setup-hook' instead.
   (if global-eldoc-mode
@@ -222,7 +235,7 @@ expression point is on." :lighter eldoc-minor-mode-string
 ;;;###autoload
 (defun turn-on-eldoc-mode ()
   "Turn on `eldoc-mode' if the buffer has ElDoc support enabled.
-See `eldoc-documentation-function' for more detail."
+See `eldoc-documentation-strategy' for more detail."
   (when (eldoc--supported-p)
     (eldoc-mode 1)))
 
@@ -241,7 +254,9 @@ reflect the change."
                (when (or eldoc-mode
                          (and global-eldoc-mode
                               (eldoc--supported-p)))
-                 (eldoc-print-current-symbol-info))))))
+                 ;; Don't ignore, but also don't full-on signal errors
+                 (with-demoted-errors "eldoc error: %s"
+                   (eldoc-print-current-symbol-info)) )))))
 
   ;; If user has changed the idle delay, update the timer.
   (cond ((not (= eldoc-idle-delay eldoc-current-idle-delay))
@@ -279,7 +294,10 @@ Otherwise work like `message'."
           (force-mode-line-update)))
     (apply #'message format-string args)))
 
-(defun eldoc-message (&optional string)
+(make-obsolete
+ 'eldoc-message "use `eldoc-documentation-functions' instead." "1.1.0")
+(defun eldoc-message (&optional string) (eldoc--message string))
+(defun eldoc--message (&optional string)
   "Display STRING as an ElDoc message if it's non-nil.
 
 Also store it in `eldoc-last-message' and return that value."
@@ -313,8 +331,8 @@ Also store it in `eldoc-last-message' and return that value."
        (not (minibufferp))      ;We don't use the echo area when in minibuffer.
        (if (and (eldoc-display-message-no-interference-p)
                (eldoc--message-command-p this-command))
-          (eldoc-message eldoc-last-message)
-         ;; No need to call eldoc-message since the echo area will be cleared
+          (eldoc--message eldoc-last-message)
+         ;; No need to call eldoc--message since the echo area will be cleared
          ;; for us, but do note that the last-message will be gone.
          (setq eldoc-last-message nil))))
 
@@ -338,12 +356,37 @@ Also store it in `eldoc-last-message' and return that value."
 
 \f
 (defvar eldoc-documentation-functions nil
-  "Hook for functions to call to return doc string.
-Each function should accept no arguments and return a one-line
-string for displaying doc about a function etc. appropriate to
-the context around point.  It should return nil if there's no doc
-appropriate for the context.  Typically doc is returned if point
-is on a function-like name or in its arg list.
+  "Hook of functions that produce doc strings.
+
+A doc string is typically relevant if point is on a function-like
+name, inside its arg list, or on any object with some associated
+information.
+
+Each hook function is called with at least one argument CALLBACK
+and decides whether to display a doc short string about the
+context around point.
+
+- If that decision can be taken quickly, the hook function may
+  call CALLBACK immediately following the protocol described
+  berlow.  Alternatively it may ignore CALLBACK entirely and
+  return either the doc string, or nil if there's no doc
+  appropriate for the context.
+
+- If the computation of said doc string (or the decision whether
+  there is one at all) is expensive or can't be performed
+  directly, the hook function should return a non-nil, non-string
+  value and arrange for CALLBACK to be called at a later time,
+  using asynchronous processes or other asynchronous mechanisms.
+
+Regardless of the context in which CALLBACK is called, it expects
+to be passed an argument DOCSTRING followed by an optional list
+of keyword-value pairs of the form (:KEY VALUE :KEY2 VALUE2...).
+KEY can be:
+
+* `:thing', VALUE is be a short string or symbol designating what
+  is being reported on;
+
+* `:face', VALUE is a symbol designating a face;
 
 Major modes should modify this hook locally, for example:
   (add-hook \\='eldoc-documentation-functions #\\='foo-mode-eldoc nil t)
@@ -351,77 +394,264 @@ so that the global value (i.e. the default value of the hook) is
 taken into account if the major mode specific function does not
 return any documentation.")
 
+(defvar eldoc--doc-buffer nil "Buffer holding latest eldoc-produced docs.")
+(defun eldoc-doc-buffer (&optional interactive)
+  "Get latest *eldoc* help buffer.  Interactively, display it."
+  (interactive (list t))
+  (prog1
+      (if (and eldoc--doc-buffer (buffer-live-p eldoc--doc-buffer))
+          eldoc--doc-buffer
+          (setq eldoc--doc-buffer (get-buffer-create "*eldoc*")))
+    (when interactive (display-buffer eldoc--doc-buffer))))
+
+(defun eldoc--handle-docs (docs)
+  "Display multiple DOCS in echo area.
+DOCS is a list of (STRING PLIST...).  It is already sorted.
+Honour most of `eldoc-echo-area-use-multiline-p'."
+  (if (null docs) (eldoc--message nil) ; if there's nothing to report
+                                       ; clear the echo area, but
+                                       ; don't erase the last *eldoc*
+                                       ; buffer.
+    ;; If there's something to report on, first establish some
+    ;; parameterso
+    (let* ((width (1- (window-width (minibuffer-window))))
+           (val (if (and (symbolp eldoc-echo-area-use-multiline-p)
+                         eldoc-echo-area-use-multiline-p)
+                    max-mini-window-height
+                  eldoc-echo-area-use-multiline-p))
+           (available (cl-typecase val
+                        (float (truncate (* (frame-height) val)))
+                        (integer val)
+                        (t 1)))
+           (things-reported-on)
+           single-sym-name)
+      ;; Then, compose the contents of the `*eldoc*' buffer
+      (with-current-buffer (eldoc-doc-buffer)
+        (let ((inhibit-read-only t))
+          (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")))
+        ;; rename the buffer
+        (when things-reported-on
+          (rename-buffer (format "*eldoc for %s*"
+                                 (mapconcat (lambda (s) (format "%s" s))
+                                            things-reported-on
+                                            ", ")))))
+      ;; Finally, output to the echo area.  We currently do handling
+      ;; the `truncate-sym-name-if-fit' special case first, then by
+      ;; selecting a top-section of the `*eldoc' buffer.  I'm pretty
+      ;; sure nicer strategies can be used here, probably by splitting
+      ;; this pfunction into some `eldoc-display-functions' special
+      ;; hook.
+      (if (and (eq 'truncate-sym-name-if-fit eldoc-echo-area-use-multiline-p)
+               (null (cdr docs))
+               (setq single-sym-name
+                     (format "%s" (plist-get (cdar docs) :thing)))
+               (> (+ (length (caar docs)) (length single-sym-name) 2) width))
+          (eldoc--message (caar docs))
+          (with-current-buffer (eldoc-doc-buffer)
+            (goto-char (point-min))
+            (cond
+             ;; We truncate a long message
+             ((> available 1)
+              (cl-loop
+               initially (goto-char (line-end-position (1+ available)))
+               for truncated = nil then t
+               for needed
+               = (let ((truncate-lines message-truncate-lines))
+                   (count-screen-lines (point-min) (point) t (minibuffer-window)))
+               while (> needed (if truncated (1- available) available))
+               do (goto-char (line-end-position (if truncated 0 -1)))
+               (while (bolp) (goto-char (line-end-position 0)))
+               finally
+               (unless (and truncated
+                            eldoc-prefer-doc-buffer
+                            (get-buffer-window eldoc--doc-buffer))
+                 (eldoc--message
+                  (concat (buffer-substring (point-min) (point))
+                          (and truncated
+                               (format
+                                "\n(Documentation truncated. Use `%s' to see rest)"
+                                (substitute-command-keys "\\[eldoc-doc-buffer]"))))))))
+             ((= available 1)
+              ;; truncate brutally ; FIXME: use `eldoc-prefer-doc-buffer' here, too?
+              (eldoc--message
+               (truncate-string-to-width
+                (buffer-substring (point-min) (line-end-position 1)) width)))))))))
+
+;; this variable should be unbound, but that confuses
+;; `describe-symbol' for some reason.
+(defvar eldoc--make-callback nil "Helper for function `eldoc--make-callback'.")
+
+(defun eldoc--make-callback (method)
+  "Make callback suitable for `eldoc-documentation-functions'."
+  (funcall eldoc--make-callback method))
+
 (defun eldoc-documentation-default ()
   "Show first doc string for item at point.
-Default value for `eldoc-documentation-function'."
-  (let ((res (run-hook-with-args-until-success 'eldoc-documentation-functions)))
-    (when res
-      (if eldoc-echo-area-use-multiline-p res
-        (truncate-string-to-width
-         res (1- (window-width (minibuffer-window))))))))
-
-(defun eldoc-documentation-compose ()
-  "Show multiple doc string results at once.
-Meant as a value for `eldoc-documentation-function'."
-  (let (res)
-    (run-hook-wrapped
-     'eldoc-documentation-functions
-     (lambda (f)
-       (let ((str (funcall f)))
-         (when str (push str res))
-         nil)))
-    (when res
-      (setq res (mapconcat #'identity (nreverse res) ", "))
-      (if eldoc-echo-area-use-multiline-p res
-        (truncate-string-to-width
-         res (1- (window-width (minibuffer-window))))))))
-
-(defcustom eldoc-documentation-function #'eldoc-documentation-default
-  "Function to call to return doc string.
-The function of no args should return a one-line string for displaying
-doc about a function etc.  appropriate to the context around point.
-It should return nil if there's no doc appropriate for the context.
-Typically doc is returned if point is on a function-like name or in its
-arg list.
-
-The result is used as is, so the function must explicitly handle
-the variables `eldoc-argument-case' and `eldoc-echo-area-use-multiline-p',
-and the face `eldoc-highlight-function-argument', if they are to have any
-effect."
+Default value for `eldoc-documentation-strategy'."
+  (run-hook-with-args-until-success 'eldoc-documentation-functions
+   (eldoc--make-callback :patient)))
+
+(defun eldoc-documentation-compose (&optional eagerlyp)
+  "Show multiple doc strings at once after waiting for all.
+Meant as a value for `eldoc-documentation-strategy'."
+  (run-hook-wrapped 'eldoc-documentation-functions
+                    (lambda (f)
+                      (let* ((callback (eldoc--make-callback
+                                        (if eagerlyp :eager :patient)))
+                             (str (funcall f callback)))
+                        (if (or (null str) (stringp str)) (funcall callback str))
+                        nil)))
+  t)
+
+(defun eldoc-documentation-compose-eagerly ()
+  "Show multiple doc strings at once as soon as possible.
+Meant as a value for `eldoc-documentation-strategy'."
+  (eldoc-documentation-compose 'eagerlyp))
+
+(defun eldoc-documentation-enthusiast ()
+  "Show most important doc string produced so far.
+Meant as a value for `eldoc-documentation-strategy'."
+  (run-hook-wrapped 'eldoc-documentation-functions
+                    (lambda (f)
+                      (let* ((callback (eldoc--make-callback :enthusiast))
+                             (str (funcall f callback)))
+                        (if (stringp str) (funcall callback str))
+                        nil))))
+
+(define-obsolete-variable-alias 'eldoc-documentation-function
+  'eldoc-documentation-strategy "1.1.0")
+
+(defcustom eldoc-documentation-strategy #'eldoc-documentation-default
+  "How to collect and organize results of `eldoc-documentation-functions'.
+
+This variable controls how `eldoc-documentation-functions', which
+specifies the sources of documentation, is queried and how its
+results are organized before being displayed to the user.  The
+following values are allowed:
+
+- `eldoc-documentation-default': queries the special hook for the
+  first function that produces a doc string value and displays
+  only that one;
+
+- `eldoc-documentation-compose': queries the special hook for all
+  functions that produce doc strings and displays all of them
+  together as soon as all are ready, preserving the relative
+  order of doc strings as specified by the order of functions in
+  the hook;
+
+- `eldoc-documentation-compose-eagerly': queries the special hook
+  for all functions that produce doc strings and displays as many
+  as possible, as soon as possible, preserving the relative order
+  of doc strings;
+
+- `eldoc-documentation-enthusiast': queries the special hook for
+  all functions the produce doc strings but displays only the
+  most important one at any given time.  A function appearing
+  first in the special hook is considered more important.
+
+This variable can also be set to a function of no args that
+allows for some or all of the special hook
+`eldoc-documentation-functions' to be run.  It should return nil,
+if no documentation is to be displayed at all, a string value
+with the documentation to display, or any other non-nil value in
+case the special hook functions undertake to display the
+documentation themselves."
   :link '(info-link "(emacs) Lisp Doc")
   :type '(radio (function-item eldoc-documentation-default)
                 (function-item eldoc-documentation-compose)
+                (function-item eldoc-documentation-compose-eagerly)
+                (function-item eldoc-documentation-enthusiast)
                 (function :tag "Other function"))
   :version "28.1")
 
 (defun eldoc--supported-p ()
   "Non-nil if an ElDoc function is set for this buffer."
-  (and (not (memq eldoc-documentation-function '(nil ignore)))
+  (and (not (memq eldoc-documentation-strategy '(nil ignore)))
        (or eldoc-documentation-functions
            ;; The old API had major modes set `eldoc-documentation-function'
            ;; to provide eldoc support.  It's impossible now to determine
-           ;; reliably whether the `eldoc-documentation-function' provides
+           ;; reliably whether the `eldoc-documentation-strategy' provides
            ;; eldoc support (as in the old API) or whether it just provides
            ;; a way to combine the results of the
            ;; `eldoc-documentation-functions' (as in the new API).
            ;; But at least if it's set buffer-locally it's a good hint that
            ;; there's some eldoc support in the current buffer.
-           (local-variable-p 'eldoc-documentation-function))))
+           (local-variable-p 'eldoc-documentation-strategy))))
+
+(defvar eldoc--enthusiasm-curbing-timer nil
+  "Timer used by `eldoc-documentation-enthusiast' to avoid blinking.")
 
 (defun eldoc-print-current-symbol-info ()
-  "Print the text produced by `eldoc-documentation-function'."
-  ;; This is run from post-command-hook or some idle timer thing,
-  ;; so we need to be careful that errors aren't ignored.
-  (with-demoted-errors "eldoc error: %s"
-    (if (not (eldoc-display-message-p))
-        ;; Erase the last message if we won't display a new one.
-        (when eldoc-last-message
-          (eldoc-message nil))
-      (let ((non-essential t))
-        ;; Only keep looking for the info as long as the user hasn't
-        ;; requested our attention.  This also locally disables inhibit-quit.
-        (while-no-input
-          (eldoc-message (funcall eldoc-documentation-function)))))))
+  "Document thing at point."
+  (if (not (eldoc-display-message-p))
+      ;; Erase the last message if we won't display a new one.
+      (when eldoc-last-message
+        (eldoc--message nil))
+    (let ((non-essential t))
+      ;; Only keep looking for the info as long as the user hasn't
+      ;; requested our attention.  This also locally disables inhibit-quit.
+      (while-no-input
+        (let* ((howmany 0) (want 0) (docs-registered '()))
+          (cl-labels
+              ((register-doc (pos string plist)
+                (when (and string (> (length string) 0))
+                  (push (cons pos (cons string plist)) docs-registered)))
+               (display-doc ()
+                (eldoc--handle-docs
+                 (mapcar #'cdr
+                         (setq docs-registered
+                               (sort docs-registered
+                                     (lambda (a b) (< (car a) (car b))))))))
+               (make-callback (method)
+                (let ((pos (prog1 howmany (cl-incf howmany))))
+                  (cl-ecase method
+                    (:enthusiast
+                     (lambda (string &rest plist)
+                       (when (and string (cl-loop for (p) in docs-registered
+                                                  never (< p pos)))
+                         (setq docs-registered '())
+                         (register-doc pos string plist)
+                         (when (and (timerp eldoc--enthusiasm-curbing-timer)
+                                    (memq eldoc--enthusiasm-curbing-timer
+                                          timer-list))
+                           (cancel-timer eldoc--enthusiasm-curbing-timer))
+                         (setq eldoc--enthusiasm-curbing-timer
+                               (run-at-time (unless (zerop pos) 0.3)
+                                            nil #'display-doc)))
+                       t))
+                    (:patient
+                     (cl-incf want)
+                     (lambda (string &rest plist)
+                       (register-doc pos string plist)
+                       (when (zerop (cl-decf want)) (display-doc))
+                       t))
+                    (:eager
+                     (lambda (string &rest plist)
+                       (register-doc pos string plist)
+                       (display-doc)
+                       t))))))
+            (let* ((eldoc--make-callback #'make-callback)
+                   (res (funcall eldoc-documentation-strategy)))
+              (cond (;; old protocol: got string, output immediately
+                 (stringp res) (register-doc 0 res nil) (display-doc))
+                (;; old protocol: got nil, clear the echo area
+                 (null res) (eldoc--message nil))
+                (;; got something else, trust callback will be called
+                 t)))))))))
 
 ;; If the entire line cannot fit in the echo area, the symbol name may be
 ;; truncated or eliminated entirely from the output to make room for the
index cf7118f2089bda509a6259ba40ead50a455df1d0..38eca77e260107454637cfc993862ae01cbfc427 100644 (file)
@@ -515,7 +515,7 @@ Ask the user for confirmation."
       (message "Current address is %d/0x%08x" hexl-address hexl-address))
     hexl-address))
 
-(defun hexl-print-current-point-info ()
+(defun hexl-print-current-point-info (&rest _ignored)
   "Return current hexl-address in string.
 This function is intended to be used as eldoc callback."
   (let ((addr (hexl-current-address)))
index f25b3cb9e2bb5b934bfca89bb2096f1d75f1c354..9a6d81ce064f549a61fdc545176055e71c202831 100644 (file)
@@ -1294,7 +1294,7 @@ Calls `cfengine-cf-promises' with \"-s json\"."
                           'symbols))
         syntax)))
 
-(defun cfengine3-documentation-function ()
+(defun cfengine3-documentation-function (&rest _ignored)
   "Document CFengine 3 functions around point.
 Intended as the value of `eldoc-documentation-function', which see.
 Use it by enabling `eldoc-mode'."
index 8812c49ba4397ce7c171c1e4b3ae1b450fc4f1be..5e32b25fd306bc16d1db1d5a96ab2eb5594d135e 100644 (file)
@@ -1403,8 +1403,10 @@ which see."
       or argument string for functions.
   2 - `function' if function args, `variable' if variable documentation.")
 
-(defun elisp-eldoc-documentation-function ()
-  "`eldoc-documentation-function' (which see) for Emacs Lisp."
+(defun elisp-eldoc-documentation-function (_ignored &rest _also-ignored)
+  "Contextual documentation function for Emacs Lisp.
+Intended to be placed in `eldoc-documentation-functions' (which
+see)."
   (let ((current-symbol (elisp--current-symbol))
        (current-fnsym  (elisp--fnsym-in-current-sexp)))
     (cond ((null current-fnsym)
index 352c1810d1fa2f69162a6ede64e6d13d82f918de..2cf305c404146f5fc4618df012a6c3e04b0f81ca 100644 (file)
@@ -1639,8 +1639,8 @@ code line."
                   (nreverse result)))))
   (cdr octave-eldoc-cache))
 
-(defun octave-eldoc-function ()
-  "A function for `eldoc-documentation-function' (which see)."
+(defun octave-eldoc-function (&rest _ignored)
+  "A function for `eldoc-documentation-functions' (which see)."
   (when (inferior-octave-process-live-p)
     (let* ((ppss (syntax-ppss))
            (paren-pos (cadr ppss))
index 22248f04402439de1c8710ac12cfa9989b9757cd..36b62bfe96ef6b2a79c39c07fdd33874f164175c 100644 (file)
@@ -4573,7 +4573,7 @@ returns will be used.  If not FORCE-PROCESS is passed what
   :type 'boolean
   :version "25.1")
 
-(defun python-eldoc-function ()
+(defun python-eldoc-function (&rest _ignored)
   "`eldoc-documentation-function' for Python.
 For this to work as best as possible you should call
 `python-shell-send-buffer' from time to time so context in