emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

master a7a53f0 1/8: Better handle asynchronous Eldoc sources


From: João Távora
Subject: master a7a53f0 1/8: Better handle asynchronous Eldoc sources
Date: Wed, 8 Jul 2020 06:26:47 -0400 (EDT)

branch: master
commit a7a53f0d79e0012c909e87911c4f85ad12bb78b4
Author: João Távora <joaotavora@gmail.com>
Commit: João Távora <joaotavora@gmail.com>

    Better handle asynchronous Eldoc sources
    
    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      |  10 +-
 doc/lispref/modes.texi       |   7 +-
 etc/NEWS                     |   8 +-
 lisp/emacs-lisp/eldoc.el     | 402 ++++++++++++++++++++++++++++++++++---------
 lisp/hexl.el                 |   2 +-
 lisp/progmodes/cfengine.el   |   2 +-
 lisp/progmodes/elisp-mode.el |   6 +-
 lisp/progmodes/octave.el     |   4 +-
 lisp/progmodes/python.el     |   2 +-
 9 files changed, 340 insertions(+), 103 deletions(-)

diff --git a/doc/emacs/programs.texi b/doc/emacs/programs.texi
index 865a3a6..2757c84 100644
--- a/doc/emacs/programs.texi
+++ b/doc/emacs/programs.texi
@@ -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
diff --git a/doc/lispref/modes.texi b/doc/lispref/modes.texi
index eaee56f..17e9607 100644
--- a/doc/lispref/modes.texi
+++ b/doc/lispref/modes.texi
@@ -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
diff --git a/etc/NEWS b/etc/NEWS
index fc5c215..f5b9e5e 100644
--- 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
 
diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el
index ef5dbf8..bcf3a84 100644
--- a/lisp/emacs-lisp/eldoc.el
+++ b/lisp/emacs-lisp/eldoc.el
@@ -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."
 
 
 (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
diff --git a/lisp/hexl.el b/lisp/hexl.el
index cf7118f..38eca77 100644
--- a/lisp/hexl.el
+++ b/lisp/hexl.el
@@ -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)))
diff --git a/lisp/progmodes/cfengine.el b/lisp/progmodes/cfengine.el
index f25b3cb..9a6d81c 100644
--- a/lisp/progmodes/cfengine.el
+++ b/lisp/progmodes/cfengine.el
@@ -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'."
diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el
index 8812c49..5e32b25 100644
--- a/lisp/progmodes/elisp-mode.el
+++ b/lisp/progmodes/elisp-mode.el
@@ -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)
diff --git a/lisp/progmodes/octave.el b/lisp/progmodes/octave.el
index 352c181..2cf305c 100644
--- a/lisp/progmodes/octave.el
+++ b/lisp/progmodes/octave.el
@@ -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))
diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el
index 22248f0..36b62bf 100644
--- a/lisp/progmodes/python.el
+++ b/lisp/progmodes/python.el
@@ -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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]