Skip to content
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use console api instead of ansi codes on windows #6203

Merged
merged 10 commits into from
Feb 28, 2019
Merged

use console api instead of ansi codes on windows #6203

merged 10 commits into from
Feb 28, 2019

Conversation

mwrock
Copy link
Contributor

@mwrock mwrock commented Feb 23, 2019

Versions of Windows prior to Windows 10 and server 2016 do not support ANSI color control codes and render them as "gibberish" test. This means that on many "not so legacy" windows environments, Habitat output looks very unsightly.

Server 2012 R2:
image

image

The ubiquitous way to do color in a windows terminal has been to leverage the SetConsoleMode API.

The termcolor crate leverages that API on Windows providing cross platform terminal color support for all of the versions of Windows that Habitat supports.

This PR refactors our common::UI struct to use this crate and discards the term and ansi_term crates. The term crate is no longer supported and so this has the added benefit of moving us off of that crate. This also replaces Blue with Magenta in our UIWriter.end function because blue is virtually invisible with the default windows console backgrounds.

Here is the same output above with the changes in this PR:

image

image

Signed-off-by: mwrock matt@mattwrock.com

@thesentinels
Copy link
Contributor

Thanks for the pull request! Here is what will happen next:

  1. Your PR will be reviewed by the maintainers
  2. If everything looks good, one of them will approve it, and your PR will be merged.

Thank you for contributing!

Signed-off-by: mwrock <matt@mattwrock.com>
@mwrock
Copy link
Contributor Author

mwrock commented Feb 23, 2019

fixes #5922 and #2122

