diff mbox series

[v3] git-prompt: make colourization consistent

Message ID 20220603142521.42863-1-joak-pet@online.no (mailing list archive)
State Superseded
Headers show
Series [v3] git-prompt: make colourization consistent | expand

Commit Message

Joakim Petersen June 3, 2022, 2:25 p.m. UTC
The short upstream state indicator inherits the colour of the last short
state indicator before it (if there is one), and the sparsity state
indicator inherits this colour as well. Make the colourization of these
state indicators consistent by clearing any colour before printing the
short upstream state indicator, as this immediately follows the last
coloured indicator.

As of 0ec7c23cdc6 (git-prompt: make upstream state indicator location
consistent, 2022-02-27), colourization in the output of __git_ps1 has
changed such that the short upstream state indicator inherits the colour
of the last short state indicator before it (if there is one), while
before this change it was white/the default text colour. Some examples
to illustrate this behaviour (assuming all indicators are enabled and
colourization is on):
 * If the local tree is clean and there is something in the stash, both
   the '$' and the short upstream state indicator following it will be
   blue.
 * If the local tree has new, untracked files, both the '%' and the
   short upstream state indicator will be red.
 * If all local changes are added to the index and the stash is empty,
   both the '+' and the short upstream state indicator following it will
   be green.
 * If the local tree is clean and there is nothing in the stash, the
   short upstream state indicator will be white/${default text colour}.

This appears to be an unintended side-effect of the change, and makes
little sense semantically (e.g. why is it bad to be in sync with
upstream when you have uncommitted local changes?). The cause of the
change is that previously, the short upstream state indicator appeared
immediately after the rebase/revert/bisect/merge state indicator (note
the position of $p in $gitstring):

	local f="$h$w$i$s$u"
	local gitstring="$c$b${f:+$z$f}${sparse}$r$p"
	
Said indicator is prepended with the clear colour code, and the short
upstream state indicator is thus also uncoloured. Now, the short
upstream state indicator follows the sequence of colourized indicators,
without any clearing of colour (again note the position of $p, now in
$f):

	local f="$h$w$i$s$u$p"
	local gitstring="$c$b${f:+$z$f}${sparse}$r${upstream}"

If the user is in a sparse checkout, the sparsity state indicator
follows a similar pattern to the short upstream state indicator.However,
adding a clearing of colour before the short upstream state indicator
will change how the sparsity state indicator is colourized only when the
short upstream state indicator is present, as it currently inherits (and
before the change referenced also inherited) the colour of the last
short state indicator before it. Reading the commit message of the
change that introduced the sparsity state indicator, afda36dbf3b
(git-prompt: include sparsity state as well, 2020-06-21), it appears
this colourization also was unintended, so clearing the colour for said
indicator further increases consistency.

Signed-off-by: Joakim Petersen <joak-pet@online.no>
---
Changes since v2:
 * Wrapped clearing of $p's colour in a check for emptiness to avoid
   multiple colour clears in the final gitstring.
 * Added clearing of colour for $sparse, as it wouldn't acutally be
   cleared consistently with only the change from the previous bullet
    - Having the short upstream state indicator disabled would leave the
      sparse state indicator as it is without this patch.

Range-diff against v2:
1:  e235caa7a8 < -:  ---------- git-prompt: make colourization consistent
-:  ---------- > 1:  0e107d0496 git-prompt: make colourization consistent

 contrib/completion/git-prompt.sh | 6 ++++++
 1 file changed, 6 insertions(+)

Comments

Junio C Hamano June 3, 2022, 4:38 p.m. UTC | #1
This is not a new issue, but seeing this:

 	if [ $detached = no ]; then
 		branch_color="$ok_color"
 	else
 		branch_color="$bad_color"
 	fi
 	c="$branch_color$c"
 
 	z="$c_clear$z"
 	if [ "$w" = "*" ]; then
 		w="$bad_color$w"
 	fi
 	if [ -n "$i" ]; then
 		i="$ok_color$i"
 	fi
 	if [ -n "$s" ]; then
 		s="$flags_color$s"
 	fi
 	if [ -n "$u" ]; then
 		u="$bad_color$u"
 	fi
+	if [ -n "$p" ]; then
+		p="$c_clear$p"
+	fi
+	if [ -n "$sparse" ]; then
+		sparse="$c_clear$sparse"
+	fi
 	r="$c_clear$r"
 }

it makes me wonder if the more forward looking and future-proof way
that is resistant to any future and random reshuffling like what
0ec7c23c (git-prompt: make upstream state indicator location
consistent, 2022-02-27) did would be to make it a rule to maintain
that there is no coloring by default, and when any of these tokens
like w, i, s, ... are not empty, enclose them inside "color-on" and
"color-off" sequence.

