]> git.eshelyaron.com Git - emacs.git/commitdiff
Introduce eldoc-display-functions
authorJoão Távora <joaotavora@gmail.com>
Sun, 6 Sep 2020 14:37:02 +0000 (15:37 +0100)
committerJoão Távora <joaotavora@gmail.com>
Sat, 24 Oct 2020 17:02:28 +0000 (18:02 +0100)
See bug#43609.

* lisp/emacs-lisp/eldoc.el (eldoc--request-state): Add comment.
(eldoc--last-request-state): No longer buffer-local.
(eldoc--request-docs-p): Delete.
(eldoc-display-functions): New user variable.
(eldoc--doc-buffer-docs): New variable.
(eldoc-display-message-p): Rework.
(eldoc--format-doc-buffer): Rework from eldoc--handle-docs.
(eldoc-display-in-echo-area, eldoc-display-in-buffer): New
user-visible function.
(eldoc--invoke-strategy): Take INTERACTIVE arg.
Invoke eldoc-display-in-buffer
(eldoc-print-current-symbol-info): Simplify.
(Version): Bump to 1.11.0

* etc/NEWS: Mention eldoc-display-functions.

etc/NEWS
lisp/emacs-lisp/eldoc.el

index a405c0dd3d7d146716e803aab8b26f66988142cb..8aa27fd651e597b3f2a022cd930864550eadcd24 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -650,6 +650,13 @@ may arrange for it to be produced asynchronously.  The results of all
 doc string functions are accessible to the user through the user
 option 'eldoc-documentation-strategy'.
 
+*** New hook 'eldoc-display-functions'.
+This hook is intended to be used for displaying doc string.  The
+functions receive the docstrings composed according to
+`eldoc-documentation-strategy' and are tasked with displaying it to
+the user.  Examples of such functions would use the echo area, a
+separate buffer or a tooltip.
+
 +++
 *** New user option 'eldoc-documentation-strategy'.
 The built-in choices available for this user option let users compose
index 9e38e5908edac630b69197dd57ed43061ce5af22..1b180f26c58d8d7ac5b6313278de864d470c8017 100644 (file)
@@ -5,7 +5,7 @@
 ;; Author: Noah Friedman <friedman@splode.com>
 ;; Keywords: extensions
 ;; Created: 1995-10-06
-;; Version: 1.10.0
+;; Version: 1.11.0
 ;; Package-Requires: ((emacs "26.3"))
 
 ;; This is a GNU ELPA :core package.  Avoid functionality that is not
@@ -350,40 +350,26 @@ Also store it in `eldoc-last-message' and return that value."
          ;; for us, but do note that the last-message will be gone.
          (setq eldoc-last-message nil))))
 
-(defvar-local eldoc--last-request-state nil
+;; The point of `eldoc--request-state' is not to over-request, which
+;; can happen if the idle timer is restarted on execution of command
+;; which is guaranteed not to change the conditions that warrant a new
+;; request for documentation.
+(defvar eldoc--last-request-state nil
   "Tuple containing information about last ElDoc request.")
 (defun eldoc--request-state ()
   "Compute information to store in `eldoc--last-request-state'."
   (list (current-buffer) (buffer-modified-tick) (point)))
 
 (defun eldoc-display-message-p ()
-  (eldoc--request-docs-p (eldoc--request-state)))
+  "Tell if ElDoc can use the echo area."
+  (and (eldoc-display-message-no-interference-p)
+       (not this-command)
+       (eldoc--message-command-p last-command)))
+
 (make-obsolete 'eldoc-display-message-p
                "Use `eldoc-documentation-functions' instead."
                "eldoc-1.6.0")
 
