diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi index 42b049eafde..0085f3b6750 100644 --- a/doc/lispref/display.texi +++ b/doc/lispref/display.texi @@ -306,6 +306,35 @@ reformatted, with undesirable results. Instead, use @code{(message "%s" @var{string})}. @end defun +@defvar set-message-function +When this variable is non-@code{nil}, @code{message} and related functions +call it as a function with one argument that is the message to show. + +When this function returns @code{nil}, the message is displayed in the +echo area as usual. When the function returns a string, the returned +string is displayed in the echo area. When this function returns +other non-@code{nil} values, this means that the message was handled +specially, so the same message is not displayed in the echo area. +See also @code{clear-message-function} that can be used to clear the +message displayed by this function. + +The default value is the function that displays the message at the end +of the minibuffer when the minibuffer is active. +@end defvar + +@defvar clear-message-function +When this variable is non-@code{nil}, @code{message} and related functions +call it without arguments when their message is @code{nil} or the empty string. + +Usually this function is called when the next input event arrives. +The function is called without arguments. It is expected to clear the +message displayed by its counterpart function specified by +@code{set-message-function}. + +The default value is the function that clears the message displayed at +the end of the minibuffer when the minibuffer is active. +@end defvar + @defvar inhibit-message When this variable is non-@code{nil}, @code{message} and related functions will not use the Echo Area to display messages. diff --git a/etc/NEWS b/etc/NEWS index 678139ecbcf..cd835f3d2a9 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -781,6 +781,10 @@ been introduced to allow controlling how the 'M-<' command works in the minibuffer. If non-nil, point will move to the end of the prompt (if point is after the end of the prompt). ++++ +*** When the minibuffer is active, messages are displayed at the end of +the minibuffer instead of overwriting the minibuffer by the echo area. + --- *** Minibuffer now uses 'minibuffer-message' to display error messages at the end of the active minibuffer. @@ -2723,6 +2727,10 @@ This function works like 'read-char', but uses 'read-from-minibuffer' to read a character, so it maintains a history that can be navigated via usual minibuffer keystrokes 'M-p'/'M-n'. +** New variables 'set-message-function' and 'clear-message-function' +can be used to specify functions to show and clear messages that +normally are displayed in the echo area. + ** 'setq-local' can now set an arbitrary number of variables, which makes the syntax more like 'setq'. diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index 76d8ca44757..5dc753ffd5c 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -746,6 +746,76 @@ If ARGS are provided, then pass MESSAGE through `format-message'." (sit-for (or minibuffer-message-timeout 1000000))) (delete-overlay ol))))) +(defcustom minibuffer-message-clear-timeout nil + "How long to display an echo-area message when the minibuffer is active. +If the value is a number, it should be specified in seconds. +If the value is not a number, such messages never time out, +and the text is displayed until the next input event arrives. +Unlike `minibuffer-message-timeout' used by `minibuffer-message', +this option affects the pair of functions `set-minibuffer-message' +and `clear-minibuffer-message' called automatically via +`set-message-function' and `clear-message-function'." + :type '(choice (const :tag "Never time out" nil) + (integer :tag "Wait for the number of seconds" 2)) + :version "27.1") + +(defvar minibuffer-message-timer nil) +(defvar minibuffer-message-overlay nil) + +(defun set-minibuffer-message (message) + "Temporarily display MESSAGE at the end of the minibuffer. +The text is displayed for `minibuffer-message-clear-timeout' seconds +(if the value is a number), or until the next input event arrives, +whichever comes first. +Unlike `minibuffer-message', this function is called automatically +via `set-message-function'." + (when (and (not noninteractive) + (window-live-p (active-minibuffer-window))) + (with-current-buffer (window-buffer (active-minibuffer-window)) + (setq message (if (string-match-p "\\` *\\[.+\\]\\'" message) + ;; Make sure we can put-text-property. + (copy-sequence message) + (concat " [" message "]"))) + (unless (or (null minibuffer-message-properties) + ;; Don't overwrite the face properties the caller has set + (text-properties-at 0 message)) + (setq message (apply #'propertize message minibuffer-message-properties))) + + (clear-minibuffer-message) + + (setq minibuffer-message-overlay + (make-overlay (point-max) (point-max) nil t t)) + (unless (zerop (length message)) + ;; The current C cursor code doesn't know to use the overlay's + ;; marker's stickiness to figure out whether to place the cursor + ;; before or after the string, so let's spoon-feed it the pos. + (put-text-property 0 1 'cursor t message)) + (overlay-put minibuffer-message-overlay 'after-string message) + + (when (numberp minibuffer-message-clear-timeout) + (setq minibuffer-message-timer + (run-with-timer minibuffer-message-clear-timeout nil + #'clear-minibuffer-message))) + + ;; Return `t' telling the caller that the message + ;; was handled specially by this function. + t))) + +(setq set-message-function 'set-minibuffer-message) + +(defun clear-minibuffer-message () + "Clear minibuffer message. +Intended to be called via `clear-message-function'." + (when (not noninteractive) + (when (timerp minibuffer-message-timer) + (cancel-timer minibuffer-message-timer) + (setq minibuffer-message-timer nil)) + (when (overlayp minibuffer-message-overlay) + (delete-overlay minibuffer-message-overlay) + (setq minibuffer-message-overlay nil)))) + +(setq clear-message-function 'clear-minibuffer-message) + (defun minibuffer-completion-contents () "Return the user input in a minibuffer before point as a string. In Emacs 22, that was what completion commands operated on. diff --git a/src/keyboard.c b/src/keyboard.c index 5135fd0bc84..4cf1f64b487 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -2990,6 +2990,8 @@ read_char (int commandflag, Lisp_Object map, safe_run_hooks (Qecho_area_clear_hook); clear_message (1, 0); } + else if (FUNCTIONP (Vclear_message_function)) + clear_message (1, 0); } reread_for_input_method: diff --git a/src/xdisp.c b/src/xdisp.c index 08c6927052c..8cba5c5028d 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -11706,13 +11706,32 @@ truncate_message_1 (ptrdiff_t nchars, Lisp_Object a2) static void set_message (Lisp_Object string) { + Lisp_Object message = Qnil; + eassert (STRINGP (string)); - message_enable_multibyte = STRING_MULTIBYTE (string); + if (FUNCTIONP (Vset_message_function)) + { + ptrdiff_t count = SPECPDL_INDEX (); + specbind (Qinhibit_quit, Qt); + message = safe_call1 (Vset_message_function, string); + unbind_to (count, Qnil); - with_echo_area_buffer (0, -1, set_message_1, 0, string); - message_buf_print = false; - help_echo_showing_p = false; + if (STRINGP (message)) + { + string = message; + message = Qnil; + } + } + + if (NILP (message)) + { + message_enable_multibyte = STRING_MULTIBYTE (string); + + with_echo_area_buffer (0, -1, set_message_1, 0, string); + message_buf_print = false; + help_echo_showing_p = false; + } if (STRINGP (Vdebug_on_message) && STRINGP (string) @@ -11768,6 +11787,14 @@ clear_message (bool current_p, bool last_displayed_p) { echo_area_buffer[0] = Qnil; message_cleared_p = true; + + if (FUNCTIONP (Vclear_message_function)) + { + ptrdiff_t count = SPECPDL_INDEX (); + specbind (Qinhibit_quit, Qt); + safe_call (1, Vclear_message_function); + unbind_to (count, Qnil); + } } if (last_displayed_p) @@ -34940,6 +34967,26 @@ display table takes effect; in this case, Emacs does not consult doc: /* If non-nil, debug if a message matching this regexp is displayed. */); Vdebug_on_message = Qnil; + DEFVAR_LISP ("set-message-function", Vset_message_function, + doc: /* If non-nil, function to show the message. +The function is called with one argument that is the message. +When this function returns nil, the message is displayed in the echo +area as usual. When the function returns a string, the returned +string is displayed in the echo area. When this function returns +other non-nil values, this means that the message was handled +specially, so the same message is not displayed in the echo area. +See also `clear-message-function' that can be used to clear the +message displayed by this function. */); + Vset_message_function = Qnil; + + DEFVAR_LISP ("clear-message-function", Vclear_message_function, + doc: /* If non-nil, function to clear message. +Usually this function is called when the next input event arrives. +The function is called without arguments. It is expected to clear the +message displayed by its counterpart function specified by +`set-message-function'. */); + Vclear_message_function = Qnil; + DEFVAR_LISP ("redisplay--all-windows-cause", Vredisplay__all_windows_cause, doc: /* */); Vredisplay__all_windows_cause = Fmake_hash_table (0, NULL);