// 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::collections::HashMap;use std::fmt;use std::io;use std::io::Error;use std::io::Write;use std::mem;use std::ops::Deref;use std::ops::DerefMut;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); fn pop_label(&mut self); fn maybe_color(&self) -> bool;} impl<T: Formatter + ?Sized> Formatter for &mut T { fn raw(&mut self) -> io::Result<Box<dyn Write + '_>> { <T as Formatter>::raw(self) } fn push_label(&mut self, label: &str) { <T as Formatter>::push_label(self, label); } fn pop_label(&mut self) { <T as Formatter>::pop_label(self); } fn maybe_color(&self) -> bool { <T as Formatter>::maybe_color(self) }} impl<T: Formatter + ?Sized> Formatter for Box<T> { fn raw(&mut self) -> io::Result<Box<dyn Write + '_>> { <T as Formatter>::raw(self) } fn push_label(&mut self, label: &str) { <T as Formatter>::push_label(self, label); } fn pop_label(&mut self) { <T as Formatter>::pop_label(self); } fn maybe_color(&self) -> bool { <T as Formatter>::maybe_color(self) }} /// [`Formatter`] adapters.pub trait FormatterExt: Formatter { fn labeled(&mut self, label: &str) -> LabeledScope<&mut Self> { LabeledScope::new(self, label) } fn into_labeled(self, label: &str) -> LabeledScope<Self> where Self: Sized, { LabeledScope::new(self, label) }} impl<T: Formatter + ?Sized> FormatterExt for T {} /// [`Formatter`] wrapper to apply a label within a lexical scope.#[must_use]pub struct LabeledScope<T: Formatter> { formatter: T,} impl<T: Formatter> LabeledScope<T> { pub fn new(mut formatter: T, label: &str) -> Self { formatter.push_label(label); Self { formatter } } // TODO: move to FormatterExt? /// Turns into writer that prints labeled message with the `heading`. pub fn with_heading<H>(self, heading: H) -> HeadingLabeledWriter<T, H> { HeadingLabeledWriter::new(self, heading) }} impl<T: Formatter> Drop for LabeledScope<T> { fn drop(&mut self) { self.formatter.pop_label(); }} impl<T: Formatter> Deref for LabeledScope<T> { type Target = T; fn deref(&self) -> &Self::Target { &self.formatter }} impl<T: Formatter> DerefMut for LabeledScope<T> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.formatter }} // There's no `impl Formatter for LabeledScope<T>` so nested .labeled() calls// wouldn't construct `LabeledScope<LabeledScope<T>>`. /// [`Formatter`] wrapper that 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: Formatter, H> { formatter: LabeledScope<T>, heading: Option<H>,} impl<T: Formatter, H> HeadingLabeledWriter<T, H> { pub fn new(formatter: LabeledScope<T>, heading: H) -> Self { Self { formatter, heading: Some(heading), } }} impl<T: Formatter, H: fmt::Display> HeadingLabeledWriter<T, H> { pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> io::Result<()> { if let Some(heading) = self.heading.take() { write!(self.formatter.labeled("heading"), "{heading}")?; } self.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; Self { kind } } pub fn sanitized() -> Self { let kind = FormatterFactoryKind::Sanitized; Self { 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(Self { 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 maybe_color(&self) -> bool { matches!(self.kind, FormatterFactoryKind::Color { .. }) }} pub struct PlainTextFormatter<W> { output: W,} impl<W> PlainTextFormatter<W> { pub fn new(output: W) -> Self { 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) {} fn pop_label(&mut self) {} fn maybe_color(&self) -> bool { false }} pub struct SanitizingFormatter<W> { output: W,} impl<W> SanitizingFormatter<W> { pub fn new(output: W) -> Self { 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) {} fn pop_label(&mut self) {} fn maybe_color(&self) -> bool { false }} #[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 dim: Option<bool>, pub italic: Option<bool>, pub underline: Option<bool>, pub crossed_out: Option<bool>, pub reverse: Option<bool>,} impl Style { fn merge(&mut self, other: &Self) { self.fg = other.fg.or(self.fg); self.bg = other.bg.or(self.bg); self.bold = other.bold.or(self.bold); self.dim = other.dim.or(self.dim); self.italic = other.italic.or(self.italic); self.underline = other.underline.or(self.underline); self.crossed_out = other.crossed_out.or(self.crossed_out); 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) -> Self { Self { 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 { // Bold and Dim change intensity, and NormalIntensity would reset // both. Also, NoBold results in double underlining on some // terminals. Therefore, 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. Maybe we can use // NormalIntensity instead of Reset, but let's simply reset all // attributes to work around potential terminal incompatibility. let new_bold = new_style.bold.unwrap_or_default(); let new_dim = new_style.dim.unwrap_or_default(); if (new_style.bold != self.current_style.bold && !new_bold) || (new_style.dim != self.current_style.dim && !new_dim) { queue!(self.output, SetAttribute(Attribute::Reset))?; self.current_style = Style::default(); } if new_style.bold != self.current_style.bold && new_bold { queue!(self.output, SetAttribute(Attribute::Bold))?; } if new_style.dim != self.current_style.dim && new_dim { queue!(self.output, SetAttribute(Attribute::Dim))?; } 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.crossed_out != self.current_style.crossed_out { if new_style.crossed_out.unwrap_or_default() { queue!(self.output, SetAttribute(Attribute::CrossedOut))?; } else { queue!(self.output, SetAttribute(Attribute::NotCrossedOut))?; } } 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, dim: None,Lines 1–500 of 1705. 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::collections::HashMap; use std::fmt; use std::io; use std::io::Error; use std::io::Write; use std::mem; use std::ops::Deref; use std::ops::DerefMut; 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); fn pop_label(&mut self); fn maybe_color(&self) -> bool; } impl<T: Formatter + ?Sized> Formatter for &mut T { fn raw(&mut self) -> io::Result<Box<dyn Write + '_>> { <T as Formatter>::raw(self) } fn push_label(&mut self, label: &str) { <T as Formatter>::push_label(self, label); } fn pop_label(&mut self) { <T as Formatter>::pop_label(self); } fn maybe_color(&self) -> bool { <T as Formatter>::maybe_color(self) } } impl<T: Formatter + ?Sized> Formatter for Box<T> { fn raw(&mut self) -> io::Result<Box<dyn Write + '_>> { <T as Formatter>::raw(self) } fn push_label(&mut self, label: &str) { <T as Formatter>::push_label(self, label); } fn pop_label(&mut self) { <T as Formatter>::pop_label(self); } fn maybe_color(&self) -> bool { <T as Formatter>::maybe_color(self) } } /// [`Formatter`] adapters. pub trait FormatterExt: Formatter { fn labeled(&mut self, label: &str) -> LabeledScope<&mut Self> { LabeledScope::new(self, label) } fn into_labeled(self, label: &str) -> LabeledScope<Self> where Self: Sized, { LabeledScope::new(self, label) } } impl<T: Formatter + ?Sized> FormatterExt for T {} /// [`Formatter`] wrapper to apply a label within a lexical scope. #[must_use] pub struct LabeledScope<T: Formatter> { formatter: T, } impl<T: Formatter> LabeledScope<T> { pub fn new(mut formatter: T, label: &str) -> Self { formatter.push_label(label); Self { formatter } } // TODO: move to FormatterExt? /// Turns into writer that prints labeled message with the `heading`. pub fn with_heading<H>(self, heading: H) -> HeadingLabeledWriter<T, H> { HeadingLabeledWriter::new(self, heading) } } impl<T: Formatter> Drop for LabeledScope<T> { fn drop(&mut self) { self.formatter.pop_label(); } } impl<T: Formatter> Deref for LabeledScope<T> { type Target = T; fn deref(&self) -> &Self::Target { &self.formatter } } impl<T: Formatter> DerefMut for LabeledScope<T> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.formatter } } // There's no `impl Formatter for LabeledScope<T>` so nested .labeled() calls // wouldn't construct `LabeledScope<LabeledScope<T>>`. /// [`Formatter`] wrapper that 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: Formatter, H> { formatter: LabeledScope<T>, heading: Option<H>, } impl<T: Formatter, H> HeadingLabeledWriter<T, H> { pub fn new(formatter: LabeledScope<T>, heading: H) -> Self { Self { formatter, heading: Some(heading), } } } impl<T: Formatter, H: fmt::Display> HeadingLabeledWriter<T, H> { pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> io::Result<()> { if let Some(heading) = self.heading.take() { write!(self.formatter.labeled("heading"), "{heading}")?; } self.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; Self { kind } } pub fn sanitized() -> Self { let kind = FormatterFactoryKind::Sanitized; Self { 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(Self { 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 maybe_color(&self) -> bool { matches!(self.kind, FormatterFactoryKind::Color { .. }) } } pub struct PlainTextFormatter<W> { output: W, } impl<W> PlainTextFormatter<W> { pub fn new(output: W) -> Self { 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) {} fn pop_label(&mut self) {} fn maybe_color(&self) -> bool { false } } pub struct SanitizingFormatter<W> { output: W, } impl<W> SanitizingFormatter<W> { pub fn new(output: W) -> Self { 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) {} fn pop_label(&mut self) {} fn maybe_color(&self) -> bool { false } } #[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 dim: Option<bool>, pub italic: Option<bool>, pub underline: Option<bool>, pub crossed_out: Option<bool>, pub reverse: Option<bool>, } impl Style { fn merge(&mut self, other: &Self) { self.fg = other.fg.or(self.fg); self.bg = other.bg.or(self.bg); self.bold = other.bold.or(self.bold); self.dim = other.dim.or(self.dim); self.italic = other.italic.or(self.italic); self.underline = other.underline.or(self.underline); self.crossed_out = other.crossed_out.or(self.crossed_out); 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) -> Self { Self { 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 { // Bold and Dim change intensity, and NormalIntensity would reset // both. Also, NoBold results in double underlining on some // terminals. Therefore, 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. Maybe we can use // NormalIntensity instead of Reset, but let's simply reset all // attributes to work around potential terminal incompatibility. let new_bold = new_style.bold.unwrap_or_default(); let new_dim = new_style.dim.unwrap_or_default(); if (new_style.bold != self.current_style.bold && !new_bold) || (new_style.dim != self.current_style.dim && !new_dim) { queue!(self.output, SetAttribute(Attribute::Reset))?; self.current_style = Style::default(); } if new_style.bold != self.current_style.bold && new_bold { queue!(self.output, SetAttribute(Attribute::Bold))?; } if new_style.dim != self.current_style.dim && new_dim { queue!(self.output, SetAttribute(Attribute::Dim))?; } 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.crossed_out != self.current_style.crossed_out { if new_style.crossed_out.unwrap_or_default() { queue!(self.output, SetAttribute(Attribute::CrossedOut))?; } else { queue!(self.output, SetAttribute(Attribute::NotCrossedOut))?; } } 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, dim: None,