Subject: Re: bug#1305: All code that currently beeps should use visual bell instead

From: Gregory Heytings <[email-masked]>
Date: Wed, 21 Apr 2021 14:53:42 +0000

>>>>> I have used this for a couple of days and find it strictly better 
>>>>> than both the default behavior on GNU/Linux with inverse video and 
>>>>> flashing the minibuffer background.
>>>>
>>>> Did you try the patch I sent?
>>>
>>> Yes, of course.  I provided detailed comments in a previous post.
>>
>> You mean, the post to which I replied?  Okay, it wasn't clear to me 
>> that you were also commenting on that patch.
>
> See my comments here:
>
>    https://debbugs.gnu.org/cgi/bugreport.cgi?bug=1305#574
>
> But that was on an earlier version of your patch.  Perhaps there are any 
> significant differences in your most recent version?
>

That was not a patch, that was only a proof of concept.  I attach the 
current version of the actual proposed patch.

From 1327c4e13d3c0f3ba0cf93c43ad82645365123df Mon Sep 17 00:00:00 2001
From: Gregory Heytings <[email-masked]>
Date: Wed, 21 Apr 2021 14:52:01 +0000
Subject: [PATCH] New default bell.

* lisp/simple.el (color-bell): New default bell, which briefly flashes
the cursor and the echo area when an error occurs.
(color-bell-echo-area-face, color-bell-cursor-face): New faces.
(color-bell-ignored-errors, color-bell-duration): New user options.

* src/eval.c (last-error-symbol, last-error-data): New variables.
(signal_or_quit): Set the new variables.

* lisp/face-remap.el (face-remap-remove-relative, buffer-face-mode-face,
text-scale-mode-step): Autoload them.

* etc/NEWS: Document the change.
---
 etc/NEWS           | 10 +++++++
 lisp/face-remap.el |  3 ++
 lisp/simple.el     | 69 ++++++++++++++++++++++++++++++++++++++++++++++
 src/eval.c         | 11 ++++++++
 4 files changed, 93 insertions(+)

diff --git a/etc/NEWS b/etc/NEWS
index e39aa7b437..4203e0b40d 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -272,6 +272,16 @@ commands.  The new keystrokes are 'C-x x g' ('revert-buffer'),
 ** Commands 'set-frame-width' and 'set-frame-height' can now get their
 input using the minibuffer.
 
++++
+** The default value of 'ring-bell-function' is now non-nil.
+When an error occurs, Emacs will by default briefly flash the cursor
+and the echo area.  This effect can be customized with the user options
+color-bell-duration, color-bell-cursor-face, color-bell-echo-area-face
+and color-bell-ignored-errors.  To restore the previous behavior,
+add the following to your init file:
+
+(setq ring-bell-function nil)
+
 
 * Editing Changes in Emacs 28.1
 
diff --git a/lisp/face-remap.el b/lisp/face-remap.el
index 5914ee4a20..df4c59913c 100644
--- a/lisp/face-remap.el
+++ b/lisp/face-remap.el
@@ -142,6 +142,7 @@ face-remap-add-relative
       (force-mode-line-update))
     (cons face specs)))
 
