summaryrefslogtreecommitdiffstats
path: root/src/handlers
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/handlers
parent93aabce9490d566d184579a0f9a182fd3cbf1d48 (diff)
Refactor: state machine handlers module
Diffstat (limited to 'src/handlers')
-rw-r--r--src/handlers/blame.rs181
-rw-r--r--src/handlers/commit_meta.rs59
-rw-r--r--src/handlers/diff_stat.rs102
-rw-r--r--src/handlers/draw.rs300
-rw-r--r--src/handlers/file_meta.rs536
-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.rs357
-rw-r--r--src/handlers/mod.rs48
-rw-r--r--src/handlers/submodule.rs62
11 files changed, 1783 insertions, 0 deletions
diff --git a/src/handlers/blame.rs b/src/handlers/blame.rs
new file mode 100644
index 00000000..d7486303
--- /dev/null
+++ b/src/handlers/blame.rs
@@ -0,0 +1,181 @@
+use chrono::{DateTime, FixedOffset};
+use lazy_static::lazy_static;
+use regex::Regex;
+
+use crate::color;
+use crate::config;
+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,
+ );
+ write!(
+ self.painter.writer,
+ "{}",
+ style.paint(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)
+ }
+}
+
+#[derive(Debug)]
+pub struct BlameLine<'a> {
+ pub commit: &'a str,
+ pub author: &'a str,
+ pub time: DateTime<FixedOffset>,
+ 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]{8} # commit hash
+)
+[\ ]
+\( # 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<BlameLine<'a>> {
+ if let Some(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();
+ if let Ok(time) = DateTime::parse_from_str(timestamp, timestamp_format) {
+ let line_number_str = caps.get(4).unwrap().as_str();
+ if let Ok(line_number) = line_number_str.parse::<usize>() {
+ let code = caps.get(5).unwrap().as_str();
+ Some(BlameLine {
+ commit,
+ author,
+ time,
+ line_number,
+ code,
+ })
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+}
+
+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,
+ config: &config::Config,
+) -> String {
+ let mut s = String::new();
+ let mut suffix = "";
+ for placeholder in format_data {
+ s.push_str(placeholder.prefix);
+
+ let alignment_spec = placeholder.alignment_spec.unwrap_or("<");
+ let width = placeholder.width.unwrap_or(15);
+
+ let pad = |s| format::pad(s, width, alignment_spec);
+ match placeholder.placeholder {
+ Some("timestamp") => s.push_str(&pad(
+ &chrono_humanize::HumanTime::from(blame.time).to_string()
+ )),
+ Some("author") => s.push_str(&pad(blame.author)),
+ Some("commit") => s.push_str(&pad(&delta::format_raw_line(blame.commit, config))),
+ None => {}
+ Some(_) => unreachable!(),
+ }
+ suffix = placeholder.suffix;
+ }
+ s.push_str(suffix);
+ s
+}
+
+#[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());
+ }
+}
diff --git a/src/handlers/commit_meta.rs b/src/handlers/commit_meta.rs
new file mode 100644
index 00000000..2ad59bf1
--- /dev/null
+++ b/src/handlers/commit_meta.rs
@@ -0,0 +1,59 @@
+use std::borrow::Cow;
+
+use super::draw;
+use crate::delta::{State, StateMachine};
+use crate::features;
+
+impl<'a> StateMachine<'a> {
+ #[inline]
+ fn test_commit_meta_header_line(&self) -> bool {
+ self.config.commit_regex.is_match(&self.line)
+ }
+
+ pub 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(())
+ }
+}
diff --git a/src/handlers/diff_stat.rs b/src/handlers/diff_stat.rs
new file mode 100644
index 00000000..39a2130f
--- /dev/null
+++ b/src/handlers/diff_stat.rs
@@ -0,0 +1,102 @@
+use lazy_static::lazy_static;
+use regex::Regex;
+
+use crate::delta::{State, StateMachine};
+
+impl<'a> StateMachine<'a> {
+ #[inline]
+ fn test_diff_stat_line(&self) -> bool {
+ (self.state == State::CommitMeta || self.state == State::Unknown)
+ && self.line.starts_with(' ')
+ }
+
+ pub 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) = 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)
+ }
+}
+
+// A regex to capture the path, and the content from the pipe onwards, in lines
+// like these:
+// " src/delta.rs | 14 ++++++++++----"
+// " src/config.rs | 2 ++"
+lazy_static! {
+ static ref DIFF_STAT_LINE_REGEX: Regex =
+ Regex::new(r" ([^\| ][^\|]+[^\| ]) +(\| +[0-9]+ .+)").unwrap();
+}
+
+pub fn relativize_path_in_diff_stat_line(
+ line: &str,
+ cwd_relative_to_repo_root: &str,
+ diff_stat_align_width: usize,
+) -> Option<String> {
+ if let Some(caps) = DIFF_STAT_LINE_REGEX.captures(line) {
+ let path_relative_to_repo_root = caps.get(1).unwrap().as_str();
+ if let Some(relative_path) =
+ pathdiff::diff_paths(path_relative_to_repo_root, cwd_relative_to_repo_root)
+ {
+ if let Some(relative_path) = relative_path.to_str() {
+ let suffix = caps.get(2).unwrap().as_str();
+ let pad_width = diff_stat_align_width.saturating_sub(relative_path.len());
+ let padding = " ".repeat(pad_width);
+ return Some(format!(" {}{}{}", relative_path, padding, suffix));
+ }
+ }
+ }
+ None
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_diff_stat_line_regex_1() {
+ let caps = DIFF_STAT_LINE_REGEX.captures(" src/delta.rs | 14 ++++++++++----");
+ assert!(caps.is_some());
+ let caps = caps.unwrap();
+ assert_eq!(caps.get(1).unwrap().as_str(), "src/delta.rs");
+ assert_eq!(caps.get(2).unwrap().as_str(), "| 14 ++++++++++----");
+ }
+
+ #[test]
+ fn test_diff_stat_line_regex_2() {
+ let caps = DIFF_STAT_LINE_REGEX.captures(" src/config.rs | 2 ++");
+ assert!(caps.is_some());
+ let caps = caps.unwrap();
+ assert_eq!(caps.get(1).unwrap().as_str(), "src/config.rs");
+ assert_eq!(caps.get(2).unwrap().as_str(), "| 2 ++");
+ }
+
+ #[test]
+ fn test_relative_path() {
+ for (path, cwd_relative_to_repo_root, expected) in &[
+ ("file.rs", "", "file.rs"),
+ ("file.rs", "a/", "../file.rs"),
+ ("a/file.rs", "a/", "file.rs"),
+ ("a/b/file.rs", "a", "b/file.rs"),
+ ("c/d/file.rs", "a/b/", "../../c/d/file.rs"),
+ ] {
+ assert_eq!(
+ pathdiff::diff_paths(path, cwd_relative_to_repo_root),
+ Some(expected.into())
+ )
+ }
+ }
+}
diff --git a/src/handlers/draw.rs b/src/handlers/draw.rs
new file mode 100644
index 00000000..d7edd8cd
--- /dev/null
+++ b/src/handlers/draw.rs
@@ -0,0 +1,300 @@
+use std::cmp::max;
+use std::io::Write;
+
+use crate::ansi;
+use crate::cli::Width;
+use crate::style::{DecorationStyle, Style};
+
+pub type DrawFunction =
+ dyn FnMut(&mut dyn Write, &str, &str, &Width, Style, ansi_term::Style) -> std::io::Result<()>;
+
+pub fn get_draw_function(
+ decoration_style: DecorationStyle,
+) -> (Box<DrawFunction>, bool, ansi_term::Style) {
+ match decoration_style {
+ DecorationStyle::Box(style) => (Box::new(write_boxed), true, style),
+ DecorationStyle::BoxWithUnderline(style) => {
+ (Box::new(write_boxed_with_underline), true, style)
+ }
+ DecorationStyle::BoxWithOverline(style) => {
+ // TODO: not implemented
+ (Box::new(write_boxed), true, style)
+ }
+ DecorationStyle::BoxWithUnderOverline(style) => {
+ // TODO: not implemented
+ (Box::new(write_boxed), true, style)
+ }
+ DecorationStyle::Underline(style) => (Box::new(write_underlined), false, style),
+ DecorationStyle::Overline(style) => (Box::new(write_overlined), false, style),
+ DecorationStyle::UnderOverline(style) => (Box::new(write_underoverlined), false, style),
+ DecorationStyle::NoDecoration => (
+ Box::new(write_no_decoration),
+ false,
+ ansi_term::Style::new(),
+ ),
+ }
+}
+
+fn write_no_decoration(
+ writer: &mut dyn Write,
+ text: &str,
+ raw_text: &str,
+ _line_width: &Width, // ignored
+ text_style: Style,
+ _decoration_style: ansi_term::Style,
+) -> std::io::Result<()> {
+ if text_style.is_raw {
+ writeln!(writer, "{}", raw_text)?;
+ } else {
+ writeln!(writer, "{}", text_style.paint(text))?;
+ }
+ Ok(())
+}
+
+/// Write text to stream, surrounded by a box, leaving the cursor just
+/// beyond the bottom right corner.
+fn write_boxed(
+ writer: &mut dyn Write,
+ text: &str,
+ raw_text: &str,
+ _line_width: &Width, // ignored
+ text_style: Style,
+ decoration_style: ansi_term::Style,
+) -> std::io::Result<()> {
+ let up_left = if decoration_style.is_bold {
+ box_drawing::heavy::UP_LEFT
+ } else {
+ box_drawing::light::UP_LEFT
+ };
+ let box_width = ansi::measure_text_width(text);
+ write_boxed_partial(
+ writer,
+ text,
+ raw_text,
+ box_width,
+ text_style,
+ decoration_style,
+ )?;
+ writeln!(writer, "{}", decoration_style.paint(up_left))?;
+ Ok(())
+}
+
+/// Write text to stream, surrounded by a box, and extend a line from
+/// the bottom right corner.
+fn write_boxed_with_underline(
+ writer: &mut dyn Write,
+ text: &str,
+ raw_text: &str,
+ line_width: &Width,
+ text_style: Style,
+ decoration_style: ansi_term::Style,
+) -> std::io::Result<()> {
+ let box_width = ansi::measure_text_width(text);
+ write_boxed_with_horizontal_whisker(
+ writer,
+ text,
+ raw_text,
+ box_width,
+ text_style,
+ decoration_style,
+ )?;
+ let line_width = match *line_width {
+ Width::Fixed(n) => n,
+ Width::Variable => box_width,
+ };
+ write_horizontal_line(
+ writer,
+ if line_width > box_width {
+ line_width - box_width - 1
+ } else {
+ 0
+ },
+ text_style,
+ decoration_style,
+ )?;
+ writeln!(writer)?;
+ Ok(())
+}
+
+enum UnderOverline {
+ Under,
+ Over,
+ Underover,
+}
+
+fn write_underlined(
+ writer: &mut dyn Write,
+ text: &str,
+ raw_text: &str,
+ line_width: &Width,
+ text_style: Style,
+ decoration_style: ansi_term::Style,
+) -> std::io::Result<()> {
+ _write_under_or_over_lined(
+ UnderOverline::Under,
+ writer,
+ text,
+ raw_text,
+ line_width,
+ text_style,
+ decoration_style,
+ )
+}
+
+fn write_overlined(
+ writer: &mut dyn Write,
+ text: &str,
+ raw_text: &str,
+ line_width: &Width,
+ text_style: Style,
+ decoration_style: ansi_term::Style,
+) -> std::io::Result<()> {
+ _write_under_or_over_lined(
+ UnderOverline::Over,
+ writer,
+ text,
+ raw_text,
+ line_width,
+ text_style,
+ decoration_style,
+ )
+}
+
+fn write_underoverlined(
+ writer: &mut dyn Write,
+ text: &str,
+ raw_text: &str,
+ line_width: &Width,
+ text_style: Style,
+ decoration_style: ansi_term::Style,
+) -> std::io::Result<()> {
+ _write_under_or_over_lined(
+ UnderOverline::Underover,
+ writer,
+ text,
+ raw_text,
+ line_width,
+ text_style,
+ decoration_style,
+ )
+}
+
+fn _write_under_or_over_lined(
+ underoverline: UnderOverline,
+ writer: &mut dyn Write,
+ text: &str,
+ raw_text: &str,
+ line_width: &Width,
+ text_style: Style,
+ decoration_style: ansi_term::Style,
+) -> std::io::Result<()> {
+ let text_width = ansi::measure_text_width(text);
+ let line_width = match *line_width {
+ Width::Fixed(n) => max(n, text_width),
+ Width::Variable => text_width,
+ };
+ let mut write_line: Box<dyn FnMut(&mut dyn Write) -> std::io::Result<()>> =
+ Box::new(|writer| {
+ write_horizontal_line(writer, line_width, text_style, decoration_style)?;
+ writeln!(writer)?;
+ Ok(())
+ });
+ match underoverline {
+ UnderOverline::Under => {}
+ _ => write_line(writer)?,
+ }
+ if text_style.is_raw {
+ writeln!(writer, "{}", raw_text)?;
+ } else {
+ writeln!(writer, "{}", text_style.paint(text))?;
+ }
+ match underoverline {
+ UnderOverline::Over => {}
+ _ => write_line(writer)?,
+ }
+ Ok(())
+}
+
+fn write_horizontal_line(
+ writer: &mut dyn Write,
+ width: usize,
+ _text_style: Style,
+ decoration_style: ansi_term::Style,
+) -> std::io::Result<()> {
+ let horizontal = if decoration_style.is_bold {
+ box_drawing::heavy::HORIZONTAL
+ } else {
+ box_drawing::light::HORIZONTAL
+ };
+ write!(
+ writer,
+ "{}",
+ decoration_style.paint(horizontal.repeat(width))
+ )
+}
+
+fn write_boxed_with_horizontal_whisker(
+ writer: &mut dyn Write,
+ text: &str,
+ raw_text: &str,
+ box_width: usize,
+ text_style: Style,
+ decoration_style: ansi_term::Style,
+) -> std::io::Result<()> {
+ let up_horizontal = if decoration_style.is_bold {
+ box_drawing::heavy::UP_HORIZONTAL
+ } else {
+ box_drawing::light::UP_HORIZONTAL
+ };
+ write_boxed_partial(
+ writer,
+ text,
+ raw_text,
+ box_width,
+ text_style,
+ decoration_style,
+ )?;
+ write!(writer, "{}", decoration_style.paint(up_horizontal))?;
+ Ok(())
+}
+
+fn write_boxed_partial(
+ writer: &mut dyn Write,
+ text: &str,
+ raw_text: &str,
+ box_width: usize,
+ text_style: Style,
+ decoration_style: ansi_term::Style,
+) -> std::io::Result<()> {
+ let (horizontal, down_left, vertical) = if decoration_style.is_bold {
+ (
+ box_drawing::heavy::HORIZONTAL,
+ box_drawing::heavy::DOWN_LEFT,
+ box_drawing::heavy::VERTICAL,
+ )
+ } else {
+ (
+ box_drawing::light::HORIZONTAL,
+ box_drawing::light::DOWN_LEFT,
+ box_drawing::light::VERTICAL,
+ )
+ };
+ let horizontal_edge = horizontal.repeat(box_width);
+ writeln!(
+ writer,
+ "{}{}",
+ decoration_style.paint(&horizontal_edge),
+ decoration_style.paint(down_left),
+ )?;
+ if text_style.is_raw {
+ write!(writer, "{}", raw_text)?;
+ } else {
+ write!(writer, "{}", text_style.paint(text))?;
+ }
+ write!(
+ writer,
+ "{}\n{}",
+ decoration_style.paint(vertical),
+ decoration_style.paint(&horizontal_edge),
+ )
+}
diff --git a/src/handlers/file_meta.rs b/src/handlers/file_meta.rs
new file mode 100644
index 00000000..82109a91
--- /dev/null
+++ b/src/handlers/file_meta.rs
@@ -0,0 +1,536 @@
+use std::borrow::Cow;
+use std::path::Path;
+
+use unicode_segmentation::UnicodeSegmentation;
+
+use super::draw;
+use crate::config::Config;
+use crate::delta::{Source, State, StateMachine};
+use crate::features;
+use crate::paint::Painter;
+
+// https://git-scm.com/docs/git-config#Documentation/git-config.txt-diffmnemonicPrefix
+const DIFF_PREFIXES: [&str; 6] = ["a/", "b/", "c/", "i/", "o/", "w/"];
+
+#[derive(Debug, PartialEq)]
+pub enum FileEvent {
+ Change,
+ Copy,
+ Rename,
+ ModeChange(String),
+ NoEvent,
+}
+
+impl<'a> StateMachine<'a> {
+ #[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 "))
+ }
+
+ pub 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_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 FileEvent::ModeChange(_) = &file_event {
+ 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(get_file_extension_from_marker_line(&self.line));
+ } else {
+ self.painter
+ .set_syntax(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 "))
+ }
+
+ pub 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_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 FileEvent::ModeChange(_) = &file_event {
+ 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(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::DiffUnifi