For example, 

 	if [ "$w" = "*" ]; then
 		w="$bad_color$w"
 	fi

would mean $w, when it is "*", would cause gitstring to contain an
asterisk that is painted in $bad_color, but ALSO causes whatever
that happens to come AFTER $w in gitstring to be painted in the same
color UNLESS it tries to protect itself.  Right now, $w may be
immediately followed by $i, and $i does protect itself by prefixing
with $ok_color, but if $i is empty, $w's coloring will extend to $s.

So, if we did this instead:

- 	z="$c_clear$z"
 	if [ "$w" = "*" ]; then
- 		w="$bad_color$w"
+ 		w="$bad_color$w$c_clear"
 	fi

and make similar changes to everything else we see above, we
probably can lose the ones that prefix with $c_clear, because each
token that paints itself in unusual color is now responsible for
returning the terminal state to normal with the $c_clear sequence
after it is done with it.  We do not have to special case sparse, p,
or r in this helper function at all if we go that route, no?

If the helper were written that way, then reshuffling the order of
the tokens done in 0ec7c23c (git-prompt: make upstream state
indicator location consistent, 2022-02-27) wouldn't have made the
patch under discussion necessary at all, which is what I see is
valuable from the "maintainability" point of view.
Joakim Petersen June 3, 2022, 5:23 p.m. UTC | #2
On 03/06/2022 18:38, Junio C Hamano wrote:
> This is not a new issue, but seeing this:
> 
>   	if [ $detached = no ]; then
>   		branch_color="$ok_color"
>   	else
>   		branch_color="$bad_color"
>   	fi
>   	c="$branch_color$c"
>   
>   	z="$c_clear$z"
>   	if [ "$w" = "*" ]; then
>   		w="$bad_color$w"
>   	fi
>   	if [ -n "$i" ]; then
>   		i="$ok_color$i"
>   	fi
>   	if [ -n "$s" ]; then
>   		s="$flags_color$s"
>   	fi
>   	if [ -n "$u" ]; then
>   		u="$bad_color$u"
>   	fi
> +	if [ -n "$p" ]; then
> +		p="$c_clear$p"
> +	fi
> +	if [ -n "$sparse" ]; then
> +		sparse="$c_clear$sparse"
> +	fi
>   	r="$c_clear$r"
>   }
> 
> it makes me wonder if the more forward looking and future-proof way
> that is resistant to any future and random reshuffling like what
> 0ec7c23c (git-prompt: make upstream state indicator location
> consistent, 2022-02-27) did would be to make it a rule to maintain
> that there is no coloring by default, and when any of these tokens
> like w, i, s, ... are not empty, enclose them inside "color-on" and
> "color-off" sequence.
> 
> For example,
> 
>   	if [ "$w" = "*" ]; then
>   		w="$bad_color$w"
>   	fi
> 
> would mean $w, when it is "*", would cause gitstring to contain an
> asterisk that is painted in $bad_color, but ALSO causes whatever
> that happens to come AFTER $w in gitstring to be painted in the same
> color UNLESS it tries to protect itself.  Right now, $w may be
> immediately followed by $i, and $i does protect itself by prefixing
> with $ok_color, but if $i is empty, $w's coloring will extend to $s.
> 
> So, if we did this instead:
> 
> - 	z="$c_clear$z"
>   	if [ "$w" = "*" ]; then
> - 		w="$bad_color$w"
> + 		w="$bad_color$w$c_clear"
>   	fi
> 
> and make similar changes to everything else we see above, we
> probably can lose the ones that prefix with $c_clear, because each
> token that paints itself in unusual color is now responsible for
> returning the terminal state to normal with the $c_clear sequence
> after it is done with it.  We do not have to special case sparse, p,
> or r in this helper function at all if we go that route, no?
> 
> If the helper were written that way, then reshuffling the order of
> the tokens done in 0ec7c23c (git-prompt: make upstream state
> indicator location consistent, 2022-02-27) wouldn't have made the
> patch under discussion necessary at all, which is what I see is
> valuable from the "maintainability" point of view.
> 

