bug#38181: Actual height of mode-line not taken into account

classic Classic list List threaded Threaded
38 messages Options
12
Reply | Threaded
Open this post in threaded view
|

bug#38181: Actual height of mode-line not taken into account

Jonas Bernoulli-5
If the height of the mode-line is increased by inserting an image into
it, then that height is not taken into account in a buffer/window until
`redisplay' has been called at least once since the buffer was created.

This function can be used for testing:

(defun test-popup ()
  (interactive)
  (with-current-buffer (generate-new-buffer "*test*")
    (save-excursion
      (insert "one\ntwo\nthree\nfour\nfive"))
    (let ((win (display-buffer (current-buffer)
                               '(display-buffer-in-side-window
                                 (side . bottom)))))
      ;; (redisplay)
      (fit-window-to-buffer win))))

First we can see that if we increase the height by modifying the
`mode-line' and `mode-line-inactive' faces, then that IS taken into
account.

(set-face-attribute 'mode-line nil :height 250)
(set-face-attribute 'mode-line-inactive nil :height 250)

Now decrease the height again and try this instead:

(setq-default
 mode-line-format
 (cons (propertize " " 'display
                   (create-image "/* XPM */ static char * image[] = {
\"3 60 1 1\",
\"0 c #00aaff\",
\"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\",
\"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\",
\"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\",
\"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\",
\"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\",
\"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\",
\"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\",
\"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\",
\"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\",
\"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\",
\"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\",
\"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\"
};" 'xpm t :ascent 'center))
       mode-line-format))

When you call the command now, then the lower parts of the buffer are
not visible because the window is not high enough.

A work-around is to call `redisplay' before calling
`fit-window-to-buffer', which could be done by advising that function.



Reply | Threaded
Open this post in threaded view
|

bug#38181: Actual height of mode-line not taken into account

martin rudalics
 > (setq-default
 >   mode-line-format
 >   (cons (propertize " " 'display
 >                     (create-image "/* XPM */ static char * image[] = {
 > \"3 60 1 1\",
 > \"0 c #00aaff\",
 > \"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\",
 > \"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\",
 > \"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\",
 > \"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\",
 > \"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\",
 > \"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\",
 > \"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\",
 > \"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\",
 > \"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\",
 > \"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\",
 > \"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\",
 > \"000\",\n\"000\",\n\"000\",\n\"000\",\n\"000\"
 > };" 'xpm t :ascent 'center))
 >         mode-line-format))


When I evaluate that specification, the scroll bars are screwed up for
all windows and remain so for all unselected windows.  So this problem
is not pertinent to 'fit-window-to-buffer' only.

martin



Reply | Threaded
Open this post in threaded view
|

bug#38181: Actual height of mode-line not taken into account

Eli Zaretskii
In reply to this post by Jonas Bernoulli-5
> From: Jonas Bernoulli <[hidden email]>
> Date: Tue, 12 Nov 2019 17:52:59 +0100
>
> If the height of the mode-line is increased by inserting an image into
> it, then that height is not taken into account in a buffer/window until
> `redisplay' has been called at least once since the buffer was created.

AFAIR, the mode line display is done lazily, because otherwise the
mode line would flicker.  When you change the face, that triggers a
thorough redisplay, because any change in faces does that (it could
mean many faces now have a different appearance).  Adding an image to
the mode line doesn't have that effect, which is why you need to force
redisplay manually.

So much for theory.  Now for the practical aspects: how bad is it to
have to invoke redisplay by hand in these cases?  After all, they
don't sound like something a Lisp program would do frequently.



Reply | Threaded
Open this post in threaded view
|

bug#38181: Actual height of mode-line not taken into account

Eli Zaretskii
In reply to this post by martin rudalics
> From: martin rudalics <[hidden email]>
> Date: Wed, 13 Nov 2019 09:03:56 +0100
>
> When I evaluate that specification, the scroll bars are screwed up for
> all windows and remain so for all unselected windows.

Doesn't happen here, FWIW.



Reply | Threaded
Open this post in threaded view
|

bug#38181: Actual height of mode-line not taken into account

Jonas Bernoulli-5
In reply to this post by Eli Zaretskii

Eli Zaretskii <[hidden email]> writes:

> So much for theory.  Now for the practical aspects: how bad is it to
> have to invoke redisplay by hand in these cases?  After all, they
> don't sound like something a Lisp program would do frequently.

Currently I do the manual redisplay by advising fit-window-to-buffer,
which I don't think can be avoided.  This results in some flickering,
so I make sure it is only done once per buffer.



Reply | Threaded
Open this post in threaded view
|

bug#38181: Actual height of mode-line not taken into account

Eli Zaretskii
> From: Jonas Bernoulli <[hidden email]>
> Cc: [hidden email]
> Date: Fri, 15 Nov 2019 15:24:11 +0100
>
>
> Eli Zaretskii <[hidden email]> writes:
>
> > So much for theory.  Now for the practical aspects: how bad is it to
> > have to invoke redisplay by hand in these cases?  After all, they
> > don't sound like something a Lisp program would do frequently.
>
> Currently I do the manual redisplay by advising fit-window-to-buffer,
> which I don't think can be avoided.  This results in some flickering,
> so I make sure it is only done once per buffer.

Why the need to use advising?  Your recipe shows that calling
redisplay before fit-window-to-buffer also solves the problem.  Can't
you do something like that only when you add such tall images to the
mode line?

An alternative would be to scale the image so that it doesn't enlarge
the mode line, btw.  Is that possible in your use cases?



Reply | Threaded
Open this post in threaded view
|

bug#38181: Actual height of mode-line not taken into account

Eli Zaretskii
In reply to this post by Eli Zaretskii
> Date: Fri, 15 Nov 2019 15:48:53 +0200
> From: Eli Zaretskii <[hidden email]>
> Cc: [hidden email]
>
> > From: Jonas Bernoulli <[hidden email]>
> > Date: Tue, 12 Nov 2019 17:52:59 +0100
> >
> > If the height of the mode-line is increased by inserting an image into
> > it, then that height is not taken into account in a buffer/window until
> > `redisplay' has been called at least once since the buffer was created.
>
> AFAIR, the mode line display is done lazily, because otherwise the
> mode line would flicker.  When you change the face, that triggers a
> thorough redisplay, because any change in faces does that (it could
> mean many faces now have a different appearance).  Adding an image to
> the mode line doesn't have that effect, which is why you need to force
> redisplay manually.

Btw, I was wrong above: enlarging the mode-line faces also causes a
similar problem.  Try this slightly modified recipe:

  (defun test-popup ()
    (interactive)
    (set-face-attribute 'mode-line nil :height 350)
    (set-face-attribute 'mode-line-inactive nil :height 350)
    (with-current-buffer (generate-new-buffer "*test*")
      (save-excursion
        (insert "one\ntwo\nthree\nfour\nfive"))
      (let ((win (display-buffer (current-buffer)
                                 '(display-buffer-in-side-window
                                   (side . bottom)))))
        (fit-window-to-buffer win))))

and you will see that the buffer *test* isn't shown in its entirety,
either.

I think fit-window-to-buffer relies on window's metrics (like the
number of lines in the text area) to be up to date, and that is only
true after a window was redisplayed once since changing the mode-line
height.  Martin, is this correct?



Reply | Threaded
Open this post in threaded view
|

bug#38181: Actual height of mode-line not taken into account

Jonas Bernoulli-5
In reply to this post by Eli Zaretskii

Eli Zaretskii <[hidden email]> writes:

> Why the need to use advising?  Your recipe shows that calling
> redisplay before fit-window-to-buffer also solves the problem.  Can't
> you do something like that only when you add such tall images to the
> mode line?

This is about packages like powerline, spaceline, doom-modeline and my
moody.  These packages add images to the global mode-line-format to make
them prettier.  These packages do not create any buffers of their own
and they never call fit-window-to-buffer themselves, but whenever that
function is called for a new buffer redisplay has to be run first, so
the advice is needed.

> An alternative would be to scale the image so that it doesn't enlarge
> the mode line, btw.  Is that possible in your use cases?

No because enlarging the mode-line is one of the things I did in order
to make it prettier (imo).  It's a goal not a means or side-effect.

You can see a screenshot on https://github.com/tarsius/moody.
Many other "mode-line prettifiers" also increase its height.



Reply | Threaded
Open this post in threaded view
|

bug#38181: Actual height of mode-line not taken into account

martin rudalics
In reply to this post by Eli Zaretskii
 > I think fit-window-to-buffer relies on window's metrics (like the
 > number of lines in the text area) to be up to date, and that is only
 > true after a window was redisplayed once since changing the mode-line
 > height.  Martin, is this correct?

Yes.  But, as I mentioned earlier, the problem shows up with the
scroll bar immediately when Jonas' form is evaluated, see the attached
screenshot.  Code like 'fit-window-to-buffer' works on the text area
only, it doesn't care whether a scroll bar or mode line changed size
since last redisplay.

martin

mode-line-vs-scroll-bar.png (25K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

bug#38181: Actual height of mode-line not taken into account

Eli Zaretskii
> Cc: [hidden email]
> From: martin rudalics <[hidden email]>
> Date: Sat, 16 Nov 2019 09:20:27 +0100
>
>  > I think fit-window-to-buffer relies on window's metrics (like the
>  > number of lines in the text area) to be up to date, and that is only
>  > true after a window was redisplayed once since changing the mode-line
>  > height.  Martin, is this correct?
>
> Yes.

Then I'm not sure we can fix such use cases at all, without causing
display flickering in much more popular use cases.  Do you have any
ideas for a possible fix?  Could fit-window-to-buffer invoke
'redisplay' internally, perhaps?

> But, as I mentioned earlier, the problem shows up with the
> scroll bar immediately when Jonas' form is evaluated, see the attached
> screenshot.

That just means we (or a Lisp program which makes these mode-line
modifications) need to recreate and redisplay the scroll bars anew in
these cases, right?  It's a separate problem, right?



Reply | Threaded
Open this post in threaded view
|

bug#38181: Actual height of mode-line not taken into account

Eli Zaretskii
In reply to this post by Jonas Bernoulli-5
> From: Jonas Bernoulli <[hidden email]>
> Cc: [hidden email]
> Date: Sat, 16 Nov 2019 00:51:19 +0100
>
> > An alternative would be to scale the image so that it doesn't enlarge
> > the mode line, btw.  Is that possible in your use cases?
>
> No because enlarging the mode-line is one of the things I did in order
> to make it prettier (imo).  It's a goal not a means or side-effect.

If this is to make the mode line prettier, then it should be done
once, at the beginning of a session, right?  In that case, why calling
redisplay after loading the package or enabling a feature is not a
solution?



Reply | Threaded
Open this post in threaded view
|

bug#38181: Actual height of mode-line not taken into account

martin rudalics
In reply to this post by Eli Zaretskii
 >>   > I think fit-window-to-buffer relies on window's metrics (like the
 >>   > number of lines in the text area) to be up to date, and that is only
 >>   > true after a window was redisplayed once since changing the mode-line
 >>   > height.  Martin, is this correct?
 >>
 >> Yes.
 >
 > Then I'm not sure we can fix such use cases at all, without causing
 > display flickering in much more popular use cases.  Do you have any
 > ideas for a possible fix?  Could fit-window-to-buffer invoke
 > 'redisplay' internally, perhaps?

It could but it's called way too often to warrant such behavior by
default.  But we could give it a separate optional argument so users
can avoid the advice.  I think Jonas could easily write and a test a
patch along this idea.

 >> But, as I mentioned earlier, the problem shows up with the
 >> scroll bar immediately when Jonas' form is evaluated, see the attached
 >> screenshot.
 >
 > That just means we (or a Lisp program which makes these mode-line
 > modifications) need to recreate and redisplay the scroll bars anew in
 > these cases, right?

Right.  But that same program could also redisplay all windows in
these cases, right?

 > It's a separate problem, right?

For some value of right ...

martin



Reply | Threaded
Open this post in threaded view
|

bug#38181: Actual height of mode-line not taken into account

Eli Zaretskii
> Cc: [hidden email], [hidden email]
> From: martin rudalics <[hidden email]>
> Date: Sat, 16 Nov 2019 09:57:40 +0100
>
>  > Then I'm not sure we can fix such use cases at all, without causing
>  > display flickering in much more popular use cases.  Do you have any
>  > ideas for a possible fix?  Could fit-window-to-buffer invoke
>  > 'redisplay' internally, perhaps?
>
> It could but it's called way too often to warrant such behavior by
> default.

What are the frequent calls?  Help and completions windows come to
mind, but those are interactive, and so I'm not sure I see why calling
redisplay would be a bad idea there.  Are there other callers which
I'm missing?

> But we could give it a separate optional argument so users can avoid
> the advice.

That's possible, of course.

>  >> But, as I mentioned earlier, the problem shows up with the
>  >> scroll bar immediately when Jonas' form is evaluated, see the attached
>  >> screenshot.
>  >
>  > That just means we (or a Lisp program which makes these mode-line
>  > modifications) need to recreate and redisplay the scroll bars anew in
>  > these cases, right?
>
> Right.  But that same program could also redisplay all windows in
> these cases, right?

I meant that calling redisplay should do this automatically.



Reply | Threaded
Open this post in threaded view
|

bug#38181: Actual height of mode-line not taken into account

Jonas Bernoulli-5
In reply to this post by Eli Zaretskii

Eli Zaretskii <[hidden email]> writes:

>> From: Jonas Bernoulli <[hidden email]>
>> Cc: [hidden email]
>> Date: Sat, 16 Nov 2019 00:51:19 +0100
>>
>> > An alternative would be to scale the image so that it doesn't enlarge
>> > the mode line, btw.  Is that possible in your use cases?
>>
>> No because enlarging the mode-line is one of the things I did in order
>> to make it prettier (imo).  It's a goal not a means or side-effect.
>
> If this is to make the mode line prettier, then it should be done
> once, at the beginning of a session, right?  In that case, why calling
> redisplay after loading the package or enabling a feature is not a
> solution?

No that won't work.  Each buffer has its own mode-line so when
a new buffer is created, then its height has to be calculated.

In practice all mode-lines have the same height, so if Emacs could
use the height "that all the other buffers/windows are using" when
it does not know the actual height, then that would help.



Reply | Threaded
Open this post in threaded view
|

bug#38181: Actual height of mode-line not taken into account

Jonas Bernoulli-5
In reply to this post by martin rudalics
>> Could fit-window-to-buffer invoke
>> 'redisplay' internally, perhaps?
>
> It could but it's called way too often to warrant such behavior by
> default.  But we could give it a separate optional argument so users
> can avoid the advice.  I think Jonas could easily write and a test a
> patch along this idea.

Again, the mode-line-prettifiers are not the ones who create new buffers
and then call fit-buffer-to-window.  It's arbitrary other packages that
do that.  An optional argument therefore would not help because when one
of the prettifier modes is active, then each and every third-party
caller of fit-buffer-to-window would have to pass that optional
argument.

This is the advice I currently use:

(defvar-local moody--size-hacked-p nil)

(defun moody-redisplay (&optional _force &rest _ignored)
  (unless moody--size-hacked-p
    (setq moody--size-hacked-p t)
    (redisplay t)))

(advice-add 'fit-window-to-buffer :before #'moody-redisplay)

Of course fit-buffer-to-window itself could be changed to do that and it
could also be taught to only do so iff the user opted in to doing it.

----

Creating and displaying a new buffer and creating and resizing a new
window surely *already* causes a "redisplay" without the programmer
having to explicitly call `redisplay'.  So if we explicitly tell
fit-window-to-buffer to redisplay, then that means that we are
redisplaying twice, right?

I am under the impression (but this is just wild speculation) that
redisplay only performs some of the necessary size calculations before
doing the actual redisplaying.  But some other calculations (including
those concerning the mode-lien) are done only after the actual
redisplaying has already happened.  That is too late for this redisplay
round but causes the values to be in place for all subsequent
redisplays.  So the fix could be to do the mode-line based calculations
earlier?



Reply | Threaded
Open this post in threaded view
|

bug#38181: Actual height of mode-line not taken into account

Eli Zaretskii
In reply to this post by Jonas Bernoulli-5
> From: Jonas Bernoulli <[hidden email]>
> Cc: [hidden email]
> Date: Sat, 16 Nov 2019 15:54:53 +0100
>
> > If this is to make the mode line prettier, then it should be done
> > once, at the beginning of a session, right?  In that case, why calling
> > redisplay after loading the package or enabling a feature is not a
> > solution?
>
> No that won't work.  Each buffer has its own mode-line so when
> a new buffer is created, then its height has to be calculated.

Ouch!

> In practice all mode-lines have the same height, so if Emacs could
> use the height "that all the other buffers/windows are using" when
> it does not know the actual height, then that would help.

fit-window-to-buffer doesn't use (or know, really) the height of the
mode line in the scenario you presented.  Instead, it gets the height
of the window's text area (which excludes the mode line and the header
line) from the data stored in the window object.  The problem is that
this data is recalculated only when the window is redisplayed, so
without the call to 'redisplay' we use stale data until the next
redisplay cycle.

However, if all the mode lines have the same height, then the problem
shouldn't have happened, and so now I wonder what am I missing.  If
you simulate the situation where the mode line changes, while keeping
its height (even if that height is unusually large), does the problem
with fit-window-to-buffer still happen?



Reply | Threaded
Open this post in threaded view
|

bug#38181: Actual height of mode-line not taken into account

Eli Zaretskii
In reply to this post by Jonas Bernoulli-5
> From: Jonas Bernoulli <[hidden email]>
> Cc: Eli Zaretskii <[hidden email]>, [hidden email]
> Date: Sat, 16 Nov 2019 16:27:12 +0100
>
> >> Could fit-window-to-buffer invoke
> >> 'redisplay' internally, perhaps?
> >
> > It could but it's called way too often to warrant such behavior by
> > default.  But we could give it a separate optional argument so users
> > can avoid the advice.  I think Jonas could easily write and a test a
> > patch along this idea.
>
> Again, the mode-line-prettifiers are not the ones who create new buffers
> and then call fit-buffer-to-window.  It's arbitrary other packages that
> do that.

So how are mode-line-prettifiers triggered by those packages creating
and showing new buffers?

> Of course fit-buffer-to-window itself could be changed to do that and it
> could also be taught to only do so iff the user opted in to doing it.

Can you suggest a way of knowing that this situation happened?

> Creating and displaying a new buffer and creating and resizing a new
> window surely *already* causes a "redisplay" without the programmer
> having to explicitly call `redisplay'.  So if we explicitly tell
> fit-window-to-buffer to redisplay, then that means that we are
> redisplaying twice, right?

Yes.  But if you don't call 'redisplay' _before_ fit-window-to-buffer,
that function will use stale data about the window's text area height,
computed before the mode line was updated.

You are right saying that displaying a window causes a redisplay, but
keep in mind that redisplay triggered by that happens _after_ the
command which enlarged the mode-line height finishes, and by that time
fit-window-to-buffer will have already run (using stale window
dimensions).

> I am under the impression (but this is just wild speculation) that
> redisplay only performs some of the necessary size calculations before
> doing the actual redisplaying.  But some other calculations (including
> those concerning the mode-lien) are done only after the actual
> redisplaying has already happened.  That is too late for this redisplay
> round but causes the values to be in place for all subsequent
> redisplays.  So the fix could be to do the mode-line based calculations
> earlier?

If you look at the code of redisplay_window, which is the function
that handles redisplay of each window, you will see that it indeed
calls display_mode_lines near its end (because the mode line includes
constructs that depend on position of point and other state, and that
could change as result of redisplaying a window).  However, if after
calling display_mode_lines we detect that the height of the mode line
changed, we schedule an immediate thorough redisplay.  So your theory
doesn't sound correct, at least not when taken at face value.

And once again, please keep in mind that by the time redisplay runs
(without an explicit call to 'redisplay' inside the recipe you
posted), 'fit-window-to-buffer' was already called, and it already
used stale value of height stored in the window object.



Reply | Threaded
Open this post in threaded view
|

bug#38181: Actual height of mode-line not taken into account

martin rudalics
In reply to this post by Eli Zaretskii
 > What are the frequent calls?  Help and completions windows come to
 > mind, but those are interactive, and so I'm not sure I see why calling
 > redisplay would be a bad idea there.  Are there other callers which
 > I'm missing?

The functions for displaying temporary buffers when
'temp-buffer-resize-mode' is on.  I do not think that the overhead for
calculating the mode line height is excessive.  It's just that IIUC
even Jonas changes the mode line height only once per window/buffer
assignment so even for him this overhead is practically always lost.

 >> Right.  But that same program could also redisplay all windows in
 >> these cases, right?
 >
 > I meant that calling redisplay should do this automatically.

By calculating the mode line before drawing the scroll bars?

martin



Reply | Threaded
Open this post in threaded view
|

bug#38181: Actual height of mode-line not taken into account

martin rudalics
In reply to this post by Jonas Bernoulli-5
 > Again, the mode-line-prettifiers are not the ones who create new buffers
 > and then call fit-buffer-to-window.  It's arbitrary other packages that
 > do that.  An optional argument therefore would not help because when one
 > of the prettifier modes is active, then each and every third-party
 > caller of fit-buffer-to-window would have to pass that optional
 > argument.

I see.  Maybe a function 'set-default-mode-line-format' would be
useful here.  Anyway: At the time you prettify a mode line or show a
new buffer in a window with a to be prettified mode line, would doing
an immediate redisplay work around future problems?

 > This is the advice I currently use:
 >
 > (defvar-local moody--size-hacked-p nil)
 >
 > (defun moody-redisplay (&optional _force &rest _ignored)
 >    (unless moody--size-hacked-p
 >      (setq moody--size-hacked-p t)
 >      (redisplay t)))
 >
 > (advice-add 'fit-window-to-buffer :before #'moody-redisplay)
 >
 > Of course fit-buffer-to-window itself could be changed to do that and it
 > could also be taught to only do so iff the user opted in to doing it.

If we don't want 'fit-window-to-buffer' to do that always we'd need
some variable, either buffer local or even a window parameter, that
'fit-window-to-buffer' would inspect once and reset immediately in
order to perform only the redisplay call that's really needed.

 > Creating and displaying a new buffer and creating and resizing a new
 > window surely *already* causes a "redisplay" without the programmer
 > having to explicitly call `redisplay'.  So if we explicitly tell
 > fit-window-to-buffer to redisplay, then that means that we are
 > redisplaying twice, right?

I think so.  Maybe 'fit-window-to-buffer' could use the string
returned by 'format-mode-line' instead and calculate its height
without redisplaying anything.

martin



Reply | Threaded
Open this post in threaded view
|

bug#38181: Actual height of mode-line not taken into account

martin rudalics
In reply to this post by Eli Zaretskii
 > However, if after
 > calling display_mode_lines we detect that the height of the mode line
 > changed, we schedule an immediate thorough redisplay.

... which, unfortunately, will not re-run ‘fit-window-to-buffer’ but
just use its earlier computed values.  Theoretically, 'redisplay'
could check the 'preserve-size' parameter of each window and try to
preserve the text height when resizing the mode line.  But I wouldn't
want to think of all the tricky details of such an approach.

martin




12