Subject: bug#78916: 31.0.50; C-g fails to exit loop

From: Pip Cet <[email-masked]>
Date: Mon, 30 Jun 2025 19:40:43 +0000

Pip Cet <[email-masked]> writes:

> "Stefan Monnier via \"Bug reports for GNU Emacs, the Swiss army knife of text editors\"" <[email-masked]> writes:
>
>>>> > - Both keys behave like the "old C-g".
>>>> > ?
>>>> I'm fine with that.
>>> Same, but does it mean 'C-g' will do the same as 'q'?  If not, what is
>>> the difference between them?
>>
>> Hmm... so `q` is bound to `exit` which consumes the key and exits the
>> Q&R loop.  In contrast `C-g` and `C-]` were bound to `quit` which is
>> not handled exlicitly by the Q&R loop, instead it just pushes the event
>> back on the `unread-commend-events` before exiting the loop, so in
>> Mike's test case, `q` exits the inner Q&R and passes to the next,
>> whereas `C-]` aborts all the subsequent Q&R calls after which it runs
>> the global binding of `C-[`.
>
>> I get the impression that the intention of the `replace.el` code when it
>> pushes the event back on `unread-commend-events` is to make it run
>> whichever binding is currently active "outside" (i.e. global or local
>> keymap).
>>
>> IOW, we have many different ways to skin this cat and I'm not sure which
>> is best.
>
> I'd just like to speak out in favor of making read-key take an extra
> argument to make it handle C-g the way read-event does; IIUC, the lack
> of this argument is the real problem here.  If we don't, we should be
> very careful about replacing read-event by read-key, and revert most
> such changes.

This patch (which doesn't include documentation changes yet) would make

    (read-key prompt nil t)

DTRT, I think.  I've looked at a few callers of read-key and they should
probably pass t as the can-quit argument, too, because most of them want
to quit for C-g and try to do so, with varying degrees of success.

I've yet to verify this fixes the original F-key issue which prompted
the change to read-key in the first place.  It does appear to fix the
buggy behavior observed by the OP.

>From dd850c5da3591e00dfd2fe1dd3e6d901a53e7176 Mon Sep 17 00:00:00 2001
From: Pip Cet <[email-masked]>
Subject: [PATCH 1/3] Add optional 'can-quit' argument to 'read-key'
 (bug#78916)

* lisp/subr.el (read-key): Add 'can-quit' argument and pass it on.
* src/callint.c (Fcall_interactively):
* src/keyboard.c (command_loop_1):
(read_menu_command): Adjust accordingly.
(read_key_sequence): Quit after read_char if can_quit is set.
(read_key_sequence_vs):
(Fread_key_sequence):
(Fread_key_sequence_vector): Add extra argument.
---
 lisp/subr.el   |  5 +++--
 src/callint.c  |  4 ++--
 src/keyboard.c | 32 ++++++++++++++++++++------------
 3 files changed, 25 insertions(+), 16 deletions(-)

diff --git a/lisp/subr.el b/lisp/subr.el
index 69f6e4dbab8..b06d45d449f 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -3425,7 +3425,7 @@ read-key-full-map
 
 (defvar read-key-delay 0.01) ;Fast enough for 100Hz repeat rate, hopefully.
 
-(defun read-key (&optional prompt disable-fallbacks)
+(defun read-key (&optional prompt disable-fallbacks can-quit)
   "Read a key from the keyboard, return the event thus read.
 Contrary to `read-event' this will not return a raw event but instead will
 obey the input decoding and translations usually done by `read-key-sequence'.
@@ -3505,7 +3505,8 @@ read-key
              map))
           (let* ((keys
                   (catch 'read-key (read-key-sequence-vector prompt nil t
-                                                             nil nil t)))
+                                                             nil nil t
+                                                             can-quit)))
                  (key (aref keys 0)))
             (if (and (> (length keys) 1)
                      (memq key '(mode-line header-line tab-line
diff --git a/src/callint.c b/src/callint.c
index e0246e5d594..38ffa8804d3 100644
--- a/src/callint.c
+++ b/src/callint.c
@@ -538,7 +538,7 @@ DEFUN ("call-interactively", Fcall_interactively, Scall_interactively, 1, 3, 0,
 				Qface, Qminibuffer_prompt, callint_message);
 	    args[i] = Fread_key_sequence (callint_message,
 					  Qnil, Qnil, Qnil, Qnil,
-					  Qnil);
+					  Qnil, Qnil);
 	    unbind_to (speccount1, Qnil);
 	    visargs[i] = Fkey_description (args[i], Qnil);
 
@@ -569,7 +569,7 @@ DEFUN ("call-interactively", Fcall_interactively, Scall_interactively, 1, 3, 0,
 				Qface, Qminibuffer_prompt, callint_message);
 	    args[i] = Fread_key_sequence_vector (callint_message,
 						 Qnil, Qt, Qnil, Qnil,
-						 Qnil);
+						 Qnil, Qnil);
 	    visargs[i] = Fkey_description (args[i], Qnil);
 	    unbind_to (speccount1, Qnil);
 
diff --git a/src/keyboard.c b/src/keyboard.c
index 8b2ebd215d2..93149910fa7 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -1311,7 +1311,7 @@ some_mouse_moved (void)
 
 enum { READ_KEY_ELTS = 30 };
 static int read_key_sequence (Lisp_Object *, Lisp_Object,
-                              bool, bool, bool, bool, bool);
+                              bool, bool, bool, bool, bool, bool);
 static void adjust_point_for_property (ptrdiff_t, bool);
 
 static Lisp_Object
