-
Notifications
You must be signed in to change notification settings - Fork 314
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
Conversation
Thanks for the pull request! Here is what will happen next:
Thank you for contributing! |
Signed-off-by: mwrock <matt@mattwrock.com>
@@ -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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/han/hab/
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>
There was a problem hiding this 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), |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
components/common/src/ui.rs
Outdated
@@ -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<()>; |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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(),
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
)
There was a problem hiding this comment.
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
components/common/src/ui.rs
Outdated
@@ -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<()>; | |||
} |
There was a problem hiding this comment.
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?
} | |
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)) | |
} | |
} |
There was a problem hiding this comment.
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.
components/common/src/ui.rs
Outdated
@@ -130,18 +132,18 @@ pub trait UIReader { | |||
fn prompt_yes_no(&mut self, question: &str, default: Option<bool>) -> Result<bool>; | |||
} | |||
|
|||
pub trait ColorPrinter { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is awesome!
Obvious fix; these changes are the result of automation not creative thinking.
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:
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 theterm
andansi_term
crates. Theterm
crate is no longer supported and so this has the added benefit of moving us off of that crate. This also replacesBlue
withMagenta
in ourUIWriter.end
function becauseblue
is virtually invisible with the default windows console backgrounds.Here is the same output above with the changes in this PR:
Signed-off-by: mwrock matt@mattwrock.com