Subject: Re: bug#78737: sit-for behavior changes when byte-compiled

From: Daniel Colascione <[email-masked]>
Date: Tue, 10 Jun 2025 09:21:56 -0700

On June 10, 2025 8:56:22 AM PDT, Eli Zaretskii <[email-masked]> wrote:
>> Date: Tue, 10 Jun 2025 08:06:46 -0700
>> From: Daniel Colascione <[email-masked]>
>> CC: [email-masked], [email-masked]
>> 
>> IOW, C-g in the command loop can mean cmderror or does it mean C-g. How do you know? Roll the dice. The user cannot predict whether redisplay is going to run at the exact moment he whacks a key. This particular aspect of the system has been ambiguous for a long time (ever since redisplay got the ability to run Lisp?) and ultimately comes from C-g being overloaded in the first place.
>
>First, redisplay is not part of the command loop, at least not
>conceptually: when redisplay runs, we don't respond to user input.
>
>And second, where we call Lisp from redisplay and cannot tolerate C-g
>(or don't think it would be useful), we bind inhibit-quit already.
>
>So I don't think I follow what you are saying here.
>
>> That's *not* the same as just binding inhibit-quit around reading events like transient tries to do. The problem with binding inhibit-quit is that, well, you can't quit. Quitting restartable things that tend to get wedged like redisplay is useful.
>
>It might be useful in principle, but if you allow that, you'll have an
>infinite redisplay loop on your hands.  Why? because quitting has the
>side effect of showing "Quit" in the echo area,

No, in the command loop, it would have the side effect of running whatever C-g is bound to in the command map, and that's not necessarily (although usually is) going to show Quit.

But maybe the redisplay you're interrupting has a transient problem. Maybe you've arranged the debugger to start on quit. Maybe you have a runaway compile and you want it to stop.

> which re-enters
>redisplay right away, and will then wedge the same way as the one from
>which you wanted to escape.  And second, because it isn't restartable.

Sure it is. You can quit redisplay today and it restarts just fine. 

>So aborting redisplay is tricky at best.  We have an optional feature,
>by default off, which does that, see max-redisplay-ticks.  If you
>follow how this is implemented when we decide to abort, you will see
>it isn't simple.  And my personal experience from using this is that
>sometimes you are left with a partially-redrawn screen that you (as
>the user) don't always know how to fix.

If you don't want to support making redisplay interruptable, fine. Bind inhibit-quit around it in every invocation by default. read_char still has to do something with the Vquit_flag and the only sensible thing for this function to do is translating it to quit_char and returning normally.

>> Instead, the event reading functions should internally translate quits.to quit_char returns even if a quit happened internally. And they definitely shouldn't return with quit-flag set.
>
>How can this support breaking out of a runaway synchronous subprocess?
>Or any of the similar situations which today we can interrupt with a
>C-g? are we going to give up on them?

Huh? I'm talking only about translating quit to quit_char *inside read_char*, not in general!

If you're waiting on a synchronous subprocess, you're not in read_char --- not unless you have such a synchronous operation in, say, a timer, and you already can't quit one of those.

Basically, in the command loop, once we've decided to start running a command, then quits should be reflected as errors. If a quit happens while we're reading description of the command to run, control should always flow to the binding of quit_char in the appropriate keymaps. It's the only way to keep user experience consistent.

BTW: the problem isn't just with transient. It also manifests with read-extended-command! It's a nasty race that, AFAICT, has been with us since the 90s. I think defining read_char to translate quits to quit_char solves the problem.