summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDan Davison <dandavison7@gmail.com>2021-08-29 16:52:48 -0400
committerDan Davison <dandavison7@gmail.com>2021-08-29 22:51:06 -0400
commit97203ce2338a54b62080116f17b9b928279ae8c8 (patch)
tree09bbc096f9fba24fe7b9045f3aa459fce20dd5ea /src
parent93aabce9490d566d184579a0f9a182fd3cbf1d48 (diff)
Refactor: state machine handlers module
Diffstat (limited to 'src')
-rw-r--r--src/delta.rs606
-rw-r--r--src/handlers/blame.rs (renamed from src/blame.rs)61
-rw-r--r--src/handlers/commit_meta.rs59
-rw-r--r--src/handlers/diff_stat.rs102
-rw-r--r--src/handlers/draw.rs (renamed from src/draw.rs)0
-rw-r--r--src/handlers/file_meta.rs (renamed from src/parse.rs)442
-rw-r--r--src/handlers/file_meta_diff.rs20
-rw-r--r--src/handlers/file_meta_misc.rs16
-rw-r--r--src/handlers/hunk.rs102
-rw-r--r--src/handlers/hunk_header.rs (renamed from src/hunk_header.rs)157
-rw-r--r--src/handlers/mod.rs48
-rw-r--r--src/handlers/submodule.rs62
-rw-r--r--src/main.rs5
13 files changed, 865 insertions, 815 deletions
diff --git a/src/delta.rs b/src/delta.rs
index adec6823..54160712 100644
--- a/src/delta.rs
+++ b/src/delta.rs
@@ -4,20 +4,13 @@ use std::io::BufRead;
use std::io::Write;
use bytelines::ByteLines;
-use unicode_segmentation::UnicodeSegmentation;
use crate::ansi;
-use crate::blame;
-use crate::cli;
-use crate::color;
use crate::config::Config;
-use crate::draw;
use crate::features;
-use crate::format;
-use crate::hunk_header;
+use crate::handlers;
use crate::paint::Painter;
-use crate::parse;
-use crate::style::{self, DecorationStyle, Style};
+use crate::style::DecorationStyle;
#[derive(Clone, Debug, PartialEq)]
pub enum State {
@@ -40,15 +33,6 @@ pub enum Source {
Unknown,
}
-impl State {
- fn is_in_hunk(&self) -> bool {
- matches!(
- *self,
- State::HunkHeader(_, _) | State::HunkZero | State::HunkMinus(_) | State::HunkPlus(_)
- )
- }
-}
-
// Possible transitions, with actions on entry:
//
//
@@ -61,27 +45,27 @@ impl State {
// | HunkMinus | flush, emit | flush, emit | flush, emit | flush, emit | push | push |
// | HunkPlus | flush, emit | flush, emit | flush, emit | flush, emit | flush, push | push |
-struct StateMachine<'a> {
- line: String,
- raw_line: String,
- state: State,
- source: Source,
- minus_file: String,
- plus_file: String,
- minus_file_event: parse::FileEvent,
- plus_file_event: parse::FileEvent,
- diff_line: String,
- painter: Painter<'a>,
- config: &'a Config,
+pub struct StateMachine<'a> {
+ pub line: String,
+ pub raw_line: String,
+ pub state: State,
+ pub source: Source,
+ pub minus_file: String,
+ pub plus_file: String,
+ pub minus_file_event: handlers::file_meta::FileEvent,
+ pub plus_file_event: handlers::file_meta::FileEvent,
+ pub diff_line: String,
+ pub painter: Painter<'a>,
+ pub config: &'a Config,
// When a file is modified, we use lines starting with '---' or '+++' to obtain the file name.
// When a file is renamed without changes, we use lines starting with 'rename' to obtain the
// file name (there is no diff hunk and hence no lines starting with '---' or '+++'). But when
// a file is renamed with changes, both are present, and we rely on the following variables to
// avoid emitting the file meta header line twice (#245).
- current_file_pair: Option<(String, String)>,
- handled_file_meta_header_line_file_pair: Option<(String, String)>,
- blame_commit_colors: HashMap<String, String>,
+ pub current_file_pair: Option<(String, String)>,
+ pub handled_file_meta_header_line_file_pair: Option<(String, String)>,
+ pub blame_commit_colors: HashMap<String, String>,
}
pub fn delta<I>(lines: ByteLines<I>, writer: &mut dyn Write, config: &Config) -> std::io::Result<()>
@@ -100,8 +84,8 @@ impl<'a> StateMachine<'a> {
source: Source::Unknown,
minus_file: "".to_string(),
plus_file: "".to_string(),
- minus_file_event: parse::FileEvent::NoEvent,
- plus_file_event: parse::FileEvent::NoEvent,
+ minus_file_event: handlers::file_meta::FileEvent::NoEvent,
+ plus_file_event: handlers::file_meta::FileEvent::NoEvent,
diff_line: "".to_string(),
current_file_pair: None,
handled_file_meta_header_line_file_pair: None,
@@ -129,7 +113,7 @@ impl<'a> StateMachine<'a> {
|| self.handle_file_meta_minus_line()?
|| self.handle_file_meta_plus_line()?
|| self.handle_hunk_header_line()?
- || self.handle_additional_file_meta_cases()?
+ || self.handle_file_meta_misc_lines()?
|| self.handle_submodule_log_line()?
|| self.handle_submodule_short_line()?
|| self.handle_hunk_line()?
@@ -177,526 +161,10 @@ impl<'a> StateMachine<'a> {
}
/// Should a handle_* function be called on this element?
- fn should_handle(&self) -> bool {
+ pub fn should_handle(&self) -> bool {
let style = self.config.get_style(&self.state);
!(style.is_raw && style.decoration_style == DecorationStyle::NoDecoration)
}
-
- #[inline]
- fn test_commit_meta_header_line(&self) -> bool {
- self.config.commit_regex.is_match(&self.line)
- }
-
- fn handle_commit_meta_header_line(&mut self) -> std::io::Result<bool> {
- if !self.test_commit_meta_header_line() {
- return Ok(false);
- }
- let mut handled_line = false;
- self.painter.paint_buffered_minus_and_plus_lines();
- self.state = State::CommitMeta;
- if self.should_handle() {
- self.painter.emit()?;
- self._handle_commit_meta_header_line()?;
- handled_line = true
- }
- Ok(handled_line)
- }
-
- fn _handle_commit_meta_header_line(&mut self) -> std::io::Result<()> {
- if self.config.commit_style.is_omitted {
- return Ok(());
- }
- let (mut draw_fn, pad, decoration_ansi_term_style) =
- draw::get_draw_function(self.config.commit_style.decoration_style);
- let (formatted_line, formatted_raw_line) = if self.config.hyperlinks {
- (
- features::hyperlinks::format_commit_line_with_osc8_commit_hyperlink(
- &self.line,
- self.config,
- ),
- features::hyperlinks::format_commit_line_with_osc8_commit_hyperlink(
- &self.raw_line,
- self.config,
- ),
- )
- } else {
- (Cow::from(&self.line), Cow::from(&self.raw_line))
- };
-
- draw_fn(
- self.painter.writer,
- &format!("{}{}", formatted_line, if pad { " " } else { "" }),
- &format!("{}{}", formatted_raw_line, if pad { " " } else { "" }),
- &self.config.decorations_width,
- self.config.commit_style,
- decoration_ansi_term_style,
- )?;
- Ok(())
- }
-
- #[inline]
- fn test_diff_stat_line(&self) -> bool {
- (self.state == State::CommitMeta || self.state == State::Unknown)
- && self.line.starts_with(' ')
- }
-
- fn handle_diff_stat_line(&mut self) -> std::io::Result<bool> {
- if !self.test_diff_stat_line() {
- return Ok(false);
- }
- let mut handled_line = false;
- if self.config.relative_paths {
- if let Some(cwd) = self.config.cwd_relative_to_repo_root.as_deref() {
- if let Some(replacement_line) = parse::relativize_path_in_diff_stat_line(
- &self.raw_line,
- cwd,
- self.config.diff_stat_align_width,
- ) {
- self.painter.emit()?;
- writeln!(self.painter.writer, "{}", replacement_line)?;
- handled_line = true
- }
- }
- }
- Ok(handled_line)
- }
-
- #[inline]
- fn test_file_meta_diff_line(&self) -> bool {
- self.line.starts_with("diff ")
- }
-
- #[allow(clippy::unnecessary_wraps)]
- fn handle_file_meta_diff_line(&mut self) -> std::io::Result<bool> {
- if !self.test_file_meta_diff_line() {
- return Ok(false);
- }
- self.painter.paint_buffered_minus_and_plus_lines();
- self.state = State::FileMeta;
- self.handled_file_meta_header_line_file_pair = None;
- self.diff_line = self.line.clone();
- Ok(false)
- }
-
- #[inline]
- fn test_file_meta_minus_line(&self) -> bool {
- (self.state == State::FileMeta || self.source == Source::DiffUnified)
- && (self.line.starts_with("--- ")
- || self.line.starts_with("rename from ")
- || self.line.starts_with("copy from ")
- || self.line.starts_with("old mode "))
- }
-
- fn handle_file_meta_minus_line(&mut self) -> std::io::Result<bool> {
- if !self.test_file_meta_minus_line() {
- return Ok(false);
- }
- let mut handled_line = false;
-
- let (path_or_mode, file_event) = parse::parse_file_meta_line(
- &self.line,
- self.source == Source::GitDiff,
- if self.config.relative_paths {
- self.config.cwd_relative_to_repo_root.as_deref()
- } else {
- None
- },
- );
- // In the case of ModeChange only, the file path is taken from the diff
- // --git line (since that is the only place the file path occurs);
- // otherwise it is taken from the --- / +++ line.
- self.minus_file = if let parse::FileEvent::ModeChange(_) = &file_event {
- parse::get_repeated_file_path_from_diff_line(&self.diff_line).unwrap_or(path_or_mode)
- } else {
- path_or_mode
- };
- self.minus_file_event = file_event;
-
- if self.source == Source::DiffUnified {
- self.state = State::FileMeta;
- self.painter
- .set_syntax(parse::get_file_extension_from_marker_line(&self.line));
- } else {
- self.painter
- .set_syntax(parse::get_file_extension_from_file_meta_line_file_path(
- &self.minus_file,
- ));
- }
-
- // In color_only mode, raw_line's structure shouldn't be changed.
- // So it needs to avoid fn _handle_file_meta_header_line
- // (it connects the plus_file and minus_file),
- // and to call fn handle_generic_file_meta_header_line directly.
- if self.config.color_only {
- _write_generic_file_meta_header_line(
- &self.line,
- &self.raw_line,
- &mut self.painter,
- self.config,
- )?;
- handled_line = true;
- }
- Ok(handled_line)
- }
-
- #[inline]
- fn test_file_meta_plus_line(&self) -> bool {
- (self.state == State::FileMeta || self.source == Source::DiffUnified)
- && (self.line.starts_with("+++ ")
- || self.line.starts_with("rename to ")
- || self.line.starts_with("copy to ")
- || self.line.starts_with("new mode "))
- }
-
- fn handle_file_meta_plus_line(&mut self) -> std::io::Result<bool> {
- if !self.test_file_meta_plus_line() {
- return Ok(false);
- }
- let mut handled_line = false;
- let (path_or_mode, file_event) = parse::parse_file_meta_line(
- &self.line,
- self.source == Source::GitDiff,
- if self.config.relative_paths {
- self.config.cwd_relative_to_repo_root.as_deref()
- } else {
- None
- },
- );
- // In the case of ModeChange only, the file path is taken from the diff
- // --git line (since that is the only place the file path occurs);
- // otherwise it is taken from the --- / +++ line.
- self.plus_file = if let parse::FileEvent::ModeChange(_) = &file_event {
- parse::get_repeated_file_path_from_diff_line(&self.diff_line).unwrap_or(path_or_mode)
- } else {
- path_or_mode
- };
- self.plus_file_event = file_event;
- self.painter
- .set_syntax(parse::get_file_extension_from_file_meta_line_file_path(
- &self.plus_file,
- ));
- self.current_file_pair = Some((self.minus_file.clone(), self.plus_file.clone()));
-
- // In color_only mode, raw_line's structure shouldn't be changed.
- // So it needs to avoid fn _handle_file_meta_header_line
- // (it connects the plus_file and minus_file),
- // and to call fn handle_generic_file_meta_header_line directly.
- if self.config.color_only {
- _write_generic_file_meta_header_line(
- &self.line,
- &self.raw_line,
- &mut self.painter,
- self.config,
- )?;
- handled_line = true
- } else if self.should_handle()
- && self.handled_file_meta_header_line_file_pair != self.current_file_pair
- {
- self.painter.emit()?;
- self._handle_file_meta_header_line(self.source == Source::DiffUnified)?;
- self.handled_file_meta_header_line_file_pair = self.current_file_pair.clone()
- }
- Ok(handled_line)
- }
-
- /// Construct file change line from minus and plus file and write with FileMeta styling.
- fn _handle_file_meta_header_line(&mut self, comparing: bool) -> std::io::Result<()> {
- let line = parse::get_file_change_description_from_file_paths(
- &self.minus_file,
- &self.plus_file,
- comparing,
- &self.minus_file_event,
- &self.plus_file_event,
- self.config,
- );
- // FIXME: no support for 'raw'
- _write_generic_file_meta_header_line(&line, &line, &mut self.painter, self.config)
- }
-
- #[inline]
- fn test_additional_file_meta_cases(&self) -> bool {
- self.source == Source::DiffUnified && self.line.starts_with("Only in ")
- || self.line.starts_with("Binary files ")
- }
-
- fn handle_additional_file_meta_cases(&mut self) -> std::io::Result<bool> {
- if !self.test_additional_file_meta_cases() {
- return Ok(false);
- }
- self._handle_additional_cases(State::FileMeta)
- }
-
- #[inline]
- fn test_submodule_log(&self) -> bool {
- self.line.starts_with("Submodule ")
- }
-
- fn handle_submodule_log_line(&mut self) -> std::io::Result<bool> {
- if !self.test_submodule_log() {
- return Ok(false);
- }
- self._handle_additional_cases(State::SubmoduleLog)
- }
-
- #[inline]
- fn test_submodule_short_line(&self) -> bool {
- matches!(self.state, State::HunkHeader(_, _))
- && self.line.starts_with("-Subproject commit ")
- || matches!(self.state, State::SubmoduleShort(_))
- && self.line.starts_with("+Subproject commit ")
- }
-
- fn handle_submodule_short_line(&mut self) -> std::io::Result<bool> {
- if !self.test_submodule_short_line() {
- return Ok(false);
- }
- if let Some(commit) = parse::get_submodule_short_commit(&self.line) {
- if let State::HunkHeader(_, _) = self.state {
- self.state = State::SubmoduleShort(commit.to_owned());
- } else if let State::SubmoduleShort(minus_commit) = &self.state {
- self.painter.emit()?;
- writeln!(
- self.painter.writer,
- "{}..{}",
- self.config
- .minus_style
- .paint(minus_commit.chars().take(7).collect::<String>()),
- self.config
- .plus_style
- .paint(commit.chars().take(7).collect::<String>()),
- )?;
- }
- }
- Ok(true)
- }
-
- /// 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.
- fn handle_blame_line(&mut self) -> std::io::Result<bool> {
- let mut handled_line = false;
- self.painter.emit()?;
- if matches!(self.state, State::Unknown | State::Blame(_)) {
- if let Some(blame) =
- blame::parse_git_blame_line(&self.line, &self.config.blame_timestamp_format)
- {
- // Determine color for this line
- let color = if let Some(color) = self.blame_commit_colors.get(blame.commit) {
- color
- } else {
- let n_commits = self.blame_commit_colors.len();
- let n_colors = self.config.blame_palette.len();
- let new_color = &self.config.blame_palette[(n_commits + 1) % n_colors];
- self.blame_commit_colors
- .insert(blame.commit.to_owned(), new_color.to_owned());
- new_color
- };
- let mut style = Style::from_colors(None, color::parse_color(color, true));
- style.is_syntax_highlighted = true;
-
- // Construct commit metadata, paint, and emit
- let format_data = format::parse_line_number_format(
- &self.config.blame_format,
- &*blame::BLAME_PLACEHOLDER_REGEX,
- );
- write!(
- self.painter.writer,
- "{}",
- style.paint(blame::format_blame_metadata(
- &format_data,
- &blame,
- self.config
- ))
- )?;
-
- // 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());
- }
- self.painter.syntax_highlight_and_paint_line(
- blame.code,
- style,
- self.state.clone(),
- true,
- );
- handled_line = true
- }
- }
- Ok(handled_line)
- }
-
- fn _handle_additional_cases(&mut self, to_state: State) -> std::io::Result<bool> {
- let mut handled_line = false;
-
- // Additional cases:
- //
- // 1. When comparing directories with diff -u, if filenames match between the
- // directories, the files themselves will be compared. However, if an equivalent
- // filename is not present, diff outputs a single line (Only in...) starting
- // indicating that the file is present in only one of the directories.
- //
- // 2. Git diff emits lines describing submodule state such as "Submodule x/y/z contains
- // untracked content"
- //
- // See https://github.com/dandavison/delta/issues/60#issuecomment-557485242 for a
- // proposal for more robust parsing logic.
-
- self.painter.paint_buffered_minus_and_plus_lines();
- self.state = to_state;
- if self.should_handle() {
- self.painter.emit()?;
- _write_generic_file_meta_header_line(
- &self.line,
- &self.raw_line,
- &mut self.painter,
- self.config,
- )?;
- handled_line = true;
- }
-
- Ok(handled_line)
- }
-
- #[inline]
- fn test_hunk_header_line(&self) -> bool {
- self.line.starts_with("@@")
- }
-
- fn handle_hunk_header_line(&mut self) -> std::io::Result<bool> {
- if !self.test_hunk_header_line() {
- return Ok(false);
- }
- self.state = State::HunkHeader(self.line.clone(), self.raw_line.clone());
- Ok(true)
- }
-
- /// Emit the hunk header, with any requested decoration.
- fn emit_hunk_header_line(&mut self, line: &str, raw_line: &str) -> std::io::Result<bool> {
- self.painter.paint_buffered_minus_and_plus_lines();
- self.painter.set_highlighter();
- self.painter.emit()?;
-
- let (code_fragment, line_numbers) = parse::parse_hunk_header(line);
- if self.config.line_numbers {
- self.painter
- .line_numbers_data
- .initialize_hunk(&line_numbers, self.plus_file.to_string());
- }
-
- if self.config.hunk_header_style.is_raw {
- hunk_header::write_hunk_header_raw(&mut self.painter, line, raw_line, self.config)?;
- } else if self.config.hunk_header_style.is_omitted {
- writeln!(self.painter.writer)?;
- } else {
- // Add a blank line below the hunk-header-line for readability, unless
- // color_only mode is active.
- if !self.config.color_only {
- writeln!(self.painter.writer)?;
- }
-
- hunk_header::write_hunk_header(
- &code_fragment,
- &line_numbers,
- &mut self.painter,
- line,
- &self.plus_file,
- self.config,
- )?;
- };
- self.painter.set_highlighter();
- Ok(true)
- }
-
- #[inline]
- fn test_hunk_line(&self) -> bool {
- self.state.is_in_hunk()
- }
-
- /// Handle a hunk line, i.e. a minus line, a plus line, or an unchanged line.
- // In the case of a minus or plus line, we store the line in a
- // buffer. When we exit the changed region we process the collected
- // minus and plus lines jointly, in order to paint detailed
- // highlighting according to inferred edit operations. In the case of
- // an unchanged line, we paint it immediately.
- fn handle_hunk_line(&mut self) -> std::io::Result<bool> {
- // A true hunk line should start with one of: '+', '-', ' '. However, handle_hunk_line
- // handles all lines until the state transitions away from the hunk states.
- if !self.test_hunk_line() {
- return Ok(false);
- }
- // Don't let the line buffers become arbitrarily large -- if we
- // were to allow that, then for a large deleted/added file we
- // would process the entire file before painting anything.
- if self.painter.minus_lines.len() > self.config.line_buffer_size
- || self.painter.plus_lines.len() > self.config.line_buffer_size
- {
- self.painter.paint_buffered_minus_and_plus_lines();
- }
- if let State::HunkHeader(line, raw_line) = &self.state.clone() {
- self.emit_hunk_header_line(line, raw_line)?;
- }
- self.state = match self.line.chars().next() {
- Some('-') => {
- if let State::HunkPlus(_) = self.state {
- self.painter.paint_buffered_minus_and_plus_lines();
- }
- let state = match self.config.inspect_raw_lines {
- cli::InspectRawLines::True
- if style::line_has_style_other_than(
- &self.raw_line,
- [*style::GIT_DEFAULT_MINUS_STYLE, self.config.git_minus_style].iter(),
- ) =>
- {
- State::HunkMinus(Some(self.painter.prepare_raw_line(&self.raw_line)))
- }
- _ => State::HunkMinus(None),
- };
- self.painter
- .minus_lines
- .push((self.painter.prepare(&self.line), state.clone()));
- state
- }
- Some('+') => {
- let state = match self.config.inspect_raw_lines {
- cli::InspectRawLines::True
- if style::line_has_style_other_than(
- &self.raw_line,
- [*style::GIT_DEFAULT_PLUS_STYLE, self.config.git_plus_style].iter(),
- ) =>
- {
- State::HunkPlus(Some(self.painter.prepare_raw_line(&self.raw_line)))
- }
- _ => State::HunkPlus(None),
- };
- self.painter
- .plus_lines
- .push((self.painter.prepare(&self.line), state.clone()));
- state
- }
- Some(' ') => {
- self.painter.paint_buffered_minus_and_plus_lines();
- self.painter.paint_zero_line(&self.line);
- State::HunkZero
- }
- _ => {
- // The first character here could be e.g. '\' from '\ No newline at end of file'. This
- // is not a hunk line, but the parser does not have a more accurate state corresponding
- // to this.
- self.painter.paint_buffered_minus_and_plus_lines();
- self.painter
- .output_buffer
- .push_str(&self.painter.expand_tabs(self.raw_line.graphemes(true)));
- self.painter.output_buffer.push('\n');
- State::HunkZero
- }
- };
- self.painter.emit()?;
- Ok(true)
- }
}
/// If output is going to a tty, emit hyperlinks if requested.
@@ -709,38 +177,6 @@ pub fn format_raw_line<'a>(line: &'a str, config: &Config) -> Cow<'a, str> {
}
}
-/// Write `line` with FileMeta styling.
-fn _write_generic_file_meta_header_line(
- line: &str,
- raw_line: &str,
- painter: &mut Painter,
- config: &Config,
-) -> std::io::Result<()> {
- // If file_style is "omit", we'll skip the process and print nothing.
- // However in the case of color_only mode,
- // we won't skip because we can't change raw_line structure.
- if config.file_style.is_omitted && !config.color_only {
- return Ok(());
- }
- let (mut draw_fn, pad, decoration_ansi_term_style) =
- draw::get_draw_function(config.file_style.decoration_style);
- // Prints the new line below file-meta-line.
- // However in the case of color_only mode,
- // we won't print it because we can't change raw_line structure.
- if !config.color_only {
- writeln!(painter.writer)?;
- }
- draw_fn(
- painter.writer,
- &format!("{}{}", line, if pad { " " } else { "" }),
- &format!("{}{}", raw_line, if pad { " " } else { "" }),
- &config.decorations_width,
- config.file_style,
- decoration_ansi_term_style,
- )?;
- Ok(())
-}
-
/// Try to detect what is producing the input for delta.
///
/// Currently can detect:
diff --git a/src/blame.rs b/src/handlers/blame.rs
index bf15fdb1..d7486303 100644
--- a/src/blame.rs
+++ b/src/handlers/blame.rs
@@ -2,9 +2,68 @@ use chrono::{DateTime, FixedOffset};
use lazy_static::lazy_static;
use regex::Regex;
+use crate::color;
use crate::config;
-use crate::delta;
+use crate::delta::{self, State, StateMachine};
use crate::format;
+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<bool> {
+ let mut handled_line = false;
+ self.painter.emit()?;
+ if matches!(self.state, State::Unknown | State::Blame(_)) {
+ if let Some(blame) =
+ parse_git_blame_line(&self.line, &self.config.blame_timestamp_format)
+ {
+ // Determine color for this line
+ let color = if let Some(color) = self.blame_commit_colors.get(blame.commit) {
+ color
+ } else {
+ let n_commits = self.blame_commit_colors.len();
+ let n_colors = self.config.blame_palette.len();
+ let new_color = &self.config.blame_palette[(n_commits + 1) % n_colors];
+ self.blame_commit_colors
+ .insert(blame.commit.to_owned(), new_color.to_owned());
+ new_color
+ };
+ let mut style = Style::from_colors(None, color::parse_color(color, true));
+ 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,
+