1375 lines · 47.6 KB · Rust
RawDownload
// 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 name_or_hex = String::deserialize(deserializer)?;    color_for_name_or_hex(&name_or_hex).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_name_or_hex(name_or_hex: &str) -> Result<Color, String> {    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(|| format!("Invalid color: {name_or_hex}")),    }}

Lines 1–500 of 1375.