[Top][All Lists]

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

scratch/eldoc-async cde8a6a 1/6: Better handle asynchronously produced e

From: João Távora
Subject: scratch/eldoc-async cde8a6a 1/6: Better handle asynchronously produced eldoc docstrings
Date: Wed, 3 Jun 2020 13:54:06 -0400 (EDT)

branch: scratch/eldoc-async
commit cde8a6ab981fc67789b9cc036bd783201c9e5ae5
Author: João Távora <joaotavora@gmail.com>
Commit: João Távora <joaotavora@gmail.com>

    Better handle asynchronously produced eldoc docstrings
    No longer do clients of eldoc need to call eldoc-message (an internal
    function) directly.  They may return any non-nil, non-string value and
    call a callback afterwards.  This enables eldoc.el to exert control
    over how (and crucially also when) to display the docstrings to the
    * 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--handle-multiline): 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.
 lisp/emacs-lisp/eldoc.el     | 109 ++++++++++++++++++++++++++++++++-----------
 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 +-
 6 files changed, 91 insertions(+), 34 deletions(-)

diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el
index ef5dbf8..4c05ef9 100644
--- a/lisp/emacs-lisp/eldoc.el
+++ b/lisp/emacs-lisp/eldoc.el
@@ -338,43 +338,70 @@ Also store it in `eldoc-last-message' and return that 
 (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.
-Major modes should modify this hook locally, for example:
+  "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
+Each hook function should accept at least one argument CALLBACK
+and decide whether to display a doc short string about the
+context around point.  If the decision and the doc string can be
+produced quickly, the hook function can ignore CALLBACK and
+immediately return the doc string, or nil if there's no doc
+appropriate for the context.  Otherwise, if the computation of
+said docstring is expensive or can't be performed directly, the
+hook function should return a non-nil, non-string value and then
+arrange for CALLBACK to be called at a later time.
+That call is expected to pass CALLBACK a single argument
+DOCSTRING followed by an optional list of keyword-value pairs of
+the form (:KEY VALUE :KEY2 VALUE2...).  KEY can be:
+* `:hint', whereby the corresponding VALUE should be a short
+  string designating the thing being reported on by the hook
+  function.
+Note that this hook is only in effect if the value of
+`eldoc-documentation-function' (notice the singular) is bound to
+one of its pre-set values.  Major modes should modify this hook
+locally, for example:
   (add-hook \\='eldoc-documentation-functions #\\='foo-mode-eldoc nil t)
 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.")
+(defun eldoc--handle-multiline (res)
+  "Helper for handling a bit of `eldoc-echo-area-use-multiline-p'."
+  (if eldoc-echo-area-use-multiline-p res
+    (truncate-string-to-width
+     res (1- (window-width (minibuffer-window))))))
+;; this variable should be unbound, but that confuses
+;; `describe-symbol' for some reason.
+(defvar eldoc--make-callback nil
+  "Dynamically bound to a nullary function that returns a callback.
+Besides producing the callback that is passed to
+`eldoc-documentation-functions', this also remembers the
+callbacks relative order in the queue of callbacks waiting to be
+called. ")
 (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 
-    (when res
-      (if eldoc-echo-area-use-multiline-p res
-        (truncate-string-to-width
-         res (1- (window-width (minibuffer-window))))))))
+  (run-hook-with-args-until-success 'eldoc-documentation-functions
+   (funcall eldoc--make-callback)))
 (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))))))))
+  (run-hook-wrapped 'eldoc-documentation-functions
+                    (lambda (f)
+                      (let* ((callback (funcall eldoc--make-callback))
+                             (str (funcall f callback)))
+                        (if (stringp str) (funcall callback str))
+                        nil)))
+  t)
 (defcustom eldoc-documentation-function #'eldoc-documentation-default
   "Function to call to return doc string.
@@ -417,11 +444,39 @@ effect."
         ;; Erase the last message if we won't display a new one.
         (when eldoc-last-message
           (eldoc-message nil))
-      (let ((non-essential t))
+      (let ((non-essential t)
+            (buffer (current-buffer)))
         ;; Only keep looking for the info as long as the user hasn't
         ;; requested our attention.  This also locally disables inhibit-quit.
-          (eldoc-message (funcall eldoc-documentation-function)))))))
+          (let* (;; `want' and `received' keep track of how many
+                 ;; docstrings we expect from the clients.
+                 (pos 0) (want 0) (received '())
+                 (receive-doc
+                  (lambda (_pos string _plist)
+                    (with-current-buffer buffer
+                      (when (and string (> (length string) 0))
+                        (push string received))
+                      (setq want (1- want))
+                      (when (zerop want)
+                        (eldoc-message
+                         (eldoc--handle-multiline
+                          (mapconcat #'identity
+                                     (nreverse received)
+                                     ",")))))))
+                 (eldoc--make-callback
+                  (lambda ()
+                    (setq want (1+ want))
+                    (let ((pos (setq pos (1+ pos))))
+                      (lambda (string &rest plist)
+                        (funcall receive-doc pos string plist)))))
+                 (res (funcall eldoc-documentation-function)))
+            (cond (;; old protocol: got string, output immediately
+                   (stringp res) (setq want 1) (funcall receive-doc 0 res nil))
+                  (;; 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))
-(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\"."
-(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
   (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 1ca9f01..404a67b 100644
--- a/lisp/progmodes/python.el
+++ b/lisp/progmodes/python.el
@@ -4571,7 +4571,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]