1689 lines · 56.3 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::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 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.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.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,                        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",

Lines 1–500 of 1689.