-(defun eldoc--request-docs-p (request-state)
-  "Return non-nil when it is appropriate to request docs.
-REQUEST-STATE is a candidate for `eldoc--last-request-state'"
-  (and
-   ;; FIXME: The original idea behind this function is to protect the
-   ;; Echo area from ElDoc interference, but since that is only one of
-   ;; the possible outlets of ElDoc, this must soon be reworked.
-   (eldoc-display-message-no-interference-p)
-   (not (and eldoc--doc-buffer
-             (get-buffer-window eldoc--doc-buffer)
-             (equal request-state
-                    (with-current-buffer
-                        eldoc--doc-buffer
-                      eldoc--last-request-state))))
-   ;; If this-command is non-nil while running via an idle
-   ;; timer, we're still in the middle of executing a command,
-   ;; e.g. a query-replace where it would be annoying to
-   ;; overwrite the echo area.
-   (not this-command)
-   (eldoc--message-command-p last-command)))
-
-
 ;; Check various conditions about the current environment that might make
 ;; it undesirable to print eldoc messages right this instant.
 (defun eldoc-display-message-no-interference-p ()
@@ -416,43 +402,112 @@ about the context around point.
 
 To call the CALLBACK function, the hook function must pass it an
 obligatory argument DOCSTRING, a string containing the
-documentation, followed by an optional list of keyword-value
-pairs of the form (:KEY VALUE :KEY2 VALUE2...).  KEY can be:
-
-* `:thing', VALUE is a short string or symbol designating what is
-  being reported on.  The documentation display engine can elect
-  to remove this information depending on space constraints;
-
-* `:face', VALUE is a symbol designating a face to use when
-  displaying `:thing''s value.
-
-Major modes should modify this hook locally, for example:
+documentation, followed by an optional list of arbitrary
+keyword-value pairs of the form (:KEY VALUE :KEY2 VALUE2...).
+The information contained in these pairs is understood by members
+of `eldoc-display-functions', allowing the
+documentation-producing backend to cooperate with specific
+documentation-displaying frontends.  For example, KEY can be:
+
+* `:thing', VALUE being a short string or symbol designating what
+  is being reported on.  It can, for example be the name of the
+  function whose signature is being documented, or the name of
+  the variable whose docstring is being documented.
+  `eldoc-display-in-echo-area', a member of
+  `eldoc-display-functions', sometimes omits this information
+  depending on space constraints;
+
+* `:face', VALUE being a symbol designating a face which both
+  `eldoc-display-in-echo-area' and `eldoc-display-in-buffer' will
+  use when displaying `:thing''s value.
+
+Finally, 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.")
 
+(defvar eldoc-display-functions
+  '(eldoc-display-in-echo-area eldoc-display-in-buffer)
+  "Hook of functions tasked with displaying ElDoc results.
+Each function is passed two arguments: DOCS and INTERACTIVE. DOCS
+is a list (DOC ...) where DOC looks like (STRING :KEY VALUE :KEY2
+VALUE2 ...).  STRING is a string containing the documentation's
+text and the remainder of DOC is an optional list of
+keyword-value pairs denoting additional properties of that
+documentation.  For commonly recognized properties, see
+`eldoc-documentation-functions'.
+
+INTERACTIVE says if the request to display doc strings came
+directly from the user or from ElDoc's automatic mechanisms'.")
+
 (defvar eldoc--doc-buffer nil "Buffer displaying latest ElDoc-produced docs.")
 
+(defvar eldoc--doc-buffer-docs nil "Documentation items in `eldoc--doc-buffer'.")
+
 (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.
-Honor most of `eldoc-echo-area-use-multiline-p'."
-  ;; If there's nothing to report clear the echo area, but don't erase
-  ;; the last *eldoc* buffer.
-  (if (null docs) (eldoc--message nil)
+  "Display ElDoc documentation buffer.
+This holds the results of the last documentation request."
+  (unless (buffer-live-p eldoc--doc-buffer)
+    (setq eldoc--doc-buffer (get-buffer-create "*eldoc*")))
+  (when interactive
+    (display-buffer eldoc--doc-buffer)))
+
+(defun eldoc--format-doc-buffer (docs)
+  "Ensure DOCS are displayed in an *eldoc* buffer."
+  (interactive (list t))
+  (eldoc-doc-buffer) ;; ensure buffer exists
+  (with-current-buffer eldoc--doc-buffer
+    (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"))
+        ;; Maybe rename the buffer.
+        (rename-buffer (if things-reported-on
+                           (format "*eldoc for %s*"
+                                   (mapconcat (lambda (s) (format "%s" s))
+                                              things-reported-on
+                                              ", "))
+                         "*eldoc*")))))
+  eldoc--doc-buffer)
+
+(defun eldoc-display-in-echo-area (docs _interactive)
+  "Display DOCS in echo area.
+Honor `eldoc-echo-area-use-multiline-p' and
+`eldoc-prefer-doc-buffer'."
+  (cond
+   (;; Check if he wave permission to mess with echo area at all.  For
+    ;; example, if this-command is non-nil while running via an idle
+    ;; timer, we're still in the middle of executing a command, e.g. a
+    ;; query-replace where it would be annoying to overwrite the echo
+    ;; area.
+    (or
+     (not (eldoc-display-message-no-interference-p))
+     this-command
+     (not (eldoc--message-command-p last-command))))
+   (;; If we do but nothing to report, clear the echo area.
+    (null docs)
+    (eldoc--message nil))
+   (t
+    ;; Otherwise, establish some parameters.
     (let*
-        ;; Otherwise, establish some parameters.
         ((width (1- (window-width (minibuffer-window))))
          (val (if (and (symbolp eldoc-echo-area-use-multiline-p)
                        eldoc-echo-area-use-multiline-p)
@@ -462,43 +517,12 @@ Honor most of `eldoc-echo-area-use-multiline-p'."
                       (float (truncate (* (frame-height) val)))
                       (integer val)
                       (t 1)))