That does seem like a much better idea for maintainability, I can
change the patch to do this instead. I have one question, though: the
sequence $c$b (bare state and branch name) is a special case, where
they're intended to have the same colour, should I wrap both in colour
set, colour clear, or only clear after $b? The former requires rewriting
the tests or changing $gitstring to not include $c when $c is empty,
while the latter keeps the tests unchanged, but may pose a problem if
"BARE:" should at any point not appear immediately before the branch
name.
Joakim Petersen June 3, 2022, 6:51 p.m. UTC | #3
On 03/06/2022 19:23, Joakim Petersen wrote:
> That does seem like a much better idea for maintainability, I can
> change the patch to do this instead. I have one question, though: the
> sequence $c$b (bare state and branch name) is a special case, where
> they're intended to have the same colour, should I wrap both in colour
> set, colour clear, or only clear after $b? The former requires rewriting
> the tests or changing $gitstring to not include $c when $c is empty,
> while the latter keeps the tests unchanged, but may pose a problem if
> "BARE:" should at any point not appear immediately before the branch
> name.

Sorry, the former (colourizing and clearing $c and $b individually)
requires rewriting tests no matter what.
Justin Donnelly June 3, 2022, 7:43 p.m. UTC | #4
Hi all. I'm the author of 0ec7c23c (git-prompt: make upstream state
indicator location consistent, 2022-02-27). Sorry I'm a little late
jumping in. I was also going to propose something more comprehensive
and future-proof than what's there (adding the applicable color
(including clear) to all the indicators), but I like Junio's idea
better. The only other thing I have to add is that it's probably a
good idea to include a comment in the function
`__git_ps1_colorize_gitstring` explaining the design so future
developers/reviewers know.

Thanks,
Justin


On Fri, Jun 3, 2022 at 2:52 PM Joakim Petersen <joak-pet@online.no> wrote:
>
> On 03/06/2022 19:23, Joakim Petersen wrote:
> > That does seem like a much better idea for maintainability, I can
> > change the patch to do this instead. I have one question, though: the
> > sequence $c$b (bare state and branch name) is a special case, where
> > they're intended to have the same colour, should I wrap both in colour
> > set, colour clear, or only clear after $b? The former requires rewriting
> > the tests or changing $gitstring to not include $c when $c is empty,
> > while the latter keeps the tests unchanged, but may pose a problem if
> > "BARE:" should at any point not appear immediately before the branch
> > name.
>
> Sorry, the former (colourizing and clearing $c and $b individually)
> requires rewriting tests no matter what.
Junio C Hamano June 3, 2022, 8:50 p.m. UTC | #5
Joakim Petersen <joak-pet@online.no> writes:

> That does seem like a much better idea for maintainability, I can
> change the patch to do this instead. I have one question, though: the
> sequence $c$b (bare state and branch name) is a special case, where
> they're intended to have the same colour, should I wrap both in colour
> set, colour clear, or only clear after $b?

If we want to allow $c and $b appear in different places (which I
have no opinion on), I would say we should just color them
independently and fix the test that expects the close linkage
between the two.  I offhand see no reason that they _must_ stay
together myself, though.

Thanks.
Junio C Hamano June 3, 2022, 9:16 p.m. UTC | #6
Justin Donnelly <justinrdonnelly@gmail.com> writes:

> Hi all. I'm the author of 0ec7c23c (git-prompt: make upstream state
> indicator location consistent, 2022-02-27). Sorry I'm a little late
> jumping in. I was also going to propose something more comprehensive
> and future-proof than what's there (adding the applicable color
> (including clear) to all the indicators), but I like Junio's idea
> better. The only other thing I have to add is that it's probably a
> good idea to include a comment in the function
> `__git_ps1_colorize_gitstring` explaining the design so future
> developers/reviewers know.

After thinking it again, I actually am OK with the original coloring
code structure.  The rule is "you always counter whatever color
settings left behind by somebody who came before you".

As long as the color effect you use is not additive (e.g. if the
final product is $a$b, and $a is prefixed with $c_red and $b is
prefixed with $c_blue, an additive coloring scheme may end up
painting b in purple), we'll save number of $c_clear we would need
to emit.  Plain colors are probably not additive, but some
attributes are, so this is more brittle than "always reset to the
base state" rule, but it may be more desirable in practice.

I have no strong preference either way.  But if we are to go that
route, we definitely need to make sure that the last element added
to gitstring is followed by $c_reset, by doing something like the
attached patch.  Currently, $r has unconditional $c_clear in front
of it, and $upstream is never colored, and that is the only thing
that is saving us from leftover color bleeding into whatever comes
after the prompt.

 contrib/completion/git-prompt.sh | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git c/contrib/completion/git-prompt.sh w/contrib/completion/git-prompt.sh
index 87b2b916c0..c803b9fae5 100644
--- c/contrib/completion/git-prompt.sh
+++ w/contrib/completion/git-prompt.sh
@@ -287,6 +287,7 @@ __git_ps1_colorize_gitstring ()
 		u="$bad_color$u"
 	fi
 	r="$c_clear$r"
