Subject: Re: bug#21333: 25.0.50; window-size-change-functions not called after mini-window resize

From: Pip Cet <[email-masked]>
Date: Mon, 24 Aug 2015 18:13:29 +0000

Thank you for your response!

Sorry this is a long email, but I really, really want the
full-featured window-size-change-functions. I understand if you don't
have time to read it all, so let me summarize:

 - Redisplay is so slow that it really doesn't matter that we call an
extra bit of Lisp code before running it.

 - Doing it "my way" is cheap in terms of performance (I benchmarked),
easy to implement, enables cool but not necessarily useful hacks, but
requires very slow hook functions to rate-limit themselves in order to
keep the minibuffer usable.

 - Doing it your way is even cheaper in terms of performance, not very
hard to implement, requires those hacks to hook into
pre-redisplay-functions instead, and has the questionable benefit of
allowing badly-written non-rate-limiting hook functions to keep the
minibuffer usable, but still break drag-resizing.

On Mon, Aug 24, 2015 at 2:35 PM, Eli Zaretskii <[email-masked]> wrote:
>> Recipe: eval
>> (progn (push (lambda (&rest args) (message "window size changed"))
>> window-size-change-functions)
>>     (message (make-string 3000 ?*)))
>>
>> Expected result: a "window size changed" message.
>>
>> Actual result: no such message.
>>
>> The symptom is that the window size change function is not run after a
>> mini-window size change.
>
> Note that resizing the mini-window involves resizing of at least one
> other window on the same frame.  So this is not exactly about the
> mini-window.

Noted.

>> So far, I can produce this behavior only when the minibuffer or echo
>> area grows to several lines; when it shrinks afterwards, my window size
>> change function is called.
>
> I don't see the message even when the mini-window shrinks back.  Are
> you sure the message you see is not triggered by some other event?

No, I'm not sure, and that turns out to be correct. Thanks for helping
to clarify that point.

> It's way too easy to trigger it, see below.

I'm going to go into this in more detail, but I think that point can
be restated as "since it's called frequently, in most cases, the hook
function should return immediately after checking that the event isn't
interesting", and I agree with that.

>> I cannot reproduce the behavior with other windows.
>
> See above: at least one other window is resized in this recipe, so the
> exemption is not about the mini-window itself, it's about any window
> involved in resizing in this particular scenario.

I understand what's happening, but wanted to make clear that the
mini-window involvement is the decisive factor.

>> Is this a bug? The documentation says:
>>
>>     [...] to be called if the size of any window changes for any reason.
>>
>> Please correct me if I'm wrong, but when the minibuffer/echo area gets
>> resized (and the windows on top of it, too), that counts as a change of
>> size, I would say.
>
> I believe the original reason for this is largely historical:
> window-size-change-functions exists since Emacs 19.29, whereas
> automatic resizing of mini-window was introduced in Emacs 21.  Since
> Emacs before 21 didn't have the mini-window resizing functionality,
> Emacs 21 was careful not to gratuitously trigger these functions by
> something that is purely a new redisplay feature.
>
> That said, I wonder whether changing the code now to call these
> functions due to automatic resizing would make sense.  What would be
> the real-life use cases for using that?

I am beginning to suspect I'm missing something here. Is there another
way for windows to find out they're resized? There's pre-redisplay
function, but I believe that is called far, far more often.

The real-life use case I'm thinking of (which I'm not advocating as a
good idea, but am advocating strongly as something that it should be
possible to do) is EXWM, an X window manager written in Elisp that
creates dummy buffers and dynamically maps X windows to cover their
Emacs windows.

But it seems to me every attempt to make an Emacs window's graphical
representation fit in with its environment requires a way of knowing
when that window is resized; and, if it wants to warp the mouse cursor
(again, almost certainly not a good idea, but something that should be
possible) it needs to know when its position within the frame changed,
as well, since mouse coordinates are frame-relative.

For example, I don't see another way to implement dynamic
fit-image-to-window applications.