-         (things-reported-on)
-         (request eldoc--last-request-state)
          single-doc single-doc-sym)
-      ;; Then, compose the contents of the `*eldoc*' buffer.
-      (with-current-buffer (eldoc-doc-buffer)
-        ;; Set doc-buffer's `eldoc--last-request-state', too
-        (setq eldoc--last-request-state request)
-        (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.  I'm pretty sure nicer
-      ;; strategies can be used here, probably by splitting this
-      ;; function into some `eldoc-display-functions' special hook.
       (let ((echo-area-message
              (cond
-              (;; We handle the `truncate-sym-name-if-fit' special
-               ;; case first, by checking if for a lot of special
-               ;; conditions.
+              (;; To output to the echo area,We handle the
+               ;; `truncate-sym-name-if-fit' special case first, by
+               ;; checking if for a lot of special conditions.
                (and
                 (eq 'truncate-sym-name-if-fit eldoc-echo-area-use-multiline-p)
                 (null (cdr docs))
@@ -514,7 +538,11 @@ Honor most of `eldoc-echo-area-use-multiline-p'."
                ;; display that, we have one extra line to use.
                (unless eldoc-display-truncation-message
                  (setq available (1+ available)))
-               (with-current-buffer (eldoc-doc-buffer)
+               ;; Else we format the *eldoc* buffer, then use some of
+               ;; its contents top section.  I'm pretty sure smarter
+               ;; strategies can be used here that don't necessarily
+               ;; involve composing that entire buffer.
+               (with-current-buffer (eldoc--format-doc-buffer docs)
                  (cl-loop
                   initially
                   (goto-char (point-min))
@@ -543,11 +571,18 @@ Honor most of `eldoc-echo-area-use-multiline-p'."
                          "..."))))))))
               ((= available 1)
                ;; Truncate "brutally." ; FIXME: use `eldoc-prefer-doc-buffer' too?
-               (with-current-buffer (eldoc-doc-buffer)
+               (with-current-buffer (eldoc--format-doc-buffer docs)
                  (truncate-string-to-width
                   (buffer-substring (goto-char (point-min)) (line-end-position 1)) width))))))
         (when echo-area-message
-          (eldoc--message echo-area-message))))))
+          (eldoc--message echo-area-message)))))))
+
+(defun eldoc-display-in-buffer (docs interactive)
+  "Display DOCS in a dedicated buffer.
+If INTERACTIVE is t, also display the buffer."
+  (let ((buf (eldoc--format-doc-buffer docs)))
+    (when interactive
+      (display-buffer buf))))
 
 (defun eldoc-documentation-default ()
   "Show first doc string for item at point.
@@ -709,19 +744,29 @@ have the following values:
   strings so far, as soon as possible."
   (funcall eldoc--make-callback method))
 
