Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

fix: use ANSI colors codes instead of RGB#14665

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
DanielleMaywood merged 23 commits intomainfromdm-cli-color-theme-change
Sep 17, 2024

Conversation

DanielleMaywood
Copy link
Contributor

@DanielleMaywoodDanielleMaywood commentedSep 13, 2024
edited
Loading

Fixes#13211

Instead of using RGB color codes, we instead use ANSI color codes. This means that we respect a user's terminal theme. Not all of the new colors match the old ones but this is a limitation with using 4-bit ANSI colors.

This change also allows users to pass--no-color to the CLI to completely disable theming. It is worth noting that whilst we do define theserpent.Option, we manually checkos.Args for the flag as there doesn't appear to be a way to have a global middleware withserpent (whereasspf13/cobra does have this).

This change also removes> from Prompt, and replaces it with as on--no-color there is no difference.

Before:
Screenshot 2024-09-13 at 15 08 26

After:
Screenshot 2024-09-13 at 15 09 15

Before:
Screenshot 2024-09-13 at 15 08 34

After:
Screenshot 2024-09-13 at 15 09 30

Before:
Screenshot 2024-09-13 at 15 08 45

After:
Screenshot 2024-09-13 at 15 09 39

Before:
Screenshot 2024-09-13 at 15 08 54

After:
Screenshot 2024-09-13 at 15 09 50

With--no-color Now requiresNO_COLOR environment variable
Screenshot 2024-09-13 at 15 10 09

@DanielleMaywoodDanielleMaywood marked this pull request as ready for reviewSeptember 13, 2024 13:53
@DanielleMaywoodDanielleMaywood marked this pull request as draftSeptember 13, 2024 14:01
@DanielleMaywoodDanielleMaywood marked this pull request as ready for reviewSeptember 13, 2024 14:24
Copy link
Member

@johnstcnjohnstcn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Nice work! Just some nits on my end.
Smoke-tested example prompts with and without colour.