What I would consider a valid use case is to call
doc-view-fit-page-to-window if the window has been resized (of course,
a practical implementation would ensure that such automatic resize
operations would not cause a full re-rendering to happen upon every
size change, but that's an implementation detail). I suspect you will
object that in that case, there's no harm in the minibuffer
temporarily hiding some of the document, but imagine reading the last
line on a PDF page and, for some reason, deciding to type in some of
that text into the minibuffer: the minibuffer would resize,
obstructing the last line, and you could no longer see the text you're
trying to type in. (Yes, it's somewhat far-fetched, but you're the one
suggesting to change the documented behaviour of a feature to remove
it once and for all, and I'm the conservative one arguing we should
keep it.)

For something that's less far-fetched, I'm thinking of accessibility
and automation applications: if you want to point a screen reader to
the right text to read, you're going to need its X coordinates, which
might change due to a mini-window resize (either because you want the
screen reader to read the mini-window contents, or because a window
above the minibuffer is close to its minimum height and needs to move
vertically to accomodate the growing mini-window). And if your
accessibility utility is stupid (I understand most are) and only
allows you to enter text by warping the mouse pointer and generating
key press events, well, that's easy if you know how to update its idea
of where to type.

I can think of many X hacks that would probably be possible with or
without window-size-change-functions, but much easier to implement
with that hook: for example, while I doubt this could be made really
secure (you'd have to synchronize with x11vnc), you could clip x11vnc
to a given Emacs window without revealing the contents of the
minibuffer to the remote viewer.

Use cases:
 1. X hacks. I want to do something with the Emacs X windows and need
to know their coordinates to do so. EXWM, accessibility, compositing
WM hacks (transparency, duplicating windows, rendering them
upside-down, whatever), VNC clipping. Most of those are possible
without a reliable w-s-c-f hook, but are much easier to implement with
one.
 2. automatic-resize applications. These, strictly speaking, need to
know only the window size, not its frame-relative position.
 3. things I'm too stupid to think of. I think this is the big one:
let's leave the feature in and see what smart people come up with.

> I believe window-size-change-functions is meant for taking notice of
> resizes done by the user or some Lisp code, not for automated resizes
> whose sole purpose is to allow some message be read in its entirety.

I disagree with the notion that the possible applications of a
software feature are limited to what I can think of beforehand :-)

You appear to be arguing for considering the mini-window a temporary
overlay that obstructs "real" windows while it exists and goes away
when it's no longer needed. That's fine, you can have that: create a
minibuffer-only frame and raise or lower it to your heart's content.

I'm arguing that the mini-window should be treated like any other
window. That's what Emacs has historically implemented, it's still
clearly reflected in the code, and as you yourself point out,
auto-resizing affects all windows, not just mini-windows.

Let's also consider the potential harm done by both options: not
calling the hook when I think it should be will result in something
being done to the wrong buffer. That's potentially disastrous, either
because information is revealed that shouldn't be (my minibuffer
extended into the VNC-shared window) or not accessible when it should
be (while I specifically want the last line of my PDF to be visible at
all times, the "temporary" minibuffer has covered it). In the case of
EXWM, the mini-window will resize so the top of it is hidden
underneath another X window, rendering echo area messages unreadable.

Calling the hook "too often" will result, well, for most users, in
twenty lost CPU cycles as a variable is tested and found to be nil.
That's negligible. Those users that do have hooks, and specifically
only want to do work if the normal size of the window changed, need
one "equal" call to detect that window-normal-size hasn't changed and
no work needs to be done. That's not quite as negligible, but on
today's machines I think it's acceptable overhead to add to what's
most likely a user-triggered action. That leaves a third group, that
of users who have hooks and want them to do work only after explicit
resizes, but wrote buggy code and accidentally called window-size
instead of window-normal-size. While I sympathize with everyone who
writes buggy code, I do not think that third group should be
accommodated.

> If you agree, then the current behavior will make sense to you.

I hope I've made it clear so far that I disagree.

> If anything, IMO we should _reduce_ the number of unrelated events
> that trigger a call to these functions.  For example, currently any
> command that reads from the minibuffer will trigger it, because when
> read-from-minibuffer exits, it restores the window configuration by
> calling set-window-configuration, which is documented to trigger these
> functions.  That just doesn't make any sense to me, since most reads
> from the minibuffer don't resize any windows!

I believe that's a separate issue. From my point of view, it makes
sense to call our hook, since one window (the echo area) has been
replaced by another (the minibuffer).

I do not understand why you seem so loath to calling a rarely-used
hook somewhat more often; I conjecture you might be overestimating the
cost of checking whether a window's size has actually changed and
returning if it hasn't. I've attached a test file that uses
benchmark-run to time a million invocations of the test-and-return
code, and it takes less than two seconds on this very old machine;
IOW, to take up 1% of one CPU core, you'd need 5000 resize events per
second. I can run `redisplay' 20,000 times per second, and that's only
because those calls return without doing anything. It's cheap.

>> If this is merely a documentation issue, the exception should be noted
>> in the manual.
>
> That's easy.  Deciding what's TRT in this case is harder.

Let's go for doing what the documentation says. I'm thinking of your
implied proposal as two changes:
 1. rip out the current feature
 2. implement a new, crippled feature that satisfies some users of the
old feature but not all of them.

That's not "extensible". It's not conservative either, IMHO.

Maybe it would be useful to add to the documentation of the hook an
example of how to test whether a frame's current mini-window is at
what the manual calls its "normal size", and to suggest that expensive
hook functions not be run if it isn't. The exact code that does that
is left as an exercise to the reader (i.e. I haven't figured it out
yet).

>> If this behavior is deliberate, I believe it is inconsistent to set
>> FRAME_WINDOW_SIZES_CHANGED (f) in `resize-mini-window-internal'.
>
> It's consistent if we adopt the POV that this feature only catches
> resizes from Lisp code.

And those from explicit user interaction, yes. That's a valid POV but
not, I think, the one we should adopt.

Let me repeat that what I understand your POV to be is already fully
accommodated by minibuffer-only frames: they're temporary and obstruct
what's underneath them, but leave the window sizes in the real frame
alone. The converse does not hold: if we remove the feature/fail to
fix it, there's no good way to implement my bad ideas.

Let's go for "extensible" and "customizable" over "negligibly faster".

Sorry, again, that this email has gotten so long.

(This is a separate issue, but on first reading it, the
pre-redisplay-function(s) code seems like it would be a much better
candidate for optimization and elimination, though that might require
rewriting some of the simple.el code in C).

Thank you again for responding,
Pip