+;;;###autoload
 (defun face-remap-remove-relative (cookie)
   "Remove a face remapping previously added by `face-remap-add-relative'.
 COOKIE should be the return value from that function."
@@ -210,6 +211,7 @@ face-remap-set-base
 ;; ----------------------------------------------------------------
 ;; text-scale-mode
 
+;;;###autoload
 (defcustom text-scale-mode-step 1.2
   "Scale factor used by `text-scale-mode'.
 Each positive or negative step scales the default face height by this amount."
@@ -397,6 +399,7 @@ text-scale-adjust
 ;; ----------------------------------------------------------------
 ;; buffer-face-mode
 
+;;;###autoload
 (defcustom buffer-face-mode-face 'variable-pitch
   "The face specification used by `buffer-face-mode'.
 It may contain any value suitable for a `face' text property,
diff --git a/lisp/simple.el b/lisp/simple.el
index 999755a642..94984ba6d9 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -8511,7 +8511,76 @@ play-sound-file
 	(plist-put sound :device device))
     (push 'sound sound)
     (play-sound sound)))
+
+(defface color-bell-echo-area-face
+  `((((type tty)) (:inherit error :inverse-video t))
+    (t (:inherit match)))
+  "Face used by `color-bell' to flash the echo area when an error happened."
+  :version "28.1")
+
+(defface color-bell-cursor-face
+  `((t (:inherit error)))
+  "Face used by `color-bell' to flash the cursor when an error happened.
+The cursor is flashed with the foreground color of that face; it is not
+flashed in terminals."
+  :version "28.1")
+
+(defcustom color-bell-ignored-errors nil
+  "List of errors symbols ignored by `color-bell'.
+Error symbols that are present in this list are ignored by `color-bell', and
+are displayed without flashing the cursor and echo area.
+For example, the value '(quit beginning-of-buffer end-of-buffer) disables
+`color-bell' on \\[keyboard-quit], and for beginning and end of buffer errors.
+To find the symbol of an error, type \\[eval-expression] last-error-symbol \
+\\[newline] immediately
+after the error happened, without using \\[indent-for-tab-command] for \
+completion."
+  :type 'list
+  :version "28.1")
+
+(defcustom color-bell-duration 0.25
+  "Maximum duration of the `color-bell' flash.
+The flash stops when input is available."
+  :type 'float
+  :version "28.1")
+
+(defvar color-bell--cursor-background nil)
+(defvar color-bell--face-remapping nil)
+
+(defun color-bell ()
+  (unless (memq last-error-symbol color-bell-ignored-errors)
+    (if (not (eq (face-attribute 'cursor :background)
+                 color-bell--cursor-background))
+        (setq color-bell--cursor-background
+            (face-attribute 'cursor :background)))
+    (set-face-attribute 'cursor nil :background
+                        (face-attribute 'color-bell-cursor-face
+                                        :foreground nil t))
+    (let* ((no-minibuffer (= (minibuffer-depth) 0))
+           (inside-minibuffer (minibufferp (current-buffer)))
+           (buffer-name (if no-minibuffer
+                            " *Echo Area 0*"
+                          (format " *Minibuf-%d*" (minibuffer-depth))))
+           (buffer (get-buffer buffer-name)))
+      (unless inside-minibuffer
+        (with-current-buffer buffer
+          (when color-bell--face-remapping
+            (face-remap-remove-relative color-bell--face-remapping))
+          (setq-local color-bell--face-remapping
+                      (face-remap-add-relative 'default
+                                               'color-bell-echo-area-face))))
+      (if no-minibuffer
+          (message (error-message-string (cons last-error-symbol nil))))
+      (sit-for color-bell-duration)
+      (set-face-attribute 'cursor nil
+                          :background color-bell--cursor-background)
+      (unless inside-minibuffer
+        (if (buffer-live-p buffer)
+            (with-current-buffer buffer
+              (face-remap-remove-relative color-bell--face-remapping)
+              (setq color-bell--face-remapping nil)))))))
 
+(setq ring-bell-function #'color-bell)
 
 (defcustom read-mail-command 'rmail
   "Your preference for a mail reading package.
diff --git a/src/eval.c b/src/eval.c
index fd93f5b9e1..ea012eef77 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -1716,6 +1716,9 @@ signal_or_quit (Lisp_Object error_symbol, Lisp_Object data, bool keyboard_quit)
 #endif
 #endif
 
+  Vlast_error_symbol = real_error_symbol;
+  Vlast_error_data = (NILP (error_symbol) ? Fcdr (data) : data);
+
   /* This hook is used by edebug.  */
   if (! NILP (Vsignal_hook_function)
       && ! NILP (error_symbol)
@@ -4321,6 +4324,14 @@ syms_of_eval (void)
 The Edebug package uses this to regain control.  */);
   Vsignal_hook_function = Qnil;
 
+  DEFVAR_LISP ("last-error-symbol", Vlast_error_symbol,
+	       doc: /* Symbol of the last error.  */);
+  Vlast_error_symbol = Qnil;
+
+  DEFVAR_LISP ("last-error-data", Vlast_error_data,
+	       doc: /* Data of the last error.  */);
+  Vlast_error_data = Qnil;
+
   DEFVAR_LISP ("debug-on-signal", Vdebug_on_signal,
 	       doc: /* Non-nil means call the debugger regardless of condition handlers.
 Note that `debug-on-error', `debug-on-quit' and friends
-- 
2.30.2