cli/root.go Outdated
@@ -461,6 +461,12 @@ func (r *RootCmd) Command(subcommands []*serpent.Command) (*serpent.Command, err
Value: serpent.StringOf(&r.globalConfig),
Group: globalGroup,
},
{
Flag: cliui.NoColorFlag,
Copy link
Member

@johnstcnjohnstcnSep 13, 2024
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Wonder if it would make sense to also allow setting it as an env (CODER_NO_COLOR)? Not blocking, but it would allow folks for whom our colour scheme doesn't work to simply turn it off forever by setting it in their shell profile.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Yes that should be a good idea. There is a 'standard' around usingNO_COLOR for thishttps://no-color.org. We could do bothNO_COLOR andCODER_NO_COLOR?

johnstcn reacted with thumbs up emoji
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Sounds good to me!

Comment on lines +46 to +52
red = Color("1")
green = Color("2")
yellow = Color("3")
magenta = Color("5")
white = Color("7")
brightBlue = Color("12")
brightMagenta = Color("13")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

nit: indicate these are ANSI color codes

Comment on lines 129 to 133
func ifTerm(f pretty.Formatter) pretty.Formatter {
if !isTerm() {
return pretty.Nop
}
returnfmt
returnf
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

👍

}

root.Children = append(root.Children, &serpent.Command{
Use: "colors",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

👍 👍

Copy link
Member

@mafredrimafredri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I think the global NoColor var could cause a race in our testing since we run things concurrently. E.g. if multiple tests incli package set--no-color option, we'd run into it.

Other than that, I think this looks great. Might be worth looking into improving serpent so we can do this in a better way. And perhaps get rid of the package-global state in favor of a "color" instance. That's a much more intrusive change though.

cli/root.go Outdated
@@ -461,6 +461,12 @@ func (r *RootCmd) Command(subcommands []*serpent.Command) (*serpent.Command, err
Value: serpent.StringOf(&r.globalConfig),
Group: globalGroup,
},
{
Flag: cliui.NoColorFlag,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Suggestion: I think we should add the env as well here.

// has the flag.
if slices.Contains(os.Args, fmt.Sprintf("--%s", NoColorFlag)) {
color = termenv.Ascii
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Is this needed? I see that the serpent options modify the global var in this package, so checkingif NoColor should suffice?

That said, I really wish we could do it differently as modifying globals is a bit icky 😅

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Unfortunately not asNoColor is setafter our code runs. This is because there is no (apparent) way to have this initialization code ran beforeevery command but after serpent sets up the options.

pretty.FgColor(Red),
pretty.BgColor(color.Color("#2c2c2c")),
pretty.FgColor(color.Color("#ED567A")),
pretty.BgColor(color.Color("#2C2C2C")),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Were there no acceptable ANSI colors to replace these? Why did we go from red to ed567a?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

As we set both the foreground and background colour, we have complete control over the contrast so this looks the same regardless of the terminal theme. If we want to use an ANSI code instead we can but there isn't a need to here.

red was defined as#ED567A, and the newred didn't look as good on the background so I kept it as the old one.

@johnstcn
Copy link
Member

I think the global NoColor var could cause a race in our testing since we run things concurrently.

@mafredri potentially, yes -- but we already check fortest.v and skip colours. So I don't think we'd even hit this as long as the check fortest.v came first.

@DanielleMaywood
Copy link
ContributorAuthor

I think the global NoColor var could cause a race in our testing since we run things concurrently. E.g. if multiple tests incli package set--no-color option, we'd run into it.

I should probably have documented this but we don't actually use the global var, I needed it to set it incmd/cliui but it appears now removing it fromcmd/coder doesn't cause the same issue so I should be able to remove this.

We can't make use of it as serpent sets it too late as we can't hook into serpent before it run commands.

@mafredri
Copy link
Member

mafredri commentedSep 13, 2024
edited
Loading

@mafredri potentially, yes -- but we already check fortest.v and skip colours. So I don't think we'd even hit this as long as the check fortest.v came first.

Updating the var from multiple locations is still a data race, can be seen in CI here:https://github.com/coder/coder/actions/runs/10851466684/job/30115309148?pr=14665#step:5:5121

Edit: I just checked the first instance of data race and linked it, stack trace doesn't really say that it's due to the global var AFAICT 😂

@mafredri
Copy link
Member

mafredri commentedSep 13, 2024
edited
Loading

I should probably have documented this but we don't actually use the global var, I needed it to set it incmd/cliui but it appears now removing it fromcmd/coder doesn't cause the same issue so I should be able to remove this.

Alright, then I think using a local var to the serpent command would be better. 👍🏻 (+ comment explaining it's purpose)

@DanielleMaywood
Copy link
ContributorAuthor

I should probably have documented this but we don't actually use the global var, I needed it to set it incmd/cliui but it appears now removing it fromcmd/coder doesn't cause the same issue so I should be able to remove this.

Turns out serpent behaves differently withserpent.BoolOf versusserpent.Discard. BoolOf allows just writing--no-color but we can't do that withDiscard.

@DanielleMaywood
Copy link
ContributorAuthor

Alright, then I think using a local var to the serpent command would be better. 👍🏻 (+ comment explaining it's purpose)

So define a local var inroot.go such asnoColorDiscarded?

// serpent as it isn't possible to create a root middleware that
// runs for every command. For now we just check if `os.Args`
// has the flag.
if slices.Contains(os.Args, fmt.Sprintf("--%s", NoColorFlag)) ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This should also support--no-color=true and--no-color=true (omitting= means true) to be consistent with our CLI. Ideally we'd rely on serpent parsing here but I understand that not easily achieved.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Can do!

--no-color=true and--no-color=true

Do my eyes deceive me or are these both the same?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

A case of copy-pasta 😂, sorry. Meantfalse for the second.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Got you, just pushed a change with--no-color=true handling. It appears we already handle the--no-color=false case!

@mafredri
Copy link
Member

So define a local var inroot.go such asnoColorDiscarded?

Yep, insidefunc (r *RootCmd) Command(subcommands []*serpent.Command) (*serpent.Command, error) 👍🏻.

DanielleMaywood reacted with thumbs up emoji

if flag.Lookup("test.v") != nil {
// Use a consistent colorless profile in tests so that results
// are deterministic.
color = termenv.Ascii
}

// Currently it appears there is no way to use the flag from
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Have you tried setting the option on the root command? It should then evaluate on every command.

// serpent as it isn't possible to create a root middleware that
// runs for every command. For now we just check if `os.Args`
// has the flag.
if slices.Contains(os.Args, fmt.Sprintf("--%s", NoColorFlag)) ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This is super janky. Relying on os.Args makes it difficult to test. Also, someone could modify the option at the CLI layer and easily forget to update this. Have you considered checking the CLI option higher up in the stack?

Copy link
ContributorAuthor

@DanielleMaywoodDanielleMaywoodSep 13, 2024
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I absolutely agree with you, however serpent doesn't (from what I can tell) have any way to support this as it lacks the ability to set a middleware that runs beforeany command.

To demonstrate what I mean, the code inthis gist has aMiddleware setup but it only runs on the root cmd and not it's children. If you know of a way to have this run before a commands handler but after options are setup I'd love to do that instead!

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Scratch that: I think usingrootCmd.Walk and injecting the middleware to every child should work? Will have a go at doing that.

Copy link
ContributorAuthor

@DanielleMaywoodDanielleMaywoodSep 13, 2024
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I've pushed a change that should be less janky (using the method describe in the prior message), let me know what you think!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

If you do the Walk approach we have the flag duplicated on every help, which is obnoxious. Can't you use an Option on the root command?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Sorry I think I explained myself poorly, the Walk was to inject middleware onto every command. The Option was on the root command. This change was backed out anyways as mutating theDefaultStyles afterfunc init() was causing Go's race checker to be unhappy.

ammario reacted with thumbs up emoji
We still support `NO_COLOR` env variable through termenv's`EnvColorProfile`. The reason for the race condition is that`DefaultStyles` is a global that we shouldn't mutate after `init`is called, but we have to mutate it after `init` has ran to haveserpent collect the cli flags and env vars for us.
@DanielleMaywood
Copy link
ContributorAuthor

I've ripped out all the logic for--no-color as mutatingDefaultStyles whilst the program is running causes a race condition that is non-trivial to solve due to usingDefaultStyles around 150 times in the codebase. The behaviour can still be enabled by setting theNO_COLOR (https://no-color.org/) environment variable by using termenv'sEnvColorProfile instead ofColorProfile.

@@ -37,6 +38,43 @@ func main() {
},
}

root.Children = append(root.Children, &serpent.Command{
Use: "colors",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This should be hidden

DanielleMaywood reacted with thumbs up emoji
Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Done!

Use: "colors",
Handler: func(inv *serpent.Invocation) error {
msg := pretty.Sprint(cliui.DefaultStyles.Code, "This is a code message")
_, _ = fmt.Fprintln(inv.Stdout, msg)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

You can simplify this withpretty.Fprintf

DanielleMaywood reacted with thumbs up emoji
Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Done! I had to separate the newline from the Fprintf call though as otherwise the newline would get themed.

ammario reacted with thumbs up emoji
Copy link
Member

@ammarioammario left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Should prob only merge when one of the core devs approve. I generally don't want to take accountability as a review since my knowledge of the internals has atrophied.

DanielleMaywood reacted with thumbs up emoji
Copy link
Member

@mafredrimafredri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Looks great. I think relying on ansi colors where we don't use a background, and hex colors where we do, is indeed the simplest approach.

Thanks for working through all the iterations of this and glad we could land on a simple solution in the end. 👍🏻

)

// Color returns a color for the given string.
func Color(s string) termenv.Color {
colorOnce.Do(func() {
color = termenv.NewOutput(os.Stdout).ColorProfile()
color = termenv.NewOutput(os.Stdout).EnvColorProfile()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Good solution for disabling colors 👍🏻

@DanielleMaywoodDanielleMaywood merged commit14d3e30 intomainSep 17, 2024
26 checks passed
@DanielleMaywoodDanielleMaywood deleted the dm-cli-color-theme-change branchSeptember 17, 2024 13:21
@github-actionsgithub-actionsbot locked and limited conversation to collaboratorsSep 17, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account?Sign in.
Reviewers

@johnstcnjohnstcnjohnstcn approved these changes

@mafredrimafredrimafredri approved these changes

@ammarioammarioammario approved these changes

Assignees

@DanielleMaywoodDanielleMaywood

Labels
None yet
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

coder cli colors override terminal's theming
4 participants
@DanielleMaywood@johnstcn@mafredri@ammario

[8]ページ先頭

©2009-2025 Movatter.jp