// 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 as _;use jj_lib::config::ConfigGetError;use jj_lib::config::StackedConfig;use serde::de::Deserialize as _;use serde::de::Error as _;use serde::de::IntoDeserializer as _; // 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: &StackedConfig, debug: bool) -> Result<Self, ConfigGetError> { 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, serde::Deserialize)]#[serde(default, rename_all = "kebab-case")]pub struct Style { #[serde(deserialize_with = "deserialize_color_opt")] pub fg: Option<Color>, #[serde(deserialize_with = "deserialize_color_opt")] pub bg: Option<Color>, pub bold: Option<bool>, pub italic: Option<bool>, pub underline: Option<bool>, pub reverse: Option<bool>,} impl Style { fn merge(&mut self, other: &Style) { self.fg = other.fg.or(self.fg); self.bg = other.bg.or(self.bg); self.bold = other.bold.or(self.bold); self.italic = other.italic.or(self.italic); self.underline = other.underline.or(self.underline); self.reverse = other.reverse.or(self.reverse); }} #[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: &StackedConfig, debug: bool, ) -> Result<Self, ConfigGetError> { 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.italic != self.current_style.italic { if new_style.italic.unwrap_or_default() { queue!(self.output, SetAttribute(Attribute::Italic))?; } else { queue!(self.output, SetAttribute(Attribute::NoItalic))?; } } if new_style.underline != self.current_style.underline { if new_style.underline.unwrap_or_default() { queue!(self.output, SetAttribute(Attribute::Underlined))?; } else { queue!(self.output, SetAttribute(Attribute::NoUnderline))?; } } if new_style.reverse != self.current_style.reverse { if new_style.reverse.unwrap_or_default() { queue!(self.output, SetAttribute(Attribute::Reverse))?; } else { queue!(self.output, SetAttribute(Attribute::NoReverse))?; } } if new_style.fg != self.current_style.fg { queue!( self.output, SetForegroundColor(new_style.fg.unwrap_or(Color::Reset)) )?; } if new_style.bg != self.current_style.bg { queue!( self.output, SetBackgroundColor(new_style.bg.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: &StackedConfig) -> Result<Rules, ConfigGetError> { config .table_keys("colors") .map(|key| { let labels = key .split_whitespace() .map(ToString::to_string) .collect_vec(); let style = config.get_value_with(["colors", key], |value| { if value.is_str() { Ok(Style { fg: Some(deserialize_color(value.into_deserializer())?), bg: None, bold: None, italic: None, underline: None, reverse: None, }) } else if value.is_inline_table() { Style::deserialize(value.into_deserializer()) } else { Err(toml_edit::de::Error::custom(format!( "invalid type: {}, expected a color name or a table of styles", value.type_name() ))) } })?; Ok((labels, style)) }) .collect()} fn deserialize_color<'de, D>(deserializer: D) -> Result<Color, D::Error>where D: serde::Deserializer<'de>,{ let color_str = String::deserialize(deserializer)?; color_for_string(&color_str).map_err(D::Error::custom)} fn deserialize_color_opt<'de, D>(deserializer: D) -> Result<Option<Color>, D::Error>where D: serde::Deserializer<'de>,{ deserialize_color(deserializer).map(Some)} fn color_for_string(color_str: &str) -> Result<Color, String> { match color_str { "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_ansi256_index(color_str) .or_else(|| color_for_hex(color_str)) .ok_or_else(|| format!("Invalid color: {color_str}")),Lines 1–500 of 1448. 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 as _; use jj_lib::config::ConfigGetError; use jj_lib::config::StackedConfig; use serde::de::Deserialize as _; use serde::de::Error as _; use serde::de::IntoDeserializer as _; // 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: &StackedConfig, debug: bool) -> Result<Self, ConfigGetError> { 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, serde::Deserialize)] #[serde(default, rename_all = "kebab-case")] pub struct Style { #[serde(deserialize_with = "deserialize_color_opt")] pub fg: Option<Color>, #[serde(deserialize_with = "deserialize_color_opt")] pub bg: Option<Color>, pub bold: Option<bool>, pub italic: Option<bool>, pub underline: Option<bool>, pub reverse: Option<bool>, } impl Style { fn merge(&mut self, other: &Style) { self.fg = other.fg.or(self.fg); self.bg = other.bg.or(self.bg); self.bold = other.bold.or(self.bold); self.italic = other.italic.or(self.italic); self.underline = other.underline.or(self.underline); self.reverse = other.reverse.or(self.reverse); } } #[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: &StackedConfig, debug: bool, ) -> Result<Self, ConfigGetError> { 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.italic != self.current_style.italic { if new_style.italic.unwrap_or_default() { queue!(self.output, SetAttribute(Attribute::Italic))?; } else { queue!(self.output, SetAttribute(Attribute::NoItalic))?; } } if new_style.underline != self.current_style.underline { if new_style.underline.unwrap_or_default() { queue!(self.output, SetAttribute(Attribute::Underlined))?; } else { queue!(self.output, SetAttribute(Attribute::NoUnderline))?; } } if new_style.reverse != self.current_style.reverse { if new_style.reverse.unwrap_or_default() { queue!(self.output, SetAttribute(Attribute::Reverse))?; } else { queue!(self.output, SetAttribute(Attribute::NoReverse))?; } } if new_style.fg != self.current_style.fg { queue!( self.output, SetForegroundColor(new_style.fg.unwrap_or(Color::Reset)) )?; } if new_style.bg != self.current_style.bg { queue!( self.output, SetBackgroundColor(new_style.bg.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: &StackedConfig) -> Result<Rules, ConfigGetError> { config .table_keys("colors") .map(|key| { let labels = key .split_whitespace() .map(ToString::to_string) .collect_vec(); let style = config.get_value_with(["colors", key], |value| { if value.is_str() { Ok(Style { fg: Some(deserialize_color(value.into_deserializer())?), bg: None, bold: None, italic: None, underline: None, reverse: None, }) } else if value.is_inline_table() { Style::deserialize(value.into_deserializer()) } else { Err(toml_edit::de::Error::custom(format!( "invalid type: {}, expected a color name or a table of styles", value.type_name() ))) } })?; Ok((labels, style)) }) .collect() } fn deserialize_color<'de, D>(deserializer: D) -> Result<Color, D::Error> where D: serde::Deserializer<'de>, { let color_str = String::deserialize(deserializer)?; color_for_string(&color_str).map_err(D::Error::custom) } fn deserialize_color_opt<'de, D>(deserializer: D) -> Result<Option<Color>, D::Error> where D: serde::Deserializer<'de>, { deserialize_color(deserializer).map(Some) } fn color_for_string(color_str: &str) -> Result<Color, String> { match color_str { "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_ansi256_index(color_str) .or_else(|| color_for_hex(color_str)) .ok_or_else(|| format!("Invalid color: {color_str}")),