use std::{
env,
fmt::{self, Display},
};
use crate::command::execute_fmt;
use crate::{csi, impl_display, Command};
pub use self::{
attributes::Attributes,
content_style::ContentStyle,
styled_content::StyledContent,
stylize::Stylize,
types::{Attribute, Color, Colored, Colors},
};
mod attributes;
mod content_style;
mod styled_content;
mod stylize;
mod sys;
mod types;
pub fn style<D: Display>(val: D) -> StyledContent<D> {
ContentStyle::new().apply(val)
}
pub fn available_color_count() -> u16 {
#[cfg(windows)]
{
if crate::ansi_support::supports_ansi() {
return u16::MAX;
}
}
const DEFAULT: u16 = 8;
env::var("COLORTERM")
.or_else(|_| env::var("TERM"))
.map_or(DEFAULT, |x| match x {
_ if x.contains("24bit") || x.contains("truecolor") => u16::MAX,
_ if x.contains("256") => 256,
_ => DEFAULT,
})
}
pub fn force_color_output(enabled: bool) {
Colored::set_ansi_color_disabled(!enabled)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SetForegroundColor(pub Color);
impl Command for SetForegroundColor {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, csi!("{}m"), Colored::ForegroundColor(self.0))
}
#[cfg(windows)]
fn execute_winapi(&self) -> std::io::Result<()> {
sys::windows::set_foreground_color(self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SetBackgroundColor(pub Color);
impl Command for SetBackgroundColor {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, csi!("{}m"), Colored::BackgroundColor(self.0))
}
#[cfg(windows)]
fn execute_winapi(&self) -> std::io::Result<()> {
sys::windows::set_background_color(self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SetUnderlineColor(pub Color);
impl Command for SetUnderlineColor {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, csi!("{}m"), Colored::UnderlineColor(self.0))
}
#[cfg(windows)]
fn execute_winapi(&self) -> std::io::Result<()> {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"SetUnderlineColor not supported by winapi.",
))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SetColors(pub Colors);
impl Command for SetColors {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
match (self.0.foreground, self.0.background) {
(Some(fg), Some(bg)) => {
write!(
f,
csi!("{};{}m"),
Colored::ForegroundColor(fg),
Colored::BackgroundColor(bg)
)
}
(Some(fg), None) => write!(f, csi!("{}m"), Colored::ForegroundColor(fg)),
(None, Some(bg)) => write!(f, csi!("{}m"), Colored::BackgroundColor(bg)),
(None, None) => Ok(()),
}
}
#[cfg(windows)]
fn execute_winapi(&self) -> std::io::Result<()> {
if let Some(color) = self.0.foreground {
sys::windows::set_foreground_color(color)?;
}
if let Some(color) = self.0.background {
sys::windows::set_background_color(color)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SetAttribute(pub Attribute);
impl Command for SetAttribute {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, csi!("{}m"), self.0.sgr())
}
#[cfg(windows)]
fn execute_winapi(&self) -> std::io::Result<()> {
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SetAttributes(pub Attributes);
impl Command for SetAttributes {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
for attr in Attribute::iterator() {
if self.0.has(attr) {
SetAttribute(attr).write_ansi(f)?;
}
}
Ok(())
}
#[cfg(windows)]
fn execute_winapi(&self) -> std::io::Result<()> {
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SetStyle(pub ContentStyle);
impl Command for SetStyle {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
if let Some(bg) = self.0.background_color {
execute_fmt(f, SetBackgroundColor(bg)).map_err(|_| fmt::Error)?;
}
if let Some(fg) = self.0.foreground_color {
execute_fmt(f, SetForegroundColor(fg)).map_err(|_| fmt::Error)?;
}
if let Some(ul) = self.0.underline_color {
execute_fmt(f, SetUnderlineColor(ul)).map_err(|_| fmt::Error)?;
}
if !self.0.attributes.is_empty() {
execute_fmt(f, SetAttributes(self.0.attributes)).map_err(|_| fmt::Error)?;
}
Ok(())
}
#[cfg(windows)]
fn execute_winapi(&self) -> std::io::Result<()> {
panic!("tried to execute SetStyle command using WinAPI, use ANSI instead");
}
#[cfg(windows)]
fn is_ansi_code_supported(&self) -> bool {
true
}
}
#[derive(Debug, Copy, Clone)]
pub struct PrintStyledContent<D: Display>(pub StyledContent<D>);
impl<D: Display> Command for PrintStyledContent<D> {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
let style = self.0.style();
let mut reset_background = false;
let mut reset_foreground = false;
let mut reset = false;
if let Some(bg) = style.background_color {
execute_fmt(f, SetBackgroundColor(bg)).map_err(|_| fmt::Error)?;
reset_background = true;
}
if let Some(fg) = style.foreground_color {
execute_fmt(f, SetForegroundColor(fg)).map_err(|_| fmt::Error)?;
reset_foreground = true;
}
if let Some(ul) = style.underline_color {
execute_fmt(f, SetUnderlineColor(ul)).map_err(|_| fmt::Error)?;
reset_foreground = true;
}
if !style.attributes.is_empty() {
execute_fmt(f, SetAttributes(style.attributes)).map_err(|_| fmt::Error)?;
reset = true;
}
write!(f, "{}", self.0.content())?;
if reset {
execute_fmt(f, ResetColor).map_err(|_| fmt::Error)?;
} else {
if reset_background {
execute_fmt(f, SetBackgroundColor(Color::Reset)).map_err(|_| fmt::Error)?;
}
if reset_foreground {
execute_fmt(f, SetForegroundColor(Color::Reset)).map_err(|_| fmt::Error)?;
}
}
Ok(())
}
#[cfg(windows)]
fn execute_winapi(&self) -> std::io::Result<()> {
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResetColor;
impl Command for ResetColor {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
f.write_str(csi!("0m"))
}
#[cfg(windows)]
fn execute_winapi(&self) -> std::io::Result<()> {
sys::windows::reset()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Print<T: Display>(pub T);
impl<T: Display> Command for Print<T> {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, "{}", self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> std::io::Result<()> {
panic!("tried to execute Print command using WinAPI, use ANSI instead");
}
#[cfg(windows)]
fn is_ansi_code_supported(&self) -> bool {
true
}
}
impl<T: Display> Display for Print<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl_display!(for SetForegroundColor);
impl_display!(for SetBackgroundColor);
impl_display!(for SetColors);
impl_display!(for SetAttribute);
impl_display!(for PrintStyledContent<String>);
impl_display!(for PrintStyledContent<&'static str>);
impl_display!(for ResetColor);
fn parse_next_u8<'a>(iter: &mut impl Iterator<Item = &'a str>) -> Option<u8> {
iter.next().and_then(|s| s.parse().ok())
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! skip_windows_ansi_supported {
() => {
#[cfg(windows)]
{
if crate::ansi_support::supports_ansi() {
return;
}
}
};
}
#[cfg_attr(windows, test)]
#[cfg(windows)]
fn windows_always_truecolor() {
if crate::ansi_support::supports_ansi() {
assert_eq!(u16::MAX, available_color_count());
};
}
#[test]
fn colorterm_overrides_term() {
skip_windows_ansi_supported!();
temp_env::with_vars(
[
("COLORTERM", Some("truecolor")),
("TERM", Some("xterm-256color")),
],
|| {
assert_eq!(u16::MAX, available_color_count());
},
);
}
#[test]
fn term_24bits() {
skip_windows_ansi_supported!();
temp_env::with_vars(
[("COLORTERM", None), ("TERM", Some("xterm-24bits"))],
|| {
assert_eq!(u16::MAX, available_color_count());
},
);
}
#[test]
fn term_256color() {
skip_windows_ansi_supported!();
temp_env::with_vars(
[("COLORTERM", None), ("TERM", Some("xterm-256color"))],
|| {
assert_eq!(256u16, available_color_count());
},
);
}
#[test]
fn default_color_count() {
skip_windows_ansi_supported!();
temp_env::with_vars([("COLORTERM", None::<&str>), ("TERM", None)], || {
assert_eq!(8, available_color_count());
});
}
#[test]
fn unsupported_term_colorterm_values() {
skip_windows_ansi_supported!();
temp_env::with_vars(
[
("COLORTERM", Some("gibberish")),
("TERM", Some("gibberish")),
],
|| {
assert_eq!(8u16, available_color_count());
},
);
}
}