-(defun eldoc--invoke-strategy ()
+(defun eldoc--invoke-strategy (interactive)
   "Invoke `eldoc-documentation-strategy' function.
 
+If INTERACTIVE is non-nil, the request came directly from a user
+command, otherwise it came from ElDoc's idle
+timer, `eldoc-timer'.
+
 That function's job is to run the `eldoc-documentation-functions'
 special hook, using the `run-hook' family of functions.  ElDoc's
 built-in strategy functions play along with the
-`eldoc--make-callback' protocol, using it to produce callback to
-feed to the functgions of `eldoc-documentation-functions'.
-
-Other third-party strategy functions do not use
-`eldoc--make-callback'.  They must find some alternate way to
-produce callbacks to feed to `eldoc-documentation-function' and
-should endeavour to display the docstrings eventually produced."
+`eldoc--make-callback' protocol, using it to produce a callback
+argument to feed the functions that the user places in
+`eldoc-documentation-functions'.  Whenever the strategy
+determines it has information to display to the user, this
+function passes responsibility to the functions in
+`eldoc-display-functions'.
+
+Other third-party values of `eldoc-documentation-strategy' should
+not use `eldoc--make-callback'.  They must find some alternate
+way to produce callbacks to feed to
+`eldoc-documentation-function' and should endeavour to display
+the docstrings eventually produced, using
+`eldoc-display-functions'."
   (let* (;; How many callbacks have been created by the strategy
          ;; function and passed to elements of
          ;; `eldoc-documentation-functions'.
@@ -739,11 +784,12 @@ should endeavour to display the docstrings eventually produced."
             (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))))))))
+          (run-hook-with-args
+           'eldoc-display-functions (mapcar #'cdr
+                                            (setq docs-registered
+                                                  (sort docs-registered
+                                                        (lambda (a b) (< (car a) (car b))))))
+           interactive))
          (make-callback
           (method)
           (let ((pos (prog1 howmany (cl-incf howmany))))
@@ -786,22 +832,23 @@ should endeavour to display the docstrings eventually produced."
 (defun eldoc-print-current-symbol-info (&optional interactive)
   "Document thing at point."
   (interactive '(t))
-  (let ((token (eldoc--request-state)))
+  (let (token)
     (cond (interactive
-           (eldoc--invoke-strategy))
-          ((not (eldoc--request-docs-p token))
-           ;; Erase the last message if we won't display a new one.
-           (when eldoc-last-message
-             (eldoc--message nil)))
-          (t
+           (eldoc--invoke-strategy t))
+          ((not (equal (setq token (eldoc--request-state))
+                       eldoc--last-request-state))
            (let ((non-essential t))
              (setq eldoc--last-request-state token)
              ;; 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--invoke-strategy)))))))
+               (eldoc--invoke-strategy nil)))))))
 
+\f
+;; This section only affects ElDoc output to the echo area, as in
+;; `eldoc-display-in-echo-area'.
+;;
 ;; When point is in a sexp, the function args are not reprinted in the echo
 ;; area after every possible interactive command because some of them print
 ;; their own messages in the echo area; the eldoc functions would instantly
@@ -833,7 +880,6 @@ should endeavour to display the docstrings eventually produced."
     (apply #'eldoc-remove-command
            (all-completions name eldoc-message-commands))))
 
-\f
 ;; Prime the command list.
 (eldoc-add-command-completions
  "back-to-indentation"