From 6719cf8e07d3bc6fb5a229e00aeaefbfb852d9a3 Mon Sep 17 00:00:00 2001 From: Dan Davison Date: Thu, 11 Nov 2021 10:50:57 -0500 Subject: [PATCH] Handle `git blame` output Fixes #426 Partial versions of these changes were previously in master and then reverted multiple times. See #746 0745f853d4bed52aca0b6739ac452d54ff54a153 3aab5d19569fa52ace2d7e6d196a1256990c4e20 --- Cargo.lock | 25 +++ Cargo.toml | 2 + README.md | 30 +++ src/cli.rs | 21 ++ src/color.rs | 6 + src/config.rs | 25 +++ src/delta.rs | 5 + src/handlers/blame.rs | 360 +++++++++++++++++++++++++++++++++ src/handlers/mod.rs | 1 + src/options/set.rs | 3 + src/paint.rs | 2 + src/style.rs | 16 ++ src/subcommands/show_config.rs | 11 +- 13 files changed, 506 insertions(+), 1 deletion(-) create mode 100644 src/handlers/blame.rs diff --git a/Cargo.lock b/Cargo.lock index 65955aeba..0c081ac2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,8 +145,20 @@ version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" dependencies = [ + "libc", "num-integer", "num-traits", + "time", + "winapi", +] + +[[package]] +name = "chrono-humanize" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eddc119501d583fd930cb92144e605f44e0252c38dd89d9247fffa1993375cb" +dependencies = [ + "chrono", ] [[package]] @@ -332,6 +344,8 @@ dependencies = [ "bitflags", "box_drawing", "bytelines", + "chrono", + "chrono-humanize", "console", "ctrlc", "dirs-next", @@ -933,6 +947,17 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + [[package]] name = "tinyvec" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 1f0d7c7de..e7181ddcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,8 @@ name = "delta" path = "src/main.rs" [dependencies] +chrono = "0.4.19" +chrono-humanize = "0.2.1" ansi_colours = "1.0.4" ansi_term = "0.12.1" atty = "0.2.14" diff --git a/README.md b/README.md index ad7756286..2437fb301 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ show = delta log = delta reflog = delta + blame = delta # see default-language to enable `git blame` syntax highlighting [interactive] diffFilter = delta --color-only @@ -152,6 +153,7 @@ Here's what `git show` can look like with git configured to use delta: - `diff-highlight` and `diff-so-fancy` emulation modes - Stylable box/line decorations to draw attention to commit, file and hunk header sections. - Support for Git's `--color-moved` feature. +- Customizable `git blame` with syntax highlighting (`--hyperlinks` formats commits as links to GitHub/GitLab/Bitbucket etc) - Code can be copied directly from the diff (`-/+` markers are removed by default). - `n` and `N` keybindings to move between files in large diffs, and between diffs in `log -p` views (`--navigate`) - Commit hashes can be formatted as terminal [hyperlinks](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda) to the GitHub/GitLab/Bitbucket page (`--hyperlinks`). @@ -400,6 +402,28 @@ In contrast, the long replacement line in the right panel overflows by almost an For control over the details of line wrapping, see `--wrap-max-lines`, `--wrap-left-symbol`, `--wrap-right-symbol`, `--wrap-right-percent`, `--wrap-right-prefix-symbol`, `--inline-hint-style`. Line wrapping was implemented by @th1000s. +### git blame + +Set delta as the pager for `git blame`, along with the standard diff-generating git operations: + +```gitconfig +[pager] + diff = delta + show = delta + log = delta + reflog = delta + blame = delta # see default-language to enable `git blame` syntax highlighting +``` + +However, in order to activate syntax highlighting of `git blame` output, you must tell delta which language the file is written in by using the `default-language` option. +If you are working in a repository that mostly contains files in a single language, the most convenient way to do this is by editing the _repository-specific_ git config file, i.e. the one at `/path/to/repo/.git/config`, as opposed to the one in your home directory: +Add an entry like this, but using + +```gitconfig +[delta] + default-language = rs # substitute the language file extension you want here! +``` + ### "Features": named groups of settings All delta options can go under the `[delta]` section in your git config file. However, you can also use named "features" to keep things organized: these are sections in git config like `[delta "my-feature"]`. Here's an example using two custom features: @@ -486,6 +510,12 @@ In order to support this feature, Delta has to look at the raw colors it receive Use the `navigate` feature to activate navigation keybindings. In this mode, pressing `n` will jump forward to the next file in the diff, and `N` will jump backwards. If you are viewing multiple commits (e.g. via `git log -p`) then navigation will also visit commit boundaries. +### Git blame + +Delta will render `git blame` output in its own way, if you have `pager.blame = delta` set in your `gitconfig`. Example: + +
image
+ ### 24 bit color (truecolor) Delta looks best if your terminal application supports 24 bit colors. See https://github.com/termstandard/colors#readme. For example, on MacOS, iTerm2 supports 24-bit colors but Terminal.app does not. diff --git a/src/cli.rs b/src/cli.rs index 9c35f68b0..9ae6130fd 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -420,6 +420,27 @@ pub struct Opt { /// (underline), 'ol' (overline), or the combination 'ul ol'. pub hunk_header_decoration_style: String, + /// Format string for git blame commit metadata. Available placeholders are + /// "{timestamp}", "{author}", and "{commit}". + #[structopt( + long = "blame-format", + default_value = "{timestamp:<15} {author:<15.14} {commit:<8} │ " + )] + pub blame_format: String, + + /// Background colors used for git blame lines (space-separated string). + /// Lines added by the same commit are painted with the same color; colors + /// are recycled as needed. + #[structopt(long = "blame-palette")] + pub blame_palette: Option, + + /// Format of `git blame` timestamp in raw git output received by delta. + #[structopt( + long = "blame-timestamp-format", + default_value = "%Y-%m-%d %H:%M:%S %z" + )] + pub blame_timestamp_format: String, + /// Default language used for syntax highlighting when this cannot be /// inferred from a filename. It will typically make sense to set this in /// per-repository git config (.git/config) diff --git a/src/color.rs b/src/color.rs index dfa86fa7c..8140f9c6b 100644 --- a/src/color.rs +++ b/src/color.rs @@ -162,3 +162,9 @@ const DARK_THEME_PLUS_COLOR_256: Color = Color::Fixed(22); const DARK_THEME_PLUS_EMPH_COLOR: Color = Color::RGB(0x00, 0x60, 0x00); const DARK_THEME_PLUS_EMPH_COLOR_256: Color = Color::Fixed(28); + +// blame + +pub const LIGHT_THEME_BLAME_PALETTE: &[&str] = &["#FFFFFF", "#DDDDDD", "#BBBBBB"]; + +pub const DARK_THEME_BLAME_PALETTE: &[&str] = &["#000000", "#222222", "#444444"]; diff --git a/src/config.rs b/src/config.rs index 770a9eb61..3cba6ba3a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -57,6 +57,9 @@ fn adapt_wrap_max_lines_argument(arg: String) -> usize { pub struct Config { pub available_terminal_width: usize, pub background_color_extends_to_terminal_width: bool, + pub blame_format: String, + pub blame_palette: Vec, + pub blame_timestamp_format: String, pub color_only: bool, pub commit_regex: Regex, pub commit_style: Style, @@ -213,6 +216,8 @@ impl From for Config { _ => *style::GIT_DEFAULT_PLUS_STYLE, }; + let blame_palette = make_blame_palette(opt.blame_palette, opt.computed.is_light_mode); + let file_added_label = opt.file_added_label; let file_copied_label = opt.file_copied_label; let file_modified_label = opt.file_modified_label; @@ -257,6 +262,9 @@ impl From for Config { background_color_extends_to_terminal_width: opt .computed .background_color_extends_to_terminal_width, + blame_format: opt.blame_format, + blame_palette, + blame_timestamp_format: opt.blame_timestamp_format, commit_style, color_only: opt.color_only, commit_regex, @@ -603,6 +611,23 @@ fn make_commit_file_hunk_header_styles(opt: &cli::Opt) -> (Style, Style, Style, ) } +fn make_blame_palette(blame_palette: Option, is_light_mode: bool) -> Vec { + match (blame_palette, is_light_mode) { + (Some(string), _) => string + .split_whitespace() + .map(|s| s.to_owned()) + .collect::>(), + (None, true) => color::LIGHT_THEME_BLAME_PALETTE + .iter() + .map(|s| s.to_string()) + .collect::>(), + (None, false) => color::DARK_THEME_BLAME_PALETTE + .iter() + .map(|s| s.to_string()) + .collect::>(), + } +} + /// Did the user supply `option` on the command line? pub fn user_supplied_option(option: &str, arg_matches: &clap::ArgMatches) -> bool { arg_matches.occurrences_of(option) > 0 diff --git a/src/delta.rs b/src/delta.rs index 8d2b067f5..fbf45c53d 100644 --- a/src/delta.rs +++ b/src/delta.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::collections::HashMap; use std::io::BufRead; use std::io::Write; @@ -21,6 +22,7 @@ pub enum State { HunkPlus(Option), // In hunk; added line (raw_line) SubmoduleLog, // In a submodule section, with gitconfig diff.submodule = log SubmoduleShort(String), // In a submodule section, with gitconfig diff.submodule = short + Blame(String, Option), // In a line of `git blame` output (commit, repeat_blame_line). Unknown, // The following elements are created when a line is wrapped to display it: HunkZeroWrapped, // Wrapped unchanged line @@ -67,6 +69,7 @@ pub struct StateMachine<'a> { // avoid emitting the file meta header line twice (#245). pub current_file_pair: Option<(String, String)>, pub handled_file_meta_header_line_file_pair: Option<(String, String)>, + pub blame_commit_colors: HashMap, } pub fn delta(lines: ByteLines, writer: &mut dyn Write, config: &Config) -> std::io::Result<()> @@ -92,6 +95,7 @@ impl<'a> StateMachine<'a> { handled_file_meta_header_line_file_pair: None, painter: Painter::new(writer, config), config, + blame_commit_colors: HashMap::new(), } } @@ -116,6 +120,7 @@ impl<'a> StateMachine<'a> { || self.handle_submodule_log_line()? || self.handle_submodule_short_line()? || self.handle_hunk_line()? + || self.handle_blame_line()? || self.should_skip_line() || self.emit_line_unchanged()?; } diff --git a/src/handlers/blame.rs b/src/handlers/blame.rs new file mode 100644 index 000000000..d6ba66c99 --- /dev/null +++ b/src/handlers/blame.rs @@ -0,0 +1,360 @@ +use chrono::{DateTime, FixedOffset}; +use lazy_static::lazy_static; +use regex::Regex; +use std::borrow::Cow; + +use crate::ansi::measure_text_width; +use crate::color; +use crate::config; +use crate::config::delta_unreachable; +use crate::delta::{self, State, StateMachine}; +use crate::format::{self, Placeholder}; +use crate::paint::BgShouldFill; +use crate::style::Style; + +impl<'a> StateMachine<'a> { + /// If this is a line of git blame output then render it accordingly. If + /// this is the first blame line, then set the syntax-highlighter language + /// according to delta.default-language. + pub fn handle_blame_line(&mut self) -> std::io::Result { + // TODO: It should be possible to eliminate some of the .clone()s and + // .to_owned()s. + let mut handled_line = false; + self.painter.emit()?; + let (previous_commit, mut repeat_blame_line, try_parse) = match &self.state { + State::Blame(commit, repeat_blame_line) => { + (Some(commit.as_str()), repeat_blame_line.clone(), true) + } + State::Unknown => (None, None, true), + _ => (None, None, false), + }; + if try_parse { + if let Some(blame) = + parse_git_blame_line(&self.line, &self.config.blame_timestamp_format) + { + let is_repeat = previous_commit == Some(blame.commit); + let color = self.get_color(blame.commit, previous_commit, is_repeat); + let mut style = Style::from_colors(None, color::parse_color(&color, true)); + // TODO: This will often be pointlessly updating a key with the + // value it already has. It might be nicer to do this (and + // compute the style) in get_color(), but as things stand the + // borrow checker won't permit that. + self.blame_commit_colors + .insert(blame.commit.to_owned(), color); + + style.is_syntax_highlighted = true; + + // Construct commit metadata, paint, and emit + let format_data = format::parse_line_number_format( + &self.config.blame_format, + &*BLAME_PLACEHOLDER_REGEX, + false, + ); + let blame_line = match (is_repeat, &repeat_blame_line) { + (false, _) => Cow::from(format_blame_metadata( + &format_data, + &blame, + false, + self.config, + )), + (true, None) => { + repeat_blame_line = Some(format_blame_metadata( + &format_data, + &blame, + true, + self.config, + )); + Cow::from(repeat_blame_line.as_ref().unwrap()) + } + (true, Some(repeat_blame_line)) => Cow::from(repeat_blame_line), + }; + write!(self.painter.writer, "{}", style.paint(blame_line))?; + + // Emit syntax-highlighted code + if matches!(self.state, State::Unknown) { + if let Some(lang) = self.config.default_language.as_ref() { + self.painter.set_syntax(Some(lang)); + self.painter.set_highlighter(); + } + } + self.state = State::Blame(blame.commit.to_owned(), repeat_blame_line.to_owned()); + self.painter.syntax_highlight_and_paint_line( + &format!("{}\n", blame.code), + style, + self.state.clone(), + BgShouldFill::default(), + ); + handled_line = true + } + } + Ok(handled_line) + } + + fn get_color( + &self, + this_commit: &str, + previous_commit: Option<&str>, + is_repeat: bool, + ) -> String { + // Determine color for this line + let previous_commit_color = match previous_commit { + Some(previous_commit) => self.blame_commit_colors.get(previous_commit), + None => None, + }; + + match ( + self.blame_commit_colors.get(this_commit), + previous_commit_color, + is_repeat, + ) { + (Some(commit_color), Some(previous_commit_color), true) => { + debug_assert!(commit_color == previous_commit_color); + // Repeated commit: assign same color + commit_color.to_owned() + } + (None, Some(previous_commit_color), false) => { + // The commit has no color: assign the next color that differs + // from previous commit. + self.get_next_color(Some(previous_commit_color)) + } + (None, None, false) => { + // The commit has no color, and there is no previous commit: + // Just assign the next color. is_repeat is necessarily false. + self.get_next_color(None) + } + (Some(commit_color), Some(previous_commit_color), false) => { + if commit_color != previous_commit_color { + // Consecutive commits differ without a collision + commit_color.to_owned() + } else { + // Consecutive commits differ; prevent color collision + self.get_next_color(Some(commit_color)) + } + } + (None, _, true) => { + delta_unreachable("is_repeat cannot be true when commit has no color.") + } + (Some(_), None, _) => { + delta_unreachable("There must be a previous commit if the commit has a color.") + } + } + } + + fn get_next_color(&self, other_than_color: Option<&str>) -> String { + let n_commits = self.blame_commit_colors.len(); + let n_colors = self.config.blame_palette.len(); + let color = self.config.blame_palette[n_commits % n_colors].clone(); + if Some(color.as_str()) != other_than_color { + color + } else { + self.config.blame_palette[(n_commits + 1) % n_colors].clone() + } + } +} + +#[derive(Debug)] +pub struct BlameLine<'a> { + pub commit: &'a str, + pub author: &'a str, + pub time: DateTime, + pub line_number: usize, + pub code: &'a str, +} + +// E.g. +//ea82f2d0 (Dan Davison 2021-08-22 18:20:19 -0700 120) let mut handled_line = self.handle_commit_meta_header_line()? + +lazy_static! { + static ref BLAME_LINE_REGEX: Regex = Regex::new( + r"(?x) +^ +( + [0-9a-f]{4,40} # commit hash +) +(?: .+)? # optional file name (unused; present if file has been renamed; TODO: inefficient?) +[\ ] +\( # open ( +( + [^\ ].*[^\ ] # author name +) +[\ ]+ +( # timestamp + [0-9]{4}-[0-9]{2}-[0-9]{2}\ [0-9]{2}:[0-9]{2}:[0-9]{2}\ [-+][0-9]{4} +) +[\ ]+ +( + [0-9]+ # line number +) +\) # close ) +( + .* # code, with leading space +) +$ +" + ) + .unwrap(); +} + +pub fn parse_git_blame_line<'a>(line: &'a str, timestamp_format: &str) -> Option> { + let caps = BLAME_LINE_REGEX.captures(line)?; + + let commit = caps.get(1).unwrap().as_str(); + let author = caps.get(2).unwrap().as_str(); + let timestamp = caps.get(3).unwrap().as_str(); + + let time = DateTime::parse_from_str(timestamp, timestamp_format).ok()?; + + let line_number = caps.get(4).unwrap().as_str().parse::().ok()?; + + let code = caps.get(5).unwrap().as_str(); + + Some(BlameLine { + commit, + author, + time, + line_number, + code, + }) +} + +lazy_static! { + pub static ref BLAME_PLACEHOLDER_REGEX: Regex = + format::make_placeholder_regex(&["timestamp", "author", "commit"]); +} + +pub fn format_blame_metadata( + format_data: &[format::FormatStringPlaceholderData], + blame: &BlameLine, + is_repeat: bool, + config: &config::Config, +) -> String { + let mut s = String::new(); + let mut suffix = ""; + for placeholder in format_data { + s.push_str(placeholder.prefix.as_str()); + + let alignment_spec = placeholder + .alignment_spec + .as_ref() + .unwrap_or(&format::Align::Left); + let width = placeholder.width.unwrap_or(15); + + let pad = |s| format::pad(s, width, alignment_spec, placeholder.precision); + let field = match placeholder.placeholder { + Some(Placeholder::Str("timestamp")) => Some(Cow::from( + chrono_humanize::HumanTime::from(blame.time).to_string(), + )), + Some(Placeholder::Str("author")) => Some(Cow::from(blame.author)), + Some(Placeholder::Str("commit")) => Some(delta::format_raw_line(blame.commit, config)), + None => None, + _ => unreachable!("Unexpected `git blame` input"), + }; + if let Some(field) = field { + let field = pad(&field); + if is_repeat { + s.push_str(&" ".repeat(measure_text_width(&field))); + } else { + s.push_str(&field) + } + } + suffix = placeholder.suffix.as_str(); + } + s.push_str(suffix); + s +} + +#[cfg(test)] +mod tests { + use itertools::Itertools; + use std::{collections::HashMap, io::Cursor}; + + use crate::tests::integration_test_utils; + + use super::*; + + #[test] + fn test_blame_line_regex() { + for line in &[ + "ea82f2d0 (Dan Davison 2021-08-22 18:20:19 -0700 120) let mut handled_line = self.handle_commit_meta_header_line()?", + "b2257cfa (Dan Davison 2020-07-18 15:34:43 -0400 1) use std::borrow::Cow;" + ] { + let caps = BLAME_LINE_REGEX.captures(line); + assert!(caps.is_some()); + assert!(parse_git_blame_line(line, "%Y-%m-%d %H:%M:%S %z").is_some()); + } + } + + #[test] + fn test_color_assignment() { + let mut writer = Cursor::new(vec![0; 512]); + let config = integration_test_utils::make_config_from_args(&["--blame-palette", "1 2"]); + let mut machine = StateMachine::new(&mut writer, &config); + + let blame_lines: HashMap<&str, &str> = vec![ + ( + "A", + "aaaaaaa (Dan Davison 2021-08-22 18:20:19 -0700 120) A", + ), + ( + "B", + "bbbbbbb (Dan Davison 2020-07-18 15:34:43 -0400 1) B", + ), + ( + "C", + "ccccccc (Dan Davison 2020-07-18 15:34:43 -0400 1) C", + ), + ] + .into_iter() + .collect(); + + // First commit gets first color + machine.line = blame_lines["A"].into(); + machine.handle_blame_line().unwrap(); + assert_eq!( + hashmap_items(&machine.blame_commit_colors), + &[("aaaaaaa", "1")] + ); + + // Repeat commit: same color + machine.line = blame_lines["A"].into(); + machine.handle_blame_line().unwrap(); + assert_eq!( + hashmap_items(&machine.blame_commit_colors), + &[("aaaaaaa", "1")] + ); + + // Second distinct commit gets second color + machine.line = blame_lines["B"].into(); + machine.handle_blame_line().unwrap(); + assert_eq!( + hashmap_items(&machine.blame_commit_colors), + &[("aaaaaaa", "1"), ("bbbbbbb", "2")] + ); + + // Third distinct commit gets first color (we only have 2 colors) + machine.line = blame_lines["C"].into(); + machine.handle_blame_line().unwrap(); + assert_eq!( + hashmap_items(&machine.blame_commit_colors), + &[("aaaaaaa", "1"), ("bbbbbbb", "2"), ("ccccccc", "1")] + ); + + // Now the first commit appears again. It would get the first color, but + // that would be a consecutive-commit-color-collision. So it gets the + // second color. + machine.line = blame_lines["A"].into(); + machine.handle_blame_line().unwrap(); + assert_eq!( + hashmap_items(&machine.blame_commit_colors), + &[("aaaaaaa", "2"), ("bbbbbbb", "2"), ("ccccccc", "1")] + ); + } + + fn hashmap_items(hashmap: &HashMap) -> Vec<(&str, &str)> { + hashmap + .iter() + .sorted() + .map(|(k, v)| (k.as_str(), v.as_str())) + .collect() + } +} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 6302c7396..db27e28fc 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -1,5 +1,6 @@ /// This module contains functions handling input lines encountered during the /// main `StateMachine::consume()` loop. +pub mod blame; pub mod commit_meta; pub mod diff_stat; pub mod draw; diff --git a/src/options/set.rs b/src/options/set.rs index 05946d43f..656918412 100644 --- a/src/options/set.rs +++ b/src/options/set.rs @@ -122,6 +122,9 @@ pub fn set_options( set_options!( [ + blame_format, + blame_palette, + blame_timestamp_format, color_only, commit_decoration_style, commit_regex, diff --git a/src/paint.rs b/src/paint.rs index 264cecdce..02854a5c8 100644 --- a/src/paint.rs +++ b/src/paint.rs @@ -476,6 +476,7 @@ impl<'p> Painter<'p> { (config.plus_style, config.plus_non_emph_style) } } + State::Blame(_, _) => (diff_sections[0].0, diff_sections[0].0), _ => (config.null_style, config.null_style), }; let fill_style = if style_sections_contain_more_than_one_style(diff_sections) { @@ -621,6 +622,7 @@ impl<'p> Painter<'p> { } State::HunkHeader(_, _) => true, State::HunkMinus(Some(_)) | State::HunkPlus(Some(_)) => false, + State::Blame(_, _) => true, _ => panic!( "should_compute_syntax_highlighting is undefined for state {:?}", state diff --git a/src/style.rs b/src/style.rs index 693583e76..8ce78875b 100644 --- a/src/style.rs +++ b/src/style.rs @@ -127,6 +127,22 @@ impl Style { } } +/// Interpret `color_string` as a color specifier and return it painted accordingly. +pub fn paint_color_string( + color_string: &str, + true_color: bool, +) -> ansi_term::ANSIGenericString { + if let Some(color) = color::parse_color(color_string, true_color) { + let style = ansi_term::Style { + background: Some(color), + ..ansi_term::Style::default() + }; + style.paint(color_string) + } else { + ansi_term::ANSIGenericString::from(color_string) + } +} + impl fmt::Display for Style { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.is_raw { diff --git a/src/subcommands/show_config.rs b/src/subcommands/show_config.rs index e6e6f2467..a9c8db2d7 100644 --- a/src/subcommands/show_config.rs +++ b/src/subcommands/show_config.rs @@ -1,11 +1,14 @@ use std::io::Write; +use itertools::Itertools; + use crate::bat_utils::output::PagingMode; use crate::cli; use crate::config; use crate::features::side_by_side::{Left, Right}; use crate::minusplus::*; use crate::paint::BgFillMethod; +use crate::style; pub fn show_config(config: &config::Config, writer: &mut dyn Write) -> std::io::Result<()> { // styles first @@ -23,7 +26,13 @@ pub fn show_config(config: &config::Config, writer: &mut dyn Write) -> std::io:: plus-non-emph-style = {plus_non_emph_style} plus-emph-style = {plus_emph_style} plus-empty-line-marker-style = {plus_empty_line_marker_style} - whitespace-error-style = {whitespace_error_style}", + whitespace-error-style = {whitespace_error_style} + blame-palette = {blame_palette}", + blame_palette = config + .blame_palette + .iter() + .map(|s| style::paint_color_string(s, config.true_color)) + .join(" "), commit_style = config.commit_style.to_painted_string(), file_style = config.file_style.to_painted_string(), hunk_header_style = config.hunk_header_style.to_painted_string(),