@ -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.
" 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 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 a number ( integer or floating point ) , it has the
semantics of ` max-mini-window-height ', constraining the resizing
for Eldoc purposes only.
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.
Any resizing respects ` max-mini-window-height '.
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)))
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 ) ) ) ) ) ) ) )
Default value for ` eldoc-documentation-strategy '. "
( run-hook-with-args-until-success 'eldoc-documentation-functions
( eldoc--make-callback :patient ) ) )
( 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 ) ) ) ) ) ) ) )
( 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 )
( 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.
( 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 ) )
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. "
( 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