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

From: Stefan Monnier <[email-masked]>
Date: Mon, 30 Jun 2025 12:01:02 -0400

>> > - 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.  We can do something like:

    @@ -2491,7 +2491,7 @@ query-replace-map
         (define-key map [f1] 'help)
         (define-key map [help] 'help)
         (define-key map "?" 'help)
    -    (define-key map "\C-g" 'quit)
    -    (define-key map "\C-]" 'quit)
    +    (define-key map "\C-g" #'keyboard-quit)
    +    (define-key map "\C-]" #'keyboard-quit)
         (define-key map "\C-v" 'scroll-up)
         (define-key map "\M-v" 'scroll-down)

or we can do something like:

    @@ -3328,6 +3328,9 @@ perform-replace
     			 (replace-dehighlight)
     			 (save-excursion (recursive-edit))
     			 (setq replaced t))
    +			((eq def 'quit)
    +			 ;; FIXME: Should we call `keyboard-quit'?
    +			 (signal 'quit nil))
                             ((commandp def t)
                              (call-interactively def))
     			;; Note: we do not need to treat `exit-prefix'

or we can do something like:

    diff --git a/lisp/replace.el b/lisp/replace.el
    index 9939273594f..62cabb45c09 100644
    --- a/lisp/replace.el
    +++ b/lisp/replace.el
    @@ -2491,7 +2491,7 @@ query-replace-map
         (define-key map [f1] 'help)
         (define-key map [help] 'help)
         (define-key map "?" 'help)
    -    (define-key map "\C-g" 'quit)
    -    (define-key map "\C-]" 'quit)
    +    (define-key map "\C-g" 'normal-binding)
    +    (define-key map "\C-]" 'normal-binding)
         (define-key map "\C-]" 'quit)
         (define-key map "\C-v" 'scroll-up)
         (define-key map "\M-v" 'scroll-down)
    @@ -3328,6 +3328,12 @@ perform-replace
     			 (replace-dehighlight)
     			 (save-excursion (recursive-edit))
     			 (setq replaced t))
    +			((eq def 'normal-binding)
    +			 (let ((cmd (key-binding (vector key))))
    +			   (setq this-command cmd)
    +			   (setq keep-going nil)
    +			   (setq done t)
    +			   (execute-command cmd)))
                             ((commandp def t)
                              (call-interactively def))
     			;; Note: we do not need to treat `exit-prefix'

I lean towards the first option.

        Stefan