Subject: Re: bug#39649: 27.0.60; tab-line doesn't scroll

From: Juri Linkov <[email-masked]>
Date: Mon, 02 Mar 2020 01:40:52 +0200

>> format-mode-line already exposes it, but fails to properly
>> handle its arg FACE.
>
> So you are saying that we already have the API exposed to Lisp, but it
> (format-mode-line) doesn't work well?

Exactly.

>> Do you see the same button styles with the faces tab-line-tab-inactive and
>> tab-line-tab-current as in the case above like they are on the real tab-line?
>> These faces are lost when the arg FACE is specified.
>
> I don't think this is a bug, the code behaves as intended.  It's just
> that its intent might be not what you expect, and the available
> documentation doesn't help to expect what the code does.
>
> The doc string says, inter alia:
>
>   Optional second arg FACE specifies the face property to put on all
>   characters for which no face is specified.

It says "no face is specified", but actually the face is specified
on all characters of tabs, and the implementation doesn't follow
the documentation, e.g.:

(tab-line-format)
=>
(#(" tab1 x" 1 5 (face tab-line-tab-current)))
                       ====================

The face 'tab-line-tab-current' is on all characters, yet
(format-mode-line (tab-line-format) 'tab-line)
overwrites the face 'tab-line-tab-current' with the face 'tab-line':

(format-mode-line (tab-line-format) 'tab-line)
=>
#(" tab1 x" 1 5 (face tab-line))
                      ========

> But what it really means is that text produced by the various
> %-constructs will have FACE if the %-constructs don't provide a face.
> In your case, you supply the function with a fixed string, so
> specifying FACE overrides the faces you have there, see
> store_mode_line_string (which is called in this case with PROPS set to
> nil).

Trying to find the minimal test case, it seems working correctly
by merging faces, so there is no bug in the simplest case:

(format-mode-line (propertize "tab1" 'face 'tab-line-tab-current))
=>
#("tab1" 0 4 (face tab-line-tab-current))

(format-mode-line (propertize "tab1" 'face 'tab-line-tab-current) 'tab-line)
=>
#("tab1" 0 4 (face (tab-line-tab-current tab-line)))

In the above case tab-line-tab-current and tab-line are merged.

But the bug creeps in only when there is space between tabs:

(format-mode-line (concat " " (propertize "tab1" 'face 'tab-line-tab-current) " x"))
#(" tab1 x" 1 5 (face tab-line-tab-current))

(format-mode-line (concat " " (propertize "tab1" 'face 'tab-line-tab-current) " x") 'tab-line)
#(" tab1 x" 0 1 (face tab-line) 1 5 (face tab-line) 5 7 (face tab-line))

Here the face 'tab-line' overwrites the face 'tab-line-tab-current'.

> At this point let me turn the table and ask why did you need to pass
> FACE to format-mode-line in your case?  What did you want to achieve?

With the arg FACE it should have the same look as on the real tab-line
where tabs are placed on the line that already has the face 'tab-line'.