@@ -1422,7 +1422,7 @@ command_loop_1 (void)
       raw_keybuf_count = 0;
       Lisp_Object keybuf[READ_KEY_ELTS];
       int i = read_key_sequence (keybuf, Qnil, false, true, true, false,
-				 false);
+				 false, false);
 
       /* A filter may have run while we were reading the input.  */
       if (! FRAME_LIVE_P (XFRAME (selected_frame)))
@@ -1698,7 +1698,7 @@ read_menu_command (void)
 
   Lisp_Object keybuf[READ_KEY_ELTS];
   int i = read_key_sequence (keybuf, Qnil, false, true, true, true,
-			     false);
+			     false, false);
 
   unbind_to (count, Qnil);
 
@@ -10544,7 +10544,7 @@ restore_reading_key_sequence (int old_reading_key_sequence)
 read_key_sequence (Lisp_Object *keybuf, Lisp_Object prompt,
 		   bool dont_downcase_last, bool can_return_switch_frame,
 		   bool fix_current_buffer, bool prevent_redisplay,
-		   bool disable_text_conversion_p)
+		   bool disable_text_conversion_p, bool can_quit)
 {
   specpdl_ref count = SPECPDL_INDEX ();
 
@@ -10867,6 +10867,11 @@ read_key_sequence (Lisp_Object *keybuf, Lisp_Object prompt,
 	    key = read_char (prevent_redisplay ? -2 : NILP (prompt),
 		             current_binding, last_nonmenu_event,
                              &used_mouse_menu, NULL);
+	    if (can_quit && EQ (key, make_fixnum (quit_char)))
+	      {
+		Vquit_flag = Qt;
+		maybe_quit ();
+	      }
 	    used_mouse_menu_history[t] = used_mouse_menu;
 	    if ((FIXNUMP (key) && XFIXNUM (key) == -2) /* wrong_kboard_jmpbuf */
 		/* When switching to a new tty (with a new keyboard),
@@ -11546,7 +11551,8 @@ read_key_sequence_vs (Lisp_Object prompt, Lisp_Object continue_echo,
 		      Lisp_Object dont_downcase_last,
 		      Lisp_Object can_return_switch_frame,
 		      Lisp_Object cmd_loop, bool allow_string,
-		      bool disable_text_conversion)
+		      bool disable_text_conversion,
+		      bool can_quit)
 {
   specpdl_ref count = SPECPDL_INDEX ();
 
@@ -11574,7 +11580,7 @@ read_key_sequence_vs (Lisp_Object prompt, Lisp_Object continue_echo,
   Lisp_Object keybuf[READ_KEY_ELTS];
   int i = read_key_sequence (keybuf, prompt, ! NILP (dont_downcase_last),
 			     ! NILP (can_return_switch_frame), false, false,
-			     disable_text_conversion);
+			     disable_text_conversion, can_quit);
 
 #if 0  /* The following is fine for code reading a key sequence and
 	  then proceeding with a lengthy computation, but it's not good
@@ -11596,7 +11602,7 @@ read_key_sequence_vs (Lisp_Object prompt, Lisp_Object continue_echo,
 		     (i, keybuf)));
 }
 
-DEFUN ("read-key-sequence", Fread_key_sequence, Sread_key_sequence, 1, 6, 0,
+DEFUN ("read-key-sequence", Fread_key_sequence, Sread_key_sequence, 1, 7, 0,
        doc: /* Read a sequence of keystrokes and return as a string or vector.
 The sequence is sufficient to specify a non-prefix command in the
 current local and global maps.
@@ -11650,23 +11656,25 @@ Second (optional) arg CONTINUE-ECHO, if non-nil, means this key echos
 being sent.  */)
   (Lisp_Object prompt, Lisp_Object continue_echo, Lisp_Object dont_downcase_last,
    Lisp_Object can_return_switch_frame, Lisp_Object cmd_loop,
-   Lisp_Object disable_text_conversion)
+   Lisp_Object disable_text_conversion, Lisp_Object can_quit)
 {
   return read_key_sequence_vs (prompt, continue_echo, dont_downcase_last,
 			       can_return_switch_frame, cmd_loop, true,
-			       !NILP (disable_text_conversion));
+			       !NILP (disable_text_conversion),
+			       !NILP (can_quit));
 }
 
 DEFUN ("read-key-sequence-vector", Fread_key_sequence_vector,
-       Sread_key_sequence_vector, 1, 6, 0,
+       Sread_key_sequence_vector, 1, 7, 0,
        doc: /* Like `read-key-sequence' but always return a vector.  */)
   (Lisp_Object prompt, Lisp_Object continue_echo, Lisp_Object dont_downcase_last,
    Lisp_Object can_return_switch_frame, Lisp_Object cmd_loop,
-   Lisp_Object disable_text_conversion)
+   Lisp_Object disable_text_conversion, Lisp_Object can_quit)
 {
   return read_key_sequence_vs (prompt, continue_echo, dont_downcase_last,
 			       can_return_switch_frame, cmd_loop, false,
-			       !NILP (disable_text_conversion));
+			       !NILP (disable_text_conversion),
+			       !NILP (can_quit));
 }
 
 /* Return true if input events are pending.  */
-- 
2.50.0

>From 395ec08d4f5d31f2d6d6022fb40c4c90d44b8aeb Mon Sep 17 00:00:00 2001
From: Pip Cet <[email-masked]>
Subject: [PATCH 2/3] Make C-g quit again in perform-replace (bug#78916)

* lisp/replace.el (perform-replace): Pass 't' as 'can-quit' argument
to 'read-key'.
---
 lisp/replace.el | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lisp/replace.el b/lisp/replace.el
index 9939273594f..1d115949599 100644
--- a/lisp/replace.el
+++ b/lisp/replace.el
@@ -3103,7 +3103,7 @@ perform-replace
                                    replacement-presentation))))
                     ;; Use `read-key' so that escape sequences on TTYs
                     ;; are properly mapped back to the intended key.
-		    (setq key (read-key prompt)))
+		    (setq key (read-key prompt nil t)))
 		  ;; Necessary in case something happens during
 		  ;; read-event that clobbers the match data.
 		  (set-match-data real-match-data)
-- 
2.50.0