+	end_of_gitstring=$c_clear
 }
 
 # Helper function to read the first line of a file into a variable.
@@ -556,6 +557,7 @@ __git_ps1 ()
 
 	local z="${GIT_PS1_STATESEPARATOR-" "}"
 
+	local end_of_gitstring=
 	# NO color option unless in PROMPT_COMMAND mode or it's Zsh
 	if [ -n "${GIT_PS1_SHOWCOLORHINTS-}" ]; then
 		if [ $pcmode = yes ] || [ -n "${ZSH_VERSION-}" ]; then
@@ -570,7 +572,7 @@ __git_ps1 ()
 	fi
 
 	local f="$h$w$i$s$u$p"
-	local gitstring="$c$b${f:+$z$f}${sparse}$r${upstream}"
+	local gitstring="$c$b${f:+$z$f}${sparse}$r${upstream}${end_of_gitstring}"
 
 	if [ $pcmode = yes ]; then
 		if [ "${__git_printf_supports_v-}" != yes ]; then
Joakim Petersen June 4, 2022, 9:42 a.m. UTC | #7
On 03/06/2022 23:16, Junio C Hamano wrote:
> After thinking it again, I actually am OK with the original coloring
> code structure.  The rule is "you always counter whatever color
> settings left behind by somebody who came before you".
> 
> As long as the color effect you use is not additive (e.g. if the
> final product is $a$b, and $a is prefixed with $c_red and $b is
> prefixed with $c_blue, an additive coloring scheme may end up
> painting b in purple), we'll save number of $c_clear we would need
> to emit.  Plain colors are probably not additive, but some
> attributes are, so this is more brittle than "always reset to the
> base state" rule, but it may be more desirable in practice.
> 
> I have no strong preference either way.  But if we are to go that
> route, we definitely need to make sure that the last element added
> to gitstring is followed by $c_reset, by doing something like the
> attached patch.  Currently, $r has unconditional $c_clear in front
> of it, and $upstream is never colored, and that is the only thing
> that is saving us from leftover color bleeding into whatever comes
> after the prompt.

There might be something I'm not seeing, but having it so each element
counters whatever colour left by the preceding element seems less
intuitive when adding or moving elements in the final $gitstring. Adding
an element will then require going into __git_ps1_colorize_gitstring,
even when it is not intended to be colourized. All existing uncoloured
elements will also need to be prefixed to protect against colour bleed
from being moved around. I'm partial to the idea of each coloured
element clearing its own colour.
Junio C Hamano June 6, 2022, 4:13 p.m. UTC | #8
Joakim Petersen <joak-pet@online.no> writes:

> There might be something I'm not seeing, but having it so each element
> counters whatever colour left by the preceding element seems less
> intuitive when adding or moving elements in the final $gitstring. Adding
> an element will then require going into __git_ps1_colorize_gitstring,
> even when it is not intended to be colourized. All existing uncoloured
> elements will also need to be prefixed to protect against colour bleed
> from being moved around. I'm partial to the idea of each coloured
> element clearing its own colour.

I think that each makes sense in its own way.  Depending on what
assumption we can make on the use of terminal attributes, one can
produce shorter output than the other.

For example, if you have 3 things, A, B, and C, that are shown in
this order, the "clear after yourselves" scheme would give

	gitstring=<red>A<clear><blue>B<clear><green>C<clear>

while "clear the slate for yourself before you draw, the framework
will clear the effect of the last one" scheme can give

	gitstring=<red>A<blue>B<green>C<clear>

if we know that no additive terminal attributes are used, and the
latter gives a shorter output.

If we need to support some additive ones (like "reverse"), on the
other hand, and if each element is independent (i.e. "clear the
slate for me" cannot use the knowledge of what the previous one
did), then we have to write

	gitstring=<red>A<clear><blue>B<clear><green>C<clear>

for the latter, which becomes more verbose (but is the same as the
"each is on its own, clear after yourselves" version).

I have no strong preference either way myself.  "each on its own"
might be conceptually simpler and easier to understand and explain
what is going on.

Thanks.
diff mbox series

Patch

diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh
index 87b2b916c0..4997545ee5 100644
--- a/contrib/completion/git-prompt.sh
+++ b/contrib/completion/git-prompt.sh
@@ -286,6 +286,12 @@  __git_ps1_colorize_gitstring ()
 	if [ -n "$u" ]; then
 		u="$bad_color$u"
 	fi
+	if [ -n "$p" ]; then
+		p="$c_clear$p"
+	fi
+	if [ -n "$sparse" ]; then
+		sparse="$c_clear$sparse"
+	fi
 	r="$c_clear$r"
 }