From a7a53f0d79e0012c909e87911c4f85ad12bb78b4 Mon Sep 17 00:00:00 2001 From: João Távora Date: Mon, 25 May 2020 16:39:40 +0100 Subject: 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 and Dmitry Gutov . 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. --- lisp/emacs-lisp/eldoc.el | 402 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 316 insertions(+), 86 deletions(-) (limited to 'lisp/emacs-lisp/eldoc.el') diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el index ef5dbf8103f..bcf3a84d319 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 -- cgit v1.2.3 From 22cae4f509a950232ea2507ea549159f4b417331 Mon Sep 17 00:00:00 2001 From: João Távora Date: Mon, 15 Jun 2020 13:55:37 +0100 Subject: New M-x eldoc for on-demand and interactive documentation requests The function eldoc is just an alias for eldoc-print-current-symbol-info, which is made interactive. * lisp/emacs-lisp/eldoc.el (eldoc-print-current-symbol-info): Now an interactive function. (eldoc): Alias to eldoc-print-current-symbol-info. --- lisp/emacs-lisp/eldoc.el | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lisp/emacs-lisp/eldoc.el') diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el index bcf3a84d319..7a705001833 100644 --- a/lisp/emacs-lisp/eldoc.el +++ b/lisp/emacs-lisp/eldoc.el @@ -595,8 +595,11 @@ documentation themselves." (defvar eldoc--enthusiasm-curbing-timer nil "Timer used by `eldoc-documentation-enthusiast' to avoid blinking.") +(defalias 'eldoc #'eldoc-print-current-symbol-info) + (defun eldoc-print-current-symbol-info () "Document thing at point." + (interactive) (if (not (eldoc-display-message-p)) ;; Erase the last message if we won't display a new one. (when eldoc-last-message -- cgit v1.2.3 From a9bd5060047068165f24fb6a08248c189bab346d Mon Sep 17 00:00:00 2001 From: João Távora Date: Wed, 3 Jun 2020 18:47:24 +0100 Subject: * lisp/emacs-lisp/eldoc.el (Version): Bump to 1.1.0 --- lisp/emacs-lisp/eldoc.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lisp/emacs-lisp/eldoc.el') diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el index 7a705001833..34f37f76d48 100644 --- a/lisp/emacs-lisp/eldoc.el +++ b/lisp/emacs-lisp/eldoc.el @@ -5,7 +5,7 @@ ;; Author: Noah Friedman ;; Keywords: extensions ;; Created: 1995-10-06 -;; Version: 1.0.0 +;; Version: 1.1.0 ;; Package-Requires: ((emacs "26.3")) ;; This is a GNU ELPA :core package. Avoid functionality that is not -- cgit v1.2.3 From 1203626f472b0d99d2746f5999711137c0c1fd0c Mon Sep 17 00:00:00 2001 From: João Távora Date: Sat, 6 Jun 2020 14:04:48 +0100 Subject: Make more parts of Emacs use new Eldoc capabilities Elisp-mode was doing a lot of work that can now be delegated to Eldoc. Flymake uses the new Eldoc functionality, too, installing a global documentation function that may report on diagnostics under point. CEDET's grammar.el was left as the only user of an Eldoc-internal function. That function was moved to grammar.el. That file is still, somewhat reprehensibly, using an internal function of elisp-mode.el, but this was left unchanged. In other situations, eldoc-documentation-functions is used or recommended. The only other places where the obsolete eldoc-documentation-function is still used is in libraries which are presumably meant to remain compatible with previous Emacs versions. * lisp/progmodes/elisp-mode.el (elisp-eldoc-funcall) (elisp-eldoc-var-docstring): New functions. (emacs-lisp-mode): Put two elements in eldoc-documentation-functions. * lisp/emacs-lisp/eldoc.el (eldoc--eval-expression-setup): Setup new Elisp eldoc-documentation-functions. * lisp/progmodes/flymake.el (flymake-mode): Use flymake-eldoc-function. (flymake-eldoc-function): New function. (Package-Requires): Require eldoc 1.1.0 * lisp/descr-text.el (describe-char-eldoc): Recommend eldoc-documentation-functions. * lisp/progmodes/cfengine.el (cfengine3-documentation-function): Recommend eldoc-documentation-functions * lisp/progmodes/octave.el (inferior-octave-mode): Use eldoc-documentation-functions. * lisp/cedet/semantic/grammar.el (semantic--docstring-format-sym-doc): New function. (semantic-grammar-eldoc-get-macro-docstring): Adjust. --- lisp/cedet/semantic/grammar.el | 45 ++++++++++++++++++++++++++---- lisp/descr-text.el | 6 ++-- lisp/emacs-lisp/eldoc.el | 38 +++---------------------- lisp/progmodes/cfengine.el | 4 +-- lisp/progmodes/elisp-mode.el | 63 ++++++++++++++++++++---------------------- lisp/progmodes/flymake.el | 12 +++++++- lisp/progmodes/octave.el | 2 +- 7 files changed, 92 insertions(+), 78 deletions(-) (limited to 'lisp/emacs-lisp/eldoc.el') diff --git a/lisp/cedet/semantic/grammar.el b/lisp/cedet/semantic/grammar.el index 2c3b24b9b16..1ed18339a72 100644 --- a/lisp/cedet/semantic/grammar.el +++ b/lisp/cedet/semantic/grammar.el @@ -1663,6 +1663,42 @@ Select the buffer containing the tag's definition, and move point there." (defvar semantic-grammar-eldoc-last-data (cons nil nil)) +(defun semantic--docstring-format-sym-doc (prefix doc &optional face) + "Combine PREFIX and DOC, and shorten the result to fit in the echo area. + +When PREFIX is a symbol, propertize its symbol name with FACE +before combining it with DOC. If FACE is not provided, just +apply the nil face. + +See also: `eldoc-echo-area-use-multiline-p'." + ;; Hoisted from old `eldoc-docstring-format-sym-doc'. + ;; 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 + ;; description. + (when (symbolp prefix) + (setq prefix (concat (propertize (symbol-name prefix) 'face face) ": "))) + (let* ((ea-multi eldoc-echo-area-use-multiline-p) + ;; Subtract 1 from window width since emacs will not write + ;; any chars to the last column, or in later versions, will + ;; cause a wraparound and resize of the echo area. + (ea-width (1- (window-width (minibuffer-window)))) + (strip (- (+ (length prefix) + (length doc)) + ea-width))) + (cond ((or (<= strip 0) + (eq ea-multi t) + (and ea-multi (> (length doc) ea-width))) + (concat prefix doc)) + ((> (length doc) ea-width) + (substring (format "%s" doc) 0 ea-width)) + ((>= strip (string-match-p ":? *\\'" prefix)) + doc) + (t + ;; Show the end of the partial symbol name, rather + ;; than the beginning, since the former is more likely + ;; to be unique given package namespace conventions. + (concat (substring prefix strip) doc))))) + (defun semantic-grammar-eldoc-get-macro-docstring (macro expander) "Return a one-line docstring for the given grammar MACRO. EXPANDER is the name of the function that expands MACRO." @@ -1681,19 +1717,18 @@ EXPANDER is the name of the function that expands MACRO." (setq doc (eldoc-function-argstring expander)))) (when doc (setq doc - (eldoc-docstring-format-sym-doc + (semantic--docstring-format-sym-doc macro (format "==> %s %s" expander doc) 'default)) (setq semantic-grammar-eldoc-last-data (cons expander doc))) doc)) ((fboundp 'elisp-get-fnsym-args-string) ;; Emacs≥25 - (elisp-get-fnsym-args-string - expander nil - (concat (propertize (symbol-name macro) + (concat (propertize (symbol-name macro) 'face 'font-lock-keyword-face) " ==> " (propertize (symbol-name macro) 'face 'font-lock-function-name-face) - ": "))))) + ": " + (elisp-get-fnsym-args-string expander nil ))))) (define-mode-local-override semantic-idle-summary-current-symbol-info semantic-grammar-mode () diff --git a/lisp/descr-text.el b/lisp/descr-text.el index 1dbbd421489..22cfccb1fb2 100644 --- a/lisp/descr-text.el +++ b/lisp/descr-text.el @@ -929,10 +929,12 @@ Otherwise return a description formatted by of `eldoc-echo-area-use-multiline-p' variable and width of minibuffer window for width limit. -This function is meant to be used as a value of -`eldoc-documentation-function' variable." +This function can be used as a value of +`eldoc-documentation-functions' variable." (let ((ch (following-char))) (when (and (not (zerop ch)) (or (< ch 32) (> ch 127))) + ;; TODO: investigate if the new `eldoc-documentation-functions' + ;; API could significantly improve this. (describe-char-eldoc--format ch (unless (eq eldoc-echo-area-use-multiline-p t) diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el index 34f37f76d48..ab8dd6a73b5 100644 --- a/lisp/emacs-lisp/eldoc.el +++ b/lisp/emacs-lisp/eldoc.el @@ -229,7 +229,10 @@ expression point is on." :lighter eldoc-minor-mode-string ;; Setup `eldoc', similar to `emacs-lisp-mode'. FIXME: Call ;; `emacs-lisp-mode' itself? (add-hook 'eldoc-documentation-functions - #'elisp-eldoc-documentation-function nil t) + #'elisp-eldoc-var-docstring nil t) + (add-hook 'eldoc-documentation-functions + #'elisp-eldoc-funcall nil t) + (setq eldoc-documentation-strategy 'eldoc-documentation-default) (eldoc-mode +1)) ;;;###autoload @@ -656,39 +659,6 @@ documentation themselves." (;; 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 -;; description. -(defun eldoc-docstring-format-sym-doc (prefix doc &optional face) - "Combine PREFIX and DOC, and shorten the result to fit in the echo area. - -When PREFIX is a symbol, propertize its symbol name with FACE -before combining it with DOC. If FACE is not provided, just -apply the nil face. - -See also: `eldoc-echo-area-use-multiline-p'." - (when (symbolp prefix) - (setq prefix (concat (propertize (symbol-name prefix) 'face face) ": "))) - (let* ((ea-multi eldoc-echo-area-use-multiline-p) - ;; Subtract 1 from window width since emacs will not write - ;; any chars to the last column, or in later versions, will - ;; cause a wraparound and resize of the echo area. - (ea-width (1- (window-width (minibuffer-window)))) - (strip (- (+ (length prefix) (length doc)) ea-width))) - (cond ((or (<= strip 0) - (eq ea-multi t) - (and ea-multi (> (length doc) ea-width))) - (concat prefix doc)) - ((> (length doc) ea-width) - (substring (format "%s" doc) 0 ea-width)) - ((>= strip (string-match-p ":? *\\'" prefix)) - doc) - (t - ;; Show the end of the partial symbol name, rather - ;; than the beginning, since the former is more likely - ;; to be unique given package namespace conventions. - (concat (substring prefix strip) doc))))) - ;; 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 diff --git a/lisp/progmodes/cfengine.el b/lisp/progmodes/cfengine.el index 9a6d81ce064..acf70a5ad90 100644 --- a/lisp/progmodes/cfengine.el +++ b/lisp/progmodes/cfengine.el @@ -1296,8 +1296,8 @@ Calls `cfengine-cf-promises' with \"-s json\"." (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'." +Intended as the value of `eldoc-documentation-functions', which +see. Use it by enabling `eldoc-mode'." (let ((fdef (cfengine3--current-function))) (when fdef (cfengine3-format-function-docstring fdef)))) diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el index 5e32b25fd30..6df54111911 100644 --- a/lisp/progmodes/elisp-mode.el +++ b/lisp/progmodes/elisp-mode.el @@ -280,7 +280,9 @@ Blank lines separate paragraphs. Semicolons start comments. electric-pair-text-pairs)) (add-hook 'electric-pair-mode-hook #'emacs-lisp-set-electric-text-pairs)) (add-hook 'eldoc-documentation-functions - #'elisp-eldoc-documentation-function nil t) + #'elisp-eldoc-var-docstring nil t) + (add-hook 'eldoc-documentation-functions + #'elisp-eldoc-funcall nil t) (add-hook 'xref-backend-functions #'elisp--xref-backend nil t) (setq-local project-vc-external-roots-function #'elisp-load-path-roots) (add-hook 'completion-at-point-functions @@ -1403,22 +1405,27 @@ which see." or argument string for functions. 2 - `function' if function args, `variable' if variable documentation.") -(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) - nil) - ((eq current-symbol (car current-fnsym)) - (or (apply #'elisp-get-fnsym-args-string current-fnsym) - (elisp-get-var-docstring current-symbol))) - (t - (or (elisp-get-var-docstring current-symbol) - (apply #'elisp-get-fnsym-args-string current-fnsym)))))) - -(defun elisp-get-fnsym-args-string (sym &optional index prefix) +(defun elisp-eldoc-funcall (callback &rest _ignored) + "Document function call at point. +Intended for `eldoc-documentation-functions' (which see)." + (let* ((sym-info (elisp--fnsym-in-current-sexp)) + (fn-sym (car sym-info))) + (when fn-sym + (funcall callback (apply #'elisp-get-fnsym-args-string sym-info) + :thing fn-sym + :face (if (functionp fn-sym) + 'font-lock-function-name-face + 'font-lock-keyword-face))))) + +(defun elisp-eldoc-var-docstring (callback &rest _ignored) + "Document variable at point. +Intended for `eldoc-documentation-functions' (which see)." + (let ((sym (elisp--current-symbol))) + (when sym (funcall callback (elisp-get-var-docstring sym) + :thing sym + :face 'font-lock-variable-name-face)))) + +(defun elisp-get-fnsym-args-string (sym &optional index) "Return a string containing the parameter list of the function SYM. If SYM is a subr and no arglist is obtainable from the docstring or elsewhere, return a 1-line docstring." @@ -1444,20 +1451,13 @@ or elsewhere, return a 1-line docstring." ;; Stringify, and store before highlighting, downcasing, etc. (elisp--last-data-store sym (elisp-function-argstring args) 'function)))))) - ;; Highlight, truncate. + ;; Highlight (if argstring (elisp--highlight-function-argument - sym argstring index - (or prefix - (concat (propertize (symbol-name sym) 'face - (if (functionp sym) - 'font-lock-function-name-face - 'font-lock-keyword-face)) - ": ")))))) - -(defun elisp--highlight-function-argument (sym args index prefix) - "Highlight argument INDEX in ARGS list for function SYM. -In the absence of INDEX, just call `eldoc-docstring-format-sym-doc'." + sym argstring index)))) + +(defun elisp--highlight-function-argument (sym args index) + "Highlight argument INDEX in ARGS list for function SYM." ;; FIXME: This should probably work on the list representation of `args' ;; rather than its string representation. ;; FIXME: This function is much too long, we need to split it up! @@ -1560,7 +1560,6 @@ In the absence of INDEX, just call `eldoc-docstring-format-sym-doc'." (when start (setq doc (copy-sequence args)) (add-text-properties start end (list 'face argument-face) doc)) - (setq doc (eldoc-docstring-format-sym-doc prefix doc)) doc))) ;; Return a string containing a brief (one-line) documentation string for @@ -1573,9 +1572,7 @@ In the absence of INDEX, just call `eldoc-docstring-format-sym-doc'." (t (let ((doc (documentation-property sym 'variable-documentation t))) (when doc - (let ((doc (eldoc-docstring-format-sym-doc - sym (elisp--docstring-first-line doc) - 'font-lock-variable-name-face))) + (let ((doc (elisp--docstring-first-line doc))) (elisp--last-data-store sym doc 'variable))))))) (defun elisp--last-data-store (symbol doc type) diff --git a/lisp/progmodes/flymake.el b/lisp/progmodes/flymake.el index 4ca5c657650..58e8d56f6e3 100644 --- a/lisp/progmodes/flymake.el +++ b/lisp/progmodes/flymake.el @@ -6,7 +6,7 @@ ;; Maintainer: João Távora ;; Version: 1.0.8 ;; Keywords: c languages tools -;; Package-Requires: ((emacs "26.1")) +;; Package-Requires: ((emacs "26.1") (eldoc "1.1.0")) ;; This is a GNU ELPA :core package. Avoid functionality that is not ;; compatible with the version of Emacs recorded above. @@ -1002,6 +1002,7 @@ special *Flymake log* buffer." :group 'flymake :lighter (add-hook 'after-change-functions 'flymake-after-change-function nil t) (add-hook 'after-save-hook 'flymake-after-save-hook nil t) (add-hook 'kill-buffer-hook 'flymake-kill-buffer-hook nil t) + (add-hook 'eldoc-documentation-functions 'flymake-eldoc-function nil t) ;; If Flymake happened to be alrady already ON, we must cleanup ;; existing diagnostic overlays, lest we forget them by blindly @@ -1019,6 +1020,7 @@ special *Flymake log* buffer." :group 'flymake :lighter (remove-hook 'after-save-hook 'flymake-after-save-hook t) (remove-hook 'kill-buffer-hook 'flymake-kill-buffer-hook t) ;;+(remove-hook 'find-file-hook (function flymake-find-file-hook) t) + (remove-hook 'eldoc-documentation-functions 'flymake-eldoc-function t) (mapc #'delete-overlay (flymake--overlays)) @@ -1086,6 +1088,14 @@ START and STOP and LEN are as in `after-change-functions'." (flymake-mode) (flymake-log :warning "Turned on in `flymake-find-file-hook'"))) +(defun flymake-eldoc-function (report-doc &rest _) + "Document diagnostics at point. +Intended for `eldoc-documentation-functions' (which see)." + (let ((diags (flymake-diagnostics (point)))) + (when diags + (funcall report-doc + (mapconcat #'flymake-diagnostic-text diags "\n"))))) + (defun flymake-goto-next-error (&optional n filter interactive) "Go to Nth next Flymake diagnostic that matches FILTER. Interactively, always move to the next diagnostic. With a prefix diff --git a/lisp/progmodes/octave.el b/lisp/progmodes/octave.el index 2cf305c4041..e07f818a68a 100644 --- a/lisp/progmodes/octave.el +++ b/lisp/progmodes/octave.el @@ -755,7 +755,7 @@ Key bindings: (setq font-lock-defaults '(inferior-octave-font-lock-keywords nil nil)) (setq-local info-lookup-mode 'octave-mode) - (setq-local eldoc-documentation-function 'octave-eldoc-function) + (add-hook 'eldoc-documentation-functions 'octave-eldoc-function nil t) (setq-local comint-input-ring-file-name (or (getenv "OCTAVE_HISTFILE") "~/.octave_hist")) -- cgit v1.2.3 From bba3bea247675df234353f0c2cd8af7da23bc6b8 Mon Sep 17 00:00:00 2001 From: João Távora Date: Sun, 5 Jul 2020 12:55:27 +0100 Subject: Adjust Eldoc documentation after Eli's review * etc/NEWS (Eldoc): Adjust paragraphs. * lisp/emacs-lisp/eldoc.el (eldoc-prefer-doc-buffer): Adjust docstring. (eldoc--enthusiasm-curbing-timer, eldoc-documentation-strategy) (eldoc-documentation-functions): Adjust docstring. (eldoc--handle-docs): Adjust comments. (eldoc--documentation-compose-1): New helper. (eldoc-documentation-compose) (eldoc-documentation-compose-eagerly): Use it. (eldoc-print-current-symbol-info): Adjust comments. --- etc/NEWS | 20 +++-- lisp/emacs-lisp/eldoc.el | 201 +++++++++++++++++++++++++---------------------- 2 files changed, 120 insertions(+), 101 deletions(-) (limited to 'lisp/emacs-lisp/eldoc.el') diff --git a/etc/NEWS b/etc/NEWS index f5b9e5ed152..125457c609b 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -247,17 +247,25 @@ supplied error message. +++ ** ElDoc -*** New hook 'eldoc-documentation-functions' to be used for registering -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'. +*** New hook 'eldoc-documentation-functions' to be used for +registering doc string functions. These functions don't need to +produce the doc string right away, they may arrange for it to be +produced asynchronously. The results of all doc string functions +accessible to the user through the existing single function hook +'eldoc-documentation-strategy. *** 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 +the results of 'eldoc-documentation-functions' in various ways, even +if some of those functions are sychronous and some asynchchronous. +The user option replaces 'eldoc-documentation-function', which is now obsolete. +*** The user option 'eldoc-echo-area-use-multiline-p' is now handled +by the Eldoc library itself. Functions in +'eldoc-documentation-functions' don't need to worry about consulting +it when producing a doc string. + ** Eshell --- diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el index ab8dd6a73b5..1f7a29f9621 100644 --- a/lisp/emacs-lisp/eldoc.el +++ b/lisp/emacs-lisp/eldoc.el @@ -114,8 +114,9 @@ single line of display in the echo area." (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." +to be truncated to fit in the echo area, do so if Eldoc's +documentation buffer is not already showing, since the buffer +always holds the full documentation." :type 'boolean) (defface eldoc-highlight-function-argument @@ -365,9 +366,9 @@ 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. +Each hook function is called with at least one argument CALLBACK, +a function, 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 @@ -381,10 +382,10 @@ context around point. 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: +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 be a short string or symbol designating what is being reported on; @@ -410,25 +411,24 @@ return any documentation.") (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 +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) + (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) + 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) @@ -446,55 +446,55 @@ Honour most of `eldoc-echo-area-use-multiline-p'." this-doc)) do (insert this-doc) when rest do (insert "\n"))) - ;; rename the buffer + ;; 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. + ;; Finally, output to the echo area. We handle the + ;; `truncate-sym-name-if-fit' special case first, by selecting a + ;; top-section of the `*eldoc' buffer. I'm pretty sure nicer + ;; strategies can be used here, probably by splitting this + ;; function 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 + (with-current-buffer (eldoc-doc-buffer) + (goto-char (point-min)) + (cond + ;; Potentially truncate a long message into less lines, + ;; then display it in the echo area; + ((> 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' 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'.") @@ -506,11 +506,12 @@ Honour most of `eldoc-echo-area-use-multiline-p'." "Show first doc string for item at point. Default value for `eldoc-documentation-strategy'." (run-hook-with-args-until-success 'eldoc-documentation-functions - (eldoc--make-callback :patient))) + (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'." +(defun eldoc--documentation-compose-1 (eagerlyp) + "Helper function for composing multiple doc strings. +If EAGERLYP is non-nil show documentation as soon as possible, +else wait for all doc strings." (run-hook-wrapped 'eldoc-documentation-functions (lambda (f) (let* ((callback (eldoc--make-callback @@ -520,10 +521,15 @@ Meant as a value for `eldoc-documentation-strategy'." nil))) t) +(defun eldoc-documentation-compose () + "Show multiple doc strings at once after waiting for all. +Meant as a value for `eldoc-documentation-strategy'." + (eldoc--documentation-compose-1 nil)) + (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)) + (eldoc--documentation-compose-1 t)) (defun eldoc-documentation-enthusiast () "Show most important doc string produced so far. @@ -546,25 +552,24 @@ 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-default': calls functions in the special + hook in order until one is found that produces a doc string + value. Display only that value; -- `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': calls all functions in the + special hook and displays all of the resulting doc strings + together. Wait for all strings to be ready, and preserve their + relative 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-compose-eagerly': calls all functions in + the special hook and display as many of the resulting doc + strings as possible, as soon as possibl. 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. +- `eldoc-documentation-enthusiast': calls all functions in the + special hook and displays only the most important resulting + docstring 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 @@ -596,7 +601,11 @@ documentation themselves." (local-variable-p 'eldoc-documentation-strategy)))) (defvar eldoc--enthusiasm-curbing-timer nil - "Timer used by `eldoc-documentation-enthusiast' to avoid blinking.") + "Timer used by the `eldoc-documentation-enthusiast' strategy. +When a doc string is encountered, it must endure a certain amount +of time unchallenged until it is displayed to the user. This +prevents blinking if a lower priority docstring comes in shortly +before a higher priority one.") (defalias 'eldoc #'eldoc-print-current-symbol-info) @@ -609,7 +618,8 @@ documentation themselves." (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. + ;; requested our attention. This also locally disables + ;; inhibit-quit. (while-no-input (let* ((howmany 0) (want 0) (docs-registered '())) (cl-labels @@ -652,12 +662,13 @@ documentation themselves." 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))))))))) + ;; Observe the old and the new protocol: + (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)) + (;; New protocol: trust callback will be called; + t))))))))) ;; 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 -- cgit v1.2.3 From fbc0bc6beff407addc9cef67c37a7c5502891d16 Mon Sep 17 00:00:00 2001 From: João Távora Date: Tue, 7 Jul 2020 13:37:16 +0100 Subject: Change version scheme of two Eldoc obsolete specs * lisp/emacs-lisp/eldoc.el (eldoc-documentation-function) (eldoc-message): Obsolete spec uses eldoc-1.1.0. --- lisp/emacs-lisp/eldoc.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lisp/emacs-lisp/eldoc.el') diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el index 1f7a29f9621..27daa7580e8 100644 --- a/lisp/emacs-lisp/eldoc.el +++ b/lisp/emacs-lisp/eldoc.el @@ -299,7 +299,7 @@ Otherwise work like `message'." (apply #'message format-string args))) (make-obsolete - 'eldoc-message "use `eldoc-documentation-functions' instead." "1.1.0") + 'eldoc-message "use `eldoc-documentation-functions' instead." "eldoc-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. @@ -542,7 +542,7 @@ Meant as a value for `eldoc-documentation-strategy'." nil)))) (define-obsolete-variable-alias 'eldoc-documentation-function - 'eldoc-documentation-strategy "1.1.0") + 'eldoc-documentation-strategy "eldoc-1.1.0") (defcustom eldoc-documentation-strategy #'eldoc-documentation-default "How to collect and organize results of `eldoc-documentation-functions'. -- cgit v1.2.3 From 384fa1095871f55587487dc0107be07ba684c41d Mon Sep 17 00:00:00 2001 From: João Távora Date: Wed, 8 Jul 2020 11:19:19 +0100 Subject: Improve Eldoc docstrings * lisp/emacs-lisp/eldoc.el (eldoc-documentation-strategy): Improve docstring. (eldoc--make-callback): Improve docstring. (eldoc--invoke-strategy): New helper function. (eldoc-print-current-symbol-info): Call eldoc--invoke-strategy. (eldoc-documentation-functions): Improve docstring. --- lisp/emacs-lisp/eldoc.el | 118 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 87 insertions(+), 31 deletions(-) (limited to 'lisp/emacs-lisp/eldoc.el') diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el index 27daa7580e8..9efd770dca6 100644 --- a/lisp/emacs-lisp/eldoc.el +++ b/lisp/emacs-lisp/eldoc.el @@ -387,10 +387,12 @@ 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 be a short string or symbol designating what - is being reported on; +* `: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 contraints; -* `:face', VALUE is a symbol designating a face; +* `:face', VALUE is a symbol designating a face to use when + displaying `:thing''s value. Major modes should modify this hook locally, for example: (add-hook \\='eldoc-documentation-functions #\\='foo-mode-eldoc nil t) @@ -494,14 +496,6 @@ Honor most of `eldoc-echo-area-use-multiline-p'." (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-strategy'." @@ -572,12 +566,16 @@ following values are allowed: 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." +returns something other than a string or nil and allows for some +or all of the special hook `eldoc-documentation-functions' to be +run. In that case, the strategy function should follow that +other variable's protocol closely and endeavor to display the +resulting doc strings itself. + +For backward compatibility to the \"old\" protocol, this variable +can also be set to a function that returns nil or a doc string, +depending whether or not there is documentation to display at +all." :link '(info-link "(emacs) Lisp Doc") :type '(radio (function-item eldoc-documentation-default) (function-item eldoc-documentation-compose) @@ -609,19 +607,63 @@ before a higher priority one.") (defalias 'eldoc #'eldoc-print-current-symbol-info) -(defun eldoc-print-current-symbol-info () - "Document thing at point." - (interactive) - (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 '())) +;; This variable should be unbound, but that confuses +;; `describe-symbol' for some reason. +(defvar eldoc--make-callback nil "Helper for function `eldoc--make-callback'.") + +;; JT@2020-07-08: the below docstring for the internal function +;; `eldoc--invoke-strategy' could be moved to +;; `eldoc-documentation-strategy' or thereabouts if/when we decide to +;; extend or publish the `make-callback' protocol. +(defun eldoc--make-callback (method) + "Make callback suitable for `eldoc-documentation-functions'. +The return value is a function FN whose lambda list is (STRING +&rest PLIST) and can be called by those functions. Its +responsibility is always to register the docstring STRING along +with options specified in PLIST as the documentation to display +for each particular situation. + +METHOD specifies how the callback behaves relative to other +competing elements in `eldoc-documentation-functions'. It can +have the following values: + +- `:enthusiast' says to display STRING as soon as possible if + there's no higher priority doc string; + +- `:patient' says to display STRING along with all other + competing strings but only when all of all + `eldoc-documentation-functions' have been collected; + +- `:eager' says to display STRING along with all other competing + strings so far, as soon as possible." + (funcall eldoc--make-callback method)) + +(defun eldoc--invoke-strategy () + "Invoke `eldoc-documentation-strategy' function. + +That function's job is to run the `eldoc-documentation-functions' +special hook, using the `run-hook' family of functions. The way +we invoke it here happens in a way strategy function can itself +call `eldoc--make-callback' to produce values to give to the +elements of the special hook `eldoc-documentation-functions'. + +For each element of `eldoc-documentation-functions' invoked a +corresponding call to `eldoc--make-callback' must be made. See +docstring of `eldoc--make-callback' for the types of callback +that can be produced. + +If the strategy function does not use `eldoc--make-callback', it +must find some alternate way to produce callbacks to feed to +`eldoc-documentation-function', and those callbacks should +endeavour to display the docstrings given to them." + (let* (;; how many docstrings callbaks have been + (howmany 0) + ;; how many calls to callbacks we're waiting on. Used by + ;; `:patient'. + (want 0) + ;; how many doc strings and corresponding options have been + ;; registered it. + (docs-registered '())) (cl-labels ((register-doc (pos string plist) (when (and string (> (length string) 0)) @@ -668,7 +710,21 @@ before a higher priority one.") (;; Old protocol: got nil, clear the echo area; (null res) (eldoc--message nil)) (;; New protocol: trust callback will be called; - t))))))))) + t)))))) + +(defun eldoc-print-current-symbol-info () + "Document thing at point." + (interactive) + (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--invoke-strategy))))) ;; 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 -- cgit v1.2.3