@@ -185,6 +169,17 @@ impl io::Write for CtlRequest {
// be nice to find a cleaner way to model this, but the fact
// that CtlRequest is sending output to two destinations with
// different formatting requirements complicates things a bit.
//
// TODO (MW): For `han sup run` scenarios, it would be nice to
Copy link
Contributor

Choose a reason for hiding this comment

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

s/han/hab/

@baumanj
Copy link
Contributor

baumanj commented Feb 25, 2019

Looks like you need to locally update to nightly rusfmt based on the CI failure.

Signed-off-by: mwrock <matt@mattwrock.com>
Signed-off-by: mwrock <matt@mattwrock.com>
Signed-off-by: mwrock <matt@mattwrock.com>
Signed-off-by: mwrock <matt@mattwrock.com>
Signed-off-by: mwrock <matt@mattwrock.com>
Copy link
Contributor

@baumanj baumanj left a comment

Choose a reason for hiding this comment

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

I haven't been through every line, but since I'm suggesting some potentially significant changes, I thought it best to get the conversation going.

Status::Verified => ('✓', "Verified".into(), Colour::Green),
Status::Verifying => ('☛', "Verifying".into(), Colour::Green),
Status::Custom(c, ref s) => (c, s.to_string(), Colour::Green),
Status::Applying => ('↑', "Applying".into(), Color::Green),
Copy link
Contributor

Choose a reason for hiding this comment

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

As long as we're making a change here, what about changing the code from using color in a purely descriptive way (i.e., "green") to a semantic one (e.g., "success")? That way we can set up these mappings in one place and provide a mechanism for re-mapping the specific color choices by the user if the scheme we chose doesn't work due to their terminal defaults or visual impairment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd prefer to leave that for another PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm ok with that, but can we file an issue and take it on as a direct follow-up? I'd like to take advantage of this code being in our headspace now.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would love to see this as a feature.

@@ -130,18 +132,18 @@ pub trait UIReader {
fn prompt_yes_no(&mut self, question: &str, default: Option<bool>) -> Result<bool>;
}

pub trait ColorPrinter {
fn print(&mut self, buf: &[u8], color: Option<Color>, bold: bool) -> io::Result<()>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of None, what about termcolor::NoColor?

Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of a bool for bold, can we use an enum that would make the semantics clearer from the caller? So instead of calls like:

        self.out().print(
            format!("{} {}\n", symbol, message).as_bytes(),
            Some(Color::Yellow),
            true,

we can have

        self.out().print(
            format!("{} {}\n", symbol, message).as_bytes(),
            Some(Color::Yellow),
            Weight::Bold,

Even better may be replacing both color and bold parameters with a termcolor::ColorSpec which can carry all that information and providing some convenience functions for returning instances configured with our common desired values. That would make the above something like:

        self.out().print(
            format!("{} {}\n", symbol, message).as_bytes(),
            TextStyle::yellow_bold(),

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you mean termcolor::NoColor vs an Option?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah a weight enum seems like it would be more clear. I had thought about just passing a ColorSpec but it felt like that made alot of the calling code unnecesarily verbose.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think making the core interface totally general (i.e., ColorSpec) and building convenience functions into the trait on top of that to make our common usage ergonomic would be good. Most importantly, I don't want the callers to pass parameters values like false and None, when we can include more evocative text like bold and yellow (or, down the road a semantic identifier like info or warning)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah we could add functions like fn create_no_color_spec() -> ColorSpec and fn create_color_spec(color: Color, weight: Weight) -> ColorSpec

@@ -130,18 +132,18 @@ pub trait UIReader {
fn prompt_yes_no(&mut self, question: &str, default: Option<bool>) -> Result<bool>;
}

pub trait ColorPrinter {
fn print(&mut self, buf: &[u8], color: Option<Color>, bold: bool) -> io::Result<()>;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

It's a common idiom in rust to provide output functions with a -ln suffixed variant. Since we do that several places here, why not provide a println as well?

Suggested change
}
fn println(&mut self, buf: &[u8], color: Option<Color>, bold: bool) -> io::Result<()> {
self.print(buf, color, bold)
.and_then(|_| self.print(&[b'\n'], None, false))
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah that makes total sense.

@@ -130,18 +132,18 @@ pub trait UIReader {
fn prompt_yes_no(&mut self, question: &str, default: Option<bool>) -> Result<bool>;
}

pub trait ColorPrinter {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why define our own trait when we could use termcolor::WriteColor?

Since our streams will implement Write as well, we can use the standard write and writeln macros.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Having a one liner color printing call seemed much simpler. I ended up adding this trait for the sake of ctrl-gateway/src/mod.rs. Not sure how you would handle its needs and implement WriteColor.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'll defer to your judgement for what gives better ergonomics in the end. The nice thing about building upon termcolor::WriteColor is that we could keep a single code path for all our printing and just enable/disable colors with ColorChoice.

If we find code like this

buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
writeln!(&mut buffer, "green text!")?;
bufwtr.print(&buffer)?;

to be too verbose, there's no reason we can't build helpers on top of it like:

our_helper.print("green text!", Color::Green, Weight::Normal)?;

or

our_helper.set_color(Green).print("green text!")?;

while still retaining the logic-unifying goodness of termcolor::WriteColor in the underlying implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah my first draft used WriteColor but that breaks down in the ctrl-gateway when sending colorized messages through the ConsoleLine protocol where the actual coloring and underlying console API calls need to happen on the gateway client.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you show me a little more specifically where your approach ran into trouble?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was hoping that the termcolor::Buffer would be serializable but its not :( Maybe you know of a way to send a Buffer through prost but I think it has to implement Serializable

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm thinking of this more and I think I see how to do this with WriteColor. I'll play with it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds great. Let me know if you want input on anything.

Signed-off-by: mwrock <matt@mattwrock.com>
Signed-off-by: mwrock <matt@mattwrock.com>
Signed-off-by: mwrock <matt@mattwrock.com>
Signed-off-by: mwrock <matt@mattwrock.com>
Copy link
Contributor

@baumanj baumanj left a comment

Choose a reason for hiding this comment

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

This is awesome!

@mwrock mwrock merged commit 575762b into master Feb 28, 2019
@mwrock mwrock deleted the termcolor branch February 28, 2019 19:09
chef-ci added a commit that referenced this pull request Feb 28, 2019
Obvious fix; these changes are the result of automation not creative thinking.
@christophermaier christophermaier added Type:BugFixes PRs that fix an existing bug Focus:Supervisor Related to the Habitat Supervisor (core/hab-sup) component Focus: CLI Related to the Habitat CLI (core/hab) component Platform: Windows Deals with Windows-specific behavior Type: Bug Issues that describe broken functionality and removed X-fix labels Jul 24, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Focus: CLI Related to the Habitat CLI (core/hab) component Focus:Supervisor Related to the Habitat Supervisor (core/hab-sup) component Platform: Windows Deals with Windows-specific behavior Type: Bug Issues that describe broken functionality Type:BugFixes PRs that fix an existing bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants