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

Enable colored output on Windows #11020

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 103 additions & 103 deletions src/libextra/term.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,12 @@

use std::io::{Decorator, Writer};

#[cfg(not(target_os = "win32"))] use std::os;
#[cfg(not(target_os = "win32"))] use terminfo::*;
#[cfg(not(target_os = "win32"))] use terminfo::searcher::open;
#[cfg(not(target_os = "win32"))] use terminfo::parser::compiled::parse;
#[cfg(not(target_os = "win32"))] use terminfo::parm::{expand, Number, Variables};

// FIXME (#2807): Windows support.
use std::os;
use std::libc;
use terminfo::*;
use terminfo::searcher::open;
use terminfo::parser::compiled::parse;
use terminfo::parm::{expand, Number, Variables};

pub mod color {
pub type Color = u16;
Expand Down Expand Up @@ -74,7 +73,6 @@ pub mod attr {
}
}

#[cfg(not(target_os = "win32"))]
fn cap_for_attr(attr: attr::Attr) -> &'static str {
match attr {
attr::Bold => "bold",
Expand All @@ -93,29 +91,29 @@ fn cap_for_attr(attr: attr::Attr) -> &'static str {
}
}

#[cfg(not(target_os = "win32"))]
pub struct Terminal<T> {
priv num_colors: u16,
priv out: T,
priv ti: ~TermInfo
priv ti: Option<~TermInfo>
}

#[cfg(target_os = "win32")]
pub struct Terminal<T> {
priv num_colors: u16,
priv out: T,
}

#[cfg(not(target_os = "win32"))]
impl<T: Writer> Terminal<T> {
pub fn new(out: T) -> Result<Terminal<T>, ~str> {
let term = os::getenv("TERM");
if term.is_none() {
return Err(~"TERM environment variable undefined");
}
let term = match os::getenv("TERM") {
None => unsafe {
if libc::isatty(libc::STDOUT_FILENO) > 0 {
Copy link
Member

Choose a reason for hiding this comment

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

Stylistically, it is good to keep unsafe blocks as small as possible (e.g. so that one doesn't accidentally do something unsafe elsewhere), so in this case, if unsafe {libc::isatty(libc::STDOUT_FILENO)} > 0 {, or

let is_tty = unsafe {libc::isatty(libc::STDOUT_FILENO)};
if is_tty > 0 { ... }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay I will update that, thanks :)

return Ok(Terminal {out: out, ti: None, num_colors: 16});
Copy link
Member

Choose a reason for hiding this comment

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

Does every possible tty support at least 16 colours?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, but keep in mind that this only includes ttys which don't export TERM. Afaik only cmd.exe on Windows does that but I'm probably wrong. My idea was: It's worse to disable colors on a tty which supports it then to enable it on a tty which doesn't. Because the latter case is rare afaik.

}
return Err(~"TERM environment variable undefined")
},
Some(t) => t
};

let entry = open(term.unwrap());
let entry = open(term);
if entry.is_err() {
if term == ~"cygwin" || term.starts_with("xterm") {
return Ok(Terminal {out: out, ti: None, num_colors: 16});
}
return Err(entry.unwrap_err());
}

Expand All @@ -130,47 +128,60 @@ impl<T: Writer> Terminal<T> {
inf.numbers.find_equiv(&("colors")).map_default(0, |&n| n)
} else { 0 };

return Ok(Terminal {out: out, ti: inf, num_colors: nc});
return Ok(Terminal {out: out, ti: Some(inf), num_colors: nc});
}

/// Helper function, see fg and bg.
fn set_color(&mut self, color: color::Color, bg: bool) -> bool {
let color = self.dim_if_necessary(color);
if self.num_colors > color {
match self.ti {
None => {
let number = if bg { color + 10 } else { color };
let ansi = if number < 8 {
format!("\x1b[{}m", 30 + number)
Copy link
Member

Choose a reason for hiding this comment

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

Do we really want to be hard-coding colour escapes? Doesn't this code path get taken whenever rustc is connected to a tty, even if we don't know how it handles colours?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

AFAIK there's no other way since msys doesn't tell you the escape codes.

Regarding the second question see my comment above.

} else {
format!("\x1b[{};1m", 22 + number)
};
self.out.write(ansi.as_bytes());
return true
},
Some(ref ti) => {
let s = expand(*ti.strings.find_equiv(&(if bg {
"setab"
} else {
"setaf"
})).unwrap(), [Number(color as int)], &mut Variables::new());
if s.is_ok() {
self.out.write(s.unwrap());
return true
} else {
warn!("{}", s.unwrap_err());
}
}
}
}
false
}

/// Sets the foreground color to the given color.
///
/// If the color is a bright color, but the terminal only supports 8 colors,
/// the corresponding normal color will be used instead.
///
/// Returns true if the color was set, false otherwise.
pub fn fg(&mut self, color: color::Color) -> bool {
let color = self.dim_if_necessary(color);
if self.num_colors > color {
let s = expand(*self.ti.strings.find_equiv(&("setaf")).unwrap(),
[Number(color as int)], &mut Variables::new());
if s.is_ok() {
self.out.write(s.unwrap());
return true
} else {
warn!("{}", s.unwrap_err());
}
}
false
self.set_color(color, false)
}

/// Sets the background color to the given color.
///
/// If the color is a bright color, but the terminal only supports 8 colors,
/// the corresponding normal color will be used instead.
///
/// Returns true if the color was set, false otherwise.
pub fn bg(&mut self, color: color::Color) -> bool {
let color = self.dim_if_necessary(color);
if self.num_colors > color {
let s = expand(*self.ti.strings.find_equiv(&("setab")).unwrap(),
[Number(color as int)], &mut Variables::new());
if s.is_ok() {
self.out.write(s.unwrap());
return true
} else {
warn!("{}", s.unwrap_err());
}
}
false
self.set_color(color, true)
}

/// Sets the given terminal attribute, if supported.
Expand All @@ -180,18 +191,23 @@ impl<T: Writer> Terminal<T> {
attr::ForegroundColor(c) => self.fg(c),
attr::BackgroundColor(c) => self.bg(c),
_ => {
let cap = cap_for_attr(attr);
let parm = self.ti.strings.find_equiv(&cap);
if parm.is_some() {
let s = expand(*parm.unwrap(), [], &mut Variables::new());
if s.is_ok() {
self.out.write(s.unwrap());
return true
} else {
warn!("{}", s.unwrap_err());
match self.ti {
None => return false,
Some(ref ti) => {
let cap = cap_for_attr(attr);
let parm = ti.strings.find_equiv(&cap);
if parm.is_some() {
let s = expand(*parm.unwrap(), [], &mut Variables::new());
if s.is_ok() {
self.out.write(s.unwrap());
return true
} else {
warn!("{}", s.unwrap_err());
}
}
false
}
}
false
}
}
}
Expand All @@ -204,34 +220,44 @@ impl<T: Writer> Terminal<T> {
}
_ => {
let cap = cap_for_attr(attr);
self.ti.strings.find_equiv(&cap).is_some()
match self.ti {
None => return false,
Some(ref ti) => ti.strings.find_equiv(&cap).is_some()
}
}
}
}

/// Resets all terminal attributes and color to the default.
pub fn reset(&mut self) {
let mut cap = self.ti.strings.find_equiv(&("sgr0"));
if cap.is_none() {
// are there any terminals that have color/attrs and not sgr0?
// Try falling back to sgr, then op
cap = self.ti.strings.find_equiv(&("sgr"));
if cap.is_none() {
cap = self.ti.strings.find_equiv(&("op"));
match self.ti {
None => if self.num_colors > 0 {
self.out.write([27u8, 91u8, 51u8, 57u8, 59u8, 52u8, 57u8, 109u8])
},
Some(ref ti) => {
let mut cap = ti.strings.find_equiv(&("sgr0"));
if cap.is_none() {
// are there any terminals that have color/attrs and not sgr0?
// Try falling back to sgr, then op
cap = ti.strings.find_equiv(&("sgr"));
if cap.is_none() {
cap = ti.strings.find_equiv(&("op"));
}
}
let s = cap.map_default(Err(~"can't find terminfo capability `sgr0`"), |op| {
expand(*op, [], &mut Variables::new())
});
if s.is_ok() {
self.out.write(s.unwrap());
} else if self.num_colors > 0 {
warn!("{}", s.unwrap_err());
} else {
// if we support attributes but not color, it would be nice to still warn!()
// but it's not worth testing all known attributes just for this.
debug!("{}", s.unwrap_err());
}
}
}
let s = cap.map_default(Err(~"can't find terminfo capability `sgr0`"), |op| {
expand(*op, [], &mut Variables::new())
});
if s.is_ok() {
self.out.write(s.unwrap());
} else if self.num_colors > 0 {
warn!("{}", s.unwrap_err());
} else {
// if we support attributes but not color, it would be nice to still warn!()
// but it's not worth testing all known attributes just for this.
debug!("{}", s.unwrap_err());
}
}

fn dim_if_necessary(&self, color: color::Color) -> color::Color {
Expand All @@ -241,32 +267,6 @@ impl<T: Writer> Terminal<T> {
}
}

#[cfg(target_os = "win32")]
impl<T: Writer> Terminal<T> {
pub fn new(out: T) -> Result<Terminal<T>, ~str> {
return Ok(Terminal {out: out, num_colors: 0});
}

pub fn fg(&mut self, _color: color::Color) -> bool {
false
}

pub fn bg(&mut self, _color: color::Color) -> bool {
false
}

pub fn attr(&mut self, _attr: attr::Attr) -> bool {
false
}

pub fn supports_attr(&self, _attr: attr::Attr) -> bool {
false
}

pub fn reset(&self) {
}
}

impl<T: Writer> Decorator<T> for Terminal<T> {
fn inner(self) -> T {
self.out
Expand Down