// Copyright 2020 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. use std::borrow::BorrowMut;use std::collections::HashMap;use std::fmt;use std::io;use std::io::Error;use std::io::Write;use std::mem;use std::ops::Range;use std::sync::Arc; use crossterm::queue;use crossterm::style::Attribute;use crossterm::style::Color;use crossterm::style::SetAttribute;use crossterm::style::SetBackgroundColor;use crossterm::style::SetForegroundColor;use itertools::Itertools; // Lets the caller label strings and translates the labels to colorspub trait Formatter: Write { /// Returns the backing `Write`. This is useful for writing data that is /// already formatted, such as in the graphical log. fn raw(&mut self) -> io::Result<Box<dyn Write + '_>>; fn push_label(&mut self, label: &str) -> io::Result<()>; fn pop_label(&mut self) -> io::Result<()>;} impl dyn Formatter + '_ { pub fn labeled<S: AsRef<str>>(&mut self, label: S) -> LabeledWriter<&mut Self, S> { LabeledWriter { formatter: self, label, } } pub fn with_label<E: From<io::Error>>( &mut self, label: &str, write_inner: impl FnOnce(&mut dyn Formatter) -> Result<(), E>, ) -> Result<(), E> { self.push_label(label)?; // Call `pop_label()` whether or not `write_inner()` fails, but don't let // its error replace the one from `write_inner()`. write_inner(self).and(self.pop_label().map_err(Into::into)) }} /// `Formatter` wrapper to write a labeled message with `write!()` or/// `writeln!()`.pub struct LabeledWriter<T, S> { formatter: T, label: S,} impl<T, S> LabeledWriter<T, S> { pub fn new(formatter: T, label: S) -> Self { LabeledWriter { formatter, label } } /// Turns into writer that prints labeled message with the `heading`. pub fn with_heading<H>(self, heading: H) -> HeadingLabeledWriter<T, S, H> { HeadingLabeledWriter::new(self, heading) }} impl<'a, T, S> LabeledWriter<T, S>where T: BorrowMut<dyn Formatter + 'a>, S: AsRef<str>,{ pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> io::Result<()> { self.with_labeled(|formatter| formatter.write_fmt(args)) } fn with_labeled( &mut self, write_inner: impl FnOnce(&mut dyn Formatter) -> io::Result<()>, ) -> io::Result<()> { self.formatter .borrow_mut() .with_label(self.label.as_ref(), write_inner) }} /// Like `LabeledWriter`, but also prints the `heading` once.////// The `heading` will be printed within the first `write!()` or `writeln!()`/// invocation, which is handy because `io::Error` can be handled there.pub struct HeadingLabeledWriter<T, S, H> { writer: LabeledWriter<T, S>, heading: Option<H>,} impl<T, S, H> HeadingLabeledWriter<T, S, H> { pub fn new(writer: LabeledWriter<T, S>, heading: H) -> Self { HeadingLabeledWriter { writer, heading: Some(heading), } }} impl<'a, T, S, H> HeadingLabeledWriter<T, S, H>where T: BorrowMut<dyn Formatter + 'a>, S: AsRef<str>, H: fmt::Display,{ pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> io::Result<()> { self.writer.with_labeled(|formatter| { if let Some(heading) = self.heading.take() { write!(formatter.labeled("heading"), "{heading}")?; } formatter.write_fmt(args) }) }} type Rules = Vec<(Vec<String>, Style)>; /// Creates `Formatter` instances with preconfigured parameters.#[derive(Clone, Debug)]pub struct FormatterFactory { kind: FormatterFactoryKind,} #[derive(Clone, Debug)]enum FormatterFactoryKind { PlainText, Sanitized, Color { rules: Arc<Rules>, debug: bool },} impl FormatterFactory { pub fn plain_text() -> Self { let kind = FormatterFactoryKind::PlainText; FormatterFactory { kind } } pub fn sanitized() -> Self { let kind = FormatterFactoryKind::Sanitized; FormatterFactory { kind } } pub fn color(config: &config::Config, debug: bool) -> Result<Self, config::ConfigError> { let rules = Arc::new(rules_from_config(config)?); let kind = FormatterFactoryKind::Color { rules, debug }; Ok(FormatterFactory { kind }) } pub fn new_formatter<'output, W: Write + 'output>( &self, output: W, ) -> Box<dyn Formatter + 'output> { match &self.kind { FormatterFactoryKind::PlainText => Box::new(PlainTextFormatter::new(output)), FormatterFactoryKind::Sanitized => Box::new(SanitizingFormatter::new(output)), FormatterFactoryKind::Color { rules, debug } => { Box::new(ColorFormatter::new(output, rules.clone(), *debug)) } } } pub fn is_color(&self) -> bool { matches!(self.kind, FormatterFactoryKind::Color { .. }) }} pub struct PlainTextFormatter<W> { output: W,} impl<W> PlainTextFormatter<W> { pub fn new(output: W) -> PlainTextFormatter<W> { Self { output } }} impl<W: Write> Write for PlainTextFormatter<W> { fn write(&mut self, data: &[u8]) -> Result<usize, Error> { self.output.write(data) } fn flush(&mut self) -> Result<(), Error> { self.output.flush() }} impl<W: Write> Formatter for PlainTextFormatter<W> { fn raw(&mut self) -> io::Result<Box<dyn Write + '_>> { Ok(Box::new(self.output.by_ref())) } fn push_label(&mut self, _label: &str) -> io::Result<()> { Ok(()) } fn pop_label(&mut self) -> io::Result<()> { Ok(()) }} pub struct SanitizingFormatter<W> { output: W,} impl<W> SanitizingFormatter<W> { pub fn new(output: W) -> SanitizingFormatter<W> { Self { output } }} impl<W: Write> Write for SanitizingFormatter<W> { fn write(&mut self, data: &[u8]) -> Result<usize, Error> { write_sanitized(&mut self.output, data)?; Ok(data.len()) } fn flush(&mut self) -> Result<(), Error> { self.output.flush() }} impl<W: Write> Formatter for SanitizingFormatter<W> { fn raw(&mut self) -> io::Result<Box<dyn Write + '_>> { Ok(Box::new(self.output.by_ref())) } fn push_label(&mut self, _label: &str) -> io::Result<()> { Ok(()) } fn pop_label(&mut self) -> io::Result<()> { Ok(()) }} #[derive(Clone, Debug, Default, PartialEq, Eq)]pub struct Style { pub fg_color: Option<Color>, pub bg_color: Option<Color>, pub bold: Option<bool>, pub underlined: Option<bool>,} impl Style { fn merge(&mut self, other: &Style) { self.fg_color = other.fg_color.or(self.fg_color); self.bg_color = other.bg_color.or(self.bg_color); self.bold = other.bold.or(self.bold); self.underlined = other.underlined.or(self.underlined); }} #[derive(Clone, Debug)]pub struct ColorFormatter<W: Write> { output: W, rules: Arc<Rules>, /// The stack of currently applied labels. These determine the desired /// style. labels: Vec<String>, cached_styles: HashMap<Vec<String>, Style>, /// The style we last wrote to the output. current_style: Style, /// The debug string (space-separated labels) we last wrote to the output. /// Initialize to None to turn debug strings off. current_debug: Option<String>,} impl<W: Write> ColorFormatter<W> { pub fn new(output: W, rules: Arc<Rules>, debug: bool) -> ColorFormatter<W> { ColorFormatter { output, rules, labels: vec![], cached_styles: HashMap::new(), current_style: Style::default(), current_debug: debug.then(String::new), } } pub fn for_config( output: W, config: &config::Config, debug: bool, ) -> Result<Self, config::ConfigError> { let rules = rules_from_config(config)?; Ok(Self::new(output, Arc::new(rules), debug)) } fn requested_style(&mut self) -> Style { if let Some(cached) = self.cached_styles.get(&self.labels) { cached.clone() } else { // We use the reverse list of matched indices as a measure of how well the rule // matches the actual labels. For example, for rule "a d" and the actual labels // "a b c d", we'll get [3,0]. We compare them by Rust's default Vec comparison. // That means "a d" will trump both rule "d" (priority [3]) and rule // "a b c" (priority [2,1,0]). let mut matched_styles = vec![]; for (labels, style) in self.rules.as_ref() { let mut labels_iter = self.labels.iter().enumerate(); // The indexes in the current label stack that match the required label. let mut matched_indices = vec![]; for required_label in labels { for (label_index, label) in &mut labels_iter { if label == required_label { matched_indices.push(label_index); break; } } } if matched_indices.len() == labels.len() { matched_indices.reverse(); matched_styles.push((style, matched_indices)); } } matched_styles.sort_by_key(|(_, indices)| indices.clone()); let mut style = Style::default(); for (matched_style, _) in matched_styles { style.merge(matched_style); } self.cached_styles .insert(self.labels.clone(), style.clone()); style } } fn write_new_style(&mut self) -> io::Result<()> { let new_debug = match &self.current_debug { Some(current) => { let joined = self.labels.join(" "); if joined == *current { None } else { if !current.is_empty() { write!(self.output, ">>")?; } Some(joined) } } None => None, }; let new_style = self.requested_style(); if new_style != self.current_style { if new_style.bold != self.current_style.bold { if new_style.bold.unwrap_or_default() { queue!(self.output, SetAttribute(Attribute::Bold))?; } else { // NoBold results in double underlining on some terminals, so we use reset // instead. However, that resets other attributes as well, so we reset // our record of the current style so we re-apply the other attributes // below. queue!(self.output, SetAttribute(Attribute::Reset))?; self.current_style = Style::default(); } } if new_style.underlined != self.current_style.underlined { if new_style.underlined.unwrap_or_default() { queue!(self.output, SetAttribute(Attribute::Underlined))?; } else { queue!(self.output, SetAttribute(Attribute::NoUnderline))?; } } if new_style.fg_color != self.current_style.fg_color { queue!( self.output, SetForegroundColor(new_style.fg_color.unwrap_or(Color::Reset)) )?; } if new_style.bg_color != self.current_style.bg_color { queue!( self.output, SetBackgroundColor(new_style.bg_color.unwrap_or(Color::Reset)) )?; } self.current_style = new_style; } if let Some(d) = new_debug { if !d.is_empty() { write!(self.output, "<<{d}::")?; } self.current_debug = Some(d); } Ok(()) }} fn rules_from_config(config: &config::Config) -> Result<Rules, config::ConfigError> { let mut result = vec![]; let table = config.get_table("colors")?; for (key, value) in table { let labels = key .split_whitespace() .map(ToString::to_string) .collect_vec(); match value.kind { config::ValueKind::String(color_name) => { let style = Style { fg_color: Some(color_for_name_or_hex(&color_name)?), bg_color: None, bold: None, underlined: None, }; result.push((labels, style)); } config::ValueKind::Table(style_table) => { let mut style = Style::default(); if let Some(value) = style_table.get("fg") { if let config::ValueKind::String(color_name) = &value.kind { style.fg_color = Some(color_for_name_or_hex(color_name)?); } } if let Some(value) = style_table.get("bg") { if let config::ValueKind::String(color_name) = &value.kind { style.bg_color = Some(color_for_name_or_hex(color_name)?); } } if let Some(value) = style_table.get("bold") { if let config::ValueKind::Boolean(value) = &value.kind { style.bold = Some(*value); } } if let Some(value) = style_table.get("underline") { if let config::ValueKind::Boolean(value) = &value.kind { style.underlined = Some(*value); } } result.push((labels, style)); } _ => {} } } Ok(result)} fn color_for_name_or_hex(name_or_hex: &str) -> Result<Color, config::ConfigError> { match name_or_hex { "default" => Ok(Color::Reset), "black" => Ok(Color::Black), "red" => Ok(Color::DarkRed), "green" => Ok(Color::DarkGreen), "yellow" => Ok(Color::DarkYellow), "blue" => Ok(Color::DarkBlue), "magenta" => Ok(Color::DarkMagenta), "cyan" => Ok(Color::DarkCyan), "white" => Ok(Color::Grey), "bright black" => Ok(Color::DarkGrey), "bright red" => Ok(Color::Red), "bright green" => Ok(Color::Green), "bright yellow" => Ok(Color::Yellow), "bright blue" => Ok(Color::Blue), "bright magenta" => Ok(Color::Magenta), "bright cyan" => Ok(Color::Cyan), "bright white" => Ok(Color::White), _ => color_for_hex(name_or_hex) .ok_or_else(|| config::ConfigError::Message(format!("invalid color: {name_or_hex}"))), }} fn color_for_hex(color: &str) -> Option<Color> { if color.len() == 7 && color.starts_with('#') && color[1..].chars().all(|c| c.is_ascii_hexdigit()) { let r = u8::from_str_radix(&color[1..3], 16); let g = u8::from_str_radix(&color[3..5], 16); let b = u8::from_str_radix(&color[5..7], 16); match (r, g, b) { (Ok(r), Ok(g), Ok(b)) => Some(Color::Rgb { r, g, b }), _ => None, } } else { None }} impl<W: Write> Write for ColorFormatter<W> { fn write(&mut self, data: &[u8]) -> Result<usize, Error> { /* We clear the current style at the end of each line, and then we re-apply the style after the newline. There are several reasons for this: Lines 1–500 of 1308. Load more
// Copyright 2020 The Jujutsu Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use std::borrow::BorrowMut; use std::collections::HashMap; use std::fmt; use std::io; use std::io::Error; use std::io::Write; use std::mem; use std::ops::Range; use std::sync::Arc; use crossterm::queue; use crossterm::style::Attribute; use crossterm::style::Color; use crossterm::style::SetAttribute; use crossterm::style::SetBackgroundColor; use crossterm::style::SetForegroundColor; use itertools::Itertools; // Lets the caller label strings and translates the labels to colors pub trait Formatter: Write { /// Returns the backing `Write`. This is useful for writing data that is /// already formatted, such as in the graphical log. fn raw(&mut self) -> io::Result<Box<dyn Write + '_>>; fn push_label(&mut self, label: &str) -> io::Result<()>; fn pop_label(&mut self) -> io::Result<()>; } impl dyn Formatter + '_ { pub fn labeled<S: AsRef<str>>(&mut self, label: S) -> LabeledWriter<&mut Self, S> { LabeledWriter { formatter: self, label, } } pub fn with_label<E: From<io::Error>>( &mut self, label: &str, write_inner: impl FnOnce(&mut dyn Formatter) -> Result<(), E>, ) -> Result<(), E> { self.push_label(label)?; // Call `pop_label()` whether or not `write_inner()` fails, but don't let // its error replace the one from `write_inner()`. write_inner(self).and(self.pop_label().map_err(Into::into)) } } /// `Formatter` wrapper to write a labeled message with `write!()` or /// `writeln!()`. pub struct LabeledWriter<T, S> { formatter: T, label: S, } impl<T, S> LabeledWriter<T, S> { pub fn new(formatter: T, label: S) -> Self { LabeledWriter { formatter, label } } /// Turns into writer that prints labeled message with the `heading`. pub fn with_heading<H>(self, heading: H) -> HeadingLabeledWriter<T, S, H> { HeadingLabeledWriter::new(self, heading) } } impl<'a, T, S> LabeledWriter<T, S> where T: BorrowMut<dyn Formatter + 'a>, S: AsRef<str>, { pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> io::Result<()> { self.with_labeled(|formatter| formatter.write_fmt(args)) } fn with_labeled( &mut self, write_inner: impl FnOnce(&mut dyn Formatter) -> io::Result<()>, ) -> io::Result<()> { self.formatter .borrow_mut() .with_label(self.label.as_ref(), write_inner) } } /// Like `LabeledWriter`, but also prints the `heading` once. /// /// The `heading` will be printed within the first `write!()` or `writeln!()` /// invocation, which is handy because `io::Error` can be handled there. pub struct HeadingLabeledWriter<T, S, H> { writer: LabeledWriter<T, S>, heading: Option<H>, } impl<T, S, H> HeadingLabeledWriter<T, S, H> { pub fn new(writer: LabeledWriter<T, S>, heading: H) -> Self { HeadingLabeledWriter { writer, heading: Some(heading), } } } impl<'a, T, S, H> HeadingLabeledWriter<T, S, H> where T: BorrowMut<dyn Formatter + 'a>, S: AsRef<str>, H: fmt::Display, { pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> io::Result<()> { self.writer.with_labeled(|formatter| { if let Some(heading) = self.heading.take() { write!(formatter.labeled("heading"), "{heading}")?; } formatter.write_fmt(args) }) } } type Rules = Vec<(Vec<String>, Style)>; /// Creates `Formatter` instances with preconfigured parameters. #[derive(Clone, Debug)] pub struct FormatterFactory { kind: FormatterFactoryKind, } #[derive(Clone, Debug)] enum FormatterFactoryKind { PlainText, Sanitized, Color { rules: Arc<Rules>, debug: bool }, } impl FormatterFactory { pub fn plain_text() -> Self { let kind = FormatterFactoryKind::PlainText; FormatterFactory { kind } } pub fn sanitized() -> Self { let kind = FormatterFactoryKind::Sanitized; FormatterFactory { kind } } pub fn color(config: &config::Config, debug: bool) -> Result<Self, config::ConfigError> { let rules = Arc::new(rules_from_config(config)?); let kind = FormatterFactoryKind::Color { rules, debug }; Ok(FormatterFactory { kind }) } pub fn new_formatter<'output, W: Write + 'output>( &self, output: W, ) -> Box<dyn Formatter + 'output> { match &self.kind { FormatterFactoryKind::PlainText => Box::new(PlainTextFormatter::new(output)), FormatterFactoryKind::Sanitized => Box::new(SanitizingFormatter::new(output)), FormatterFactoryKind::Color { rules, debug } => { Box::new(ColorFormatter::new(output, rules.clone(), *debug)) } } } pub fn is_color(&self) -> bool { matches!(self.kind, FormatterFactoryKind::Color { .. }) } } pub struct PlainTextFormatter<W> { output: W, } impl<W> PlainTextFormatter<W> { pub fn new(output: W) -> PlainTextFormatter<W> { Self { output } } } impl<W: Write> Write for PlainTextFormatter<W> { fn write(&mut self, data: &[u8]) -> Result<usize, Error> { self.output.write(data) } fn flush(&mut self) -> Result<(), Error> { self.output.flush() } } impl<W: Write> Formatter for PlainTextFormatter<W> { fn raw(&mut self) -> io::Result<Box<dyn Write + '_>> { Ok(Box::new(self.output.by_ref())) } fn push_label(&mut self, _label: &str) -> io::Result<()> { Ok(()) } fn pop_label(&mut self) -> io::Result<()> { Ok(()) } } pub struct SanitizingFormatter<W> { output: W, } impl<W> SanitizingFormatter<W> { pub fn new(output: W) -> SanitizingFormatter<W> { Self { output } } } impl<W: Write> Write for SanitizingFormatter<W> { fn write(&mut self, data: &[u8]) -> Result<usize, Error> { write_sanitized(&mut self.output, data)?; Ok(data.len()) } fn flush(&mut self) -> Result<(), Error> { self.output.flush() } } impl<W: Write> Formatter for SanitizingFormatter<W> { fn raw(&mut self) -> io::Result<Box<dyn Write + '_>> { Ok(Box::new(self.output.by_ref())) } fn push_label(&mut self, _label: &str) -> io::Result<()> { Ok(()) } fn pop_label(&mut self) -> io::Result<()> { Ok(()) } } #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct Style { pub fg_color: Option<Color>, pub bg_color: Option<Color>, pub bold: Option<bool>, pub underlined: Option<bool>, } impl Style { fn merge(&mut self, other: &Style) { self.fg_color = other.fg_color.or(self.fg_color); self.bg_color = other.bg_color.or(self.bg_color); self.bold = other.bold.or(self.bold); self.underlined = other.underlined.or(self.underlined); } } #[derive(Clone, Debug)] pub struct ColorFormatter<W: Write> { output: W, rules: Arc<Rules>, /// The stack of currently applied labels. These determine the desired /// style. labels: Vec<String>, cached_styles: HashMap<Vec<String>, Style>, /// The style we last wrote to the output. current_style: Style, /// The debug string (space-separated labels) we last wrote to the output. /// Initialize to None to turn debug strings off. current_debug: Option<String>, } impl<W: Write> ColorFormatter<W> { pub fn new(output: W, rules: Arc<Rules>, debug: bool) -> ColorFormatter<W> { ColorFormatter { output, rules, labels: vec![], cached_styles: HashMap::new(), current_style: Style::default(), current_debug: debug.then(String::new), } } pub fn for_config( output: W, config: &config::Config, debug: bool, ) -> Result<Self, config::ConfigError> { let rules = rules_from_config(config)?; Ok(Self::new(output, Arc::new(rules), debug)) } fn requested_style(&mut self) -> Style { if let Some(cached) = self.cached_styles.get(&self.labels) { cached.clone() } else { // We use the reverse list of matched indices as a measure of how well the rule // matches the actual labels. For example, for rule "a d" and the actual labels // "a b c d", we'll get [3,0]. We compare them by Rust's default Vec comparison. // That means "a d" will trump both rule "d" (priority [3]) and rule // "a b c" (priority [2,1,0]). let mut matched_styles = vec![]; for (labels, style) in self.rules.as_ref() { let mut labels_iter = self.labels.iter().enumerate(); // The indexes in the current label stack that match the required label. let mut matched_indices = vec![]; for required_label in labels { for (label_index, label) in &mut labels_iter { if label == required_label { matched_indices.push(label_index); break; } } } if matched_indices.len() == labels.len() { matched_indices.reverse(); matched_styles.push((style, matched_indices)); } } matched_styles.sort_by_key(|(_, indices)| indices.clone()); let mut style = Style::default(); for (matched_style, _) in matched_styles { style.merge(matched_style); } self.cached_styles .insert(self.labels.clone(), style.clone()); style } } fn write_new_style(&mut self) -> io::Result<()> { let new_debug = match &self.current_debug { Some(current) => { let joined = self.labels.join(" "); if joined == *current { None } else { if !current.is_empty() { write!(self.output, ">>")?; } Some(joined) } } None => None, }; let new_style = self.requested_style(); if new_style != self.current_style { if new_style.bold != self.current_style.bold { if new_style.bold.unwrap_or_default() { queue!(self.output, SetAttribute(Attribute::Bold))?; } else { // NoBold results in double underlining on some terminals, so we use reset // instead. However, that resets other attributes as well, so we reset // our record of the current style so we re-apply the other attributes // below. queue!(self.output, SetAttribute(Attribute::Reset))?; self.current_style = Style::default(); } } if new_style.underlined != self.current_style.underlined { if new_style.underlined.unwrap_or_default() { queue!(self.output, SetAttribute(Attribute::Underlined))?; } else { queue!(self.output, SetAttribute(Attribute::NoUnderline))?; } } if new_style.fg_color != self.current_style.fg_color { queue!( self.output, SetForegroundColor(new_style.fg_color.unwrap_or(Color::Reset)) )?; } if new_style.bg_color != self.current_style.bg_color { queue!( self.output, SetBackgroundColor(new_style.bg_color.unwrap_or(Color::Reset)) )?; } self.current_style = new_style; } if let Some(d) = new_debug { if !d.is_empty() { write!(self.output, "<<{d}::")?; } self.current_debug = Some(d); } Ok(()) } } fn rules_from_config(config: &config::Config) -> Result<Rules, config::ConfigError> { let mut result = vec![]; let table = config.get_table("colors")?; for (key, value) in table { let labels = key .split_whitespace() .map(ToString::to_string) .collect_vec(); match value.kind { config::ValueKind::String(color_name) => { let style = Style { fg_color: Some(color_for_name_or_hex(&color_name)?), bg_color: None, bold: None, underlined: None, }; result.push((labels, style)); } config::ValueKind::Table(style_table) => { let mut style = Style::default(); if let Some(value) = style_table.get("fg") { if let config::ValueKind::String(color_name) = &value.kind { style.fg_color = Some(color_for_name_or_hex(color_name)?); } } if let Some(value) = style_table.get("bg") { if let config::ValueKind::String(color_name) = &value.kind { style.bg_color = Some(color_for_name_or_hex(color_name)?); } } if let Some(value) = style_table.get("bold") { if let config::ValueKind::Boolean(value) = &value.kind { style.bold = Some(*value); } } if let Some(value) = style_table.get("underline") { if let config::ValueKind::Boolean(value) = &value.kind { style.underlined = Some(*value); } } result.push((labels, style)); } _ => {} } } Ok(result) } fn color_for_name_or_hex(name_or_hex: &str) -> Result<Color, config::ConfigError> { match name_or_hex { "default" => Ok(Color::Reset), "black" => Ok(Color::Black), "red" => Ok(Color::DarkRed), "green" => Ok(Color::DarkGreen), "yellow" => Ok(Color::DarkYellow), "blue" => Ok(Color::DarkBlue), "magenta" => Ok(Color::DarkMagenta), "cyan" => Ok(Color::DarkCyan), "white" => Ok(Color::Grey), "bright black" => Ok(Color::DarkGrey), "bright red" => Ok(Color::Red), "bright green" => Ok(Color::Green), "bright yellow" => Ok(Color::Yellow), "bright blue" => Ok(Color::Blue), "bright magenta" => Ok(Color::Magenta), "bright cyan" => Ok(Color::Cyan), "bright white" => Ok(Color::White), _ => color_for_hex(name_or_hex) .ok_or_else(|| config::ConfigError::Message(format!("invalid color: {name_or_hex}"))), } } fn color_for_hex(color: &str) -> Option<Color> { if color.len() == 7 && color.starts_with('#') && color[1..].chars().all(|c| c.is_ascii_hexdigit()) { let r = u8::from_str_radix(&color[1..3], 16); let g = u8::from_str_radix(&color[3..5], 16); let b = u8::from_str_radix(&color[5..7], 16); match (r, g, b) { (Ok(r), Ok(g), Ok(b)) => Some(Color::Rgb { r, g, b }), _ => None, } } else { None } } impl<W: Write> Write for ColorFormatter<W> { fn write(&mut self, data: &[u8]) -> Result<usize, Error> { /* We clear the current style at the end of each line, and then we re-apply the style after the newline. There are several reasons for this: