Emacs: silencing messages

Why inhibit-message isn't enough

Ignoring batch mode, the message function displays a message to the user in the echo area and logs it in the message log buffer (default *Messages*).

If we're calling some code that we don't control (or sometimes even if we do), we might want to suppress either behavior. To do that:

  • To make message not show something in the echo area temporarily, let-bind inhibit-message to non-nil.

    (let ((inhibit-message t))
      ;; Logs but does not show in echo area
      (message "%s" "abc")
      (some-code-that-calls-message))
  • To make message not log messages to the log buffer, let bind message-log-max to nil.

    (let ((message-log-max nil))
      ;; Shows in echo area but does not log
      (message "%s" "abc")
      (some-code-that-calls-message))

Additionally, you can utilize inhibit-redisplay to prevent intermediate messages from flashing to the user. This is something that builtin files.el should do but, uh, currently doesn't.

;; Emacs 29
(defun files--message (format &rest args)
  ;; ...
  (apply #'message format args)
  ;; Redisplay can run here and flash the to-be-cleared message to the
  ;; user
  (when save-silently (message nil)))

However, this is still not enough. The message function will clear the echo area instead of leave it as-is when inhibit-message is non-nil.

This is what the shut-up package is useful for. The shut-up macro it provides temporarily rebinds message to a function that only inserts the message to a temporary buffer and prevents it from doing anything to the echo area.

Examples

Our test code looks like this:

(progn
  (message "Prior") ; simulate prior content in echo area
  (run-at-time
   1 nil
   (lambda ()
     ;; The part being tested
     (let ((inhibit-message t))
       (message "...")))))

Let's abstract it a bit.

(defmacro k:test-message-inhibiting (&rest body)
  (declare (indent 0))
  `(progn
     ;; Put some prior content into the echo area, to see what happens
     ;; to existing content in the echo area.
     ;; If this is run in Org or with eval-last-sexp, they will both
     ;; display something in the echo area, which also works as the
     ;; prior content.
     (message "Prior")
     (run-at-time
      1 nil
      (lambda ()
        ,@body))))

Now let's start testing.

(k:test-message-inhibiting
  (message "..."))
  • ... is shown in the echo area
  • ... is written into *Messages*
(k:test-message-inhibiting
  (let ((inhibit-message t))
    (message "...")))
  • The echo area is cleared
  • ... is written into *Messages*
(k:test-message-inhibiting
  (let ((message-log-max nil))
    (message "...")))
  • ... is shown in the echo area
  • ... is not written into *Messages*
(k:test-message-inhibiting
  (let ((inhibit-redisplay t))
    (message "...")))
  • ... is shown in the echo area. Although redisplay doesn't happen within the let, the message is still written to the echo area (and haven't been overwritten), so the next redisplay still shows the message.
  • ... is written into *Messages*
(require 'shut-up)
(k:test-message-inhibiting
  (shut-up
    (message "...")))
  • Echo area is untouched
  • *Messages* is untouched