From 13b7e12109fb602ea8329d4f6b6c3ce25684d05d Mon Sep 17 00:00:00 2001 From: Dan Davison Date: Thu, 4 May 2023 22:53:53 -0400 Subject: [PATCH] Introduce grep-output-type option: ripgrep or classic --- src/cli.rs | 41 +++++++ src/config.rs | 60 +++++++++- src/delta.rs | 2 +- src/features/hyperlinks.rs | 2 + src/handlers/grep.rs | 220 +++++++++++++++++++++++++++--------- src/handlers/hunk_header.rs | 122 ++++++++++++++++---- src/options/set.rs | 4 + src/paint.rs | 2 +- src/parse_styles.rs | 20 ++++ 9 files changed, 390 insertions(+), 83 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index eb1f9b857..913fb5129 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -402,12 +402,53 @@ pub struct Opt { /// See STYLES section. Defaults to hunk-header-file-path-style. pub grep_file_style: Option, + #[arg( + long = "grep-hunk-header-decoration-style", + default_value = "none", + value_name = "STYLE" + )] + /// Style string for the hunk-header decoration in grep output. + /// + /// See hunk-header-decoration-style. + pub grep_hunk_header_decoration_style: String, + + #[arg( + long = "grep-hunk-header-file-style", + default_value = "magenta", + value_name = "STYLE" + )] + /// Style string for the file path part of the hunk-header in grep output. + /// + /// See hunk_header_file_style. + pub grep_hunk_header_file_style: String, + + #[arg( + long = "grep-hunk-header-style", + default_value = "file syntax", + value_name = "STYLE" + )] + /// Style string for the hunk-header in grep output. + /// + /// See hunk_header_style. + pub grep_hunk_header_style: String, + #[arg(long = "grep-line-number-style", value_name = "STYLE")] /// Style string for line numbers in grep output. /// /// See STYLES section. Defaults to hunk-header-line-number-style. pub grep_line_number_style: Option, + #[arg( + long = "grep-output-type", + default_value = "ripgrep", + value_name = "OUTPUT_TYPE" + )] + /// Grep output format. Possible values: + /// "ripgrep" - file name printed once, followed by matching lines within that file, each preceded by a line number. + /// "classic" - file name:line number, followed by matching line. + /// Default is "ripgrep" + pub grep_output_type: String, + #[arg(long = "grep-match-line-style", value_name = "STYLE")] /// Style string for matching lines of grep output. /// diff --git a/src/config.rs b/src/config.rs index 10ab0ecac..477d56a72 100644 --- a/src/config.rs +++ b/src/config.rs @@ -65,15 +65,18 @@ pub struct Config { pub git_plus_style: Style, pub grep_context_line_style: Style, pub grep_file_style: Style, + pub grep_hunk_header_file_style: Style, + pub grep_hunk_header_style: Style, pub grep_line_number_style: Style, pub grep_match_line_style: Style, pub grep_match_word_style: Style, + pub grep_output_type: GrepOutputType, pub grep_separator_symbol: String, pub handle_merge_conflicts: bool, pub hunk_header_file_style: Style, pub hunk_header_line_number_style: Style, - pub hunk_header_style_include_file_path: bool, - pub hunk_header_style_include_line_number: bool, + pub hunk_header_style_include_file_path: HunkHeaderIncludeFilePath, + pub hunk_header_style_include_line_number: HunkHeaderIncludeLineNumber, pub hunk_header_style: Style, pub hunk_label: String, pub hyperlinks_commit_link_format: Option, @@ -129,6 +132,24 @@ pub struct Config { pub zero_style: Style, } +#[cfg_attr(test, derive(Clone))] +pub enum GrepOutputType { + Ripgrep, + Classic, +} + +#[cfg_attr(test, derive(Clone))] +pub enum HunkHeaderIncludeFilePath { + Yes, + No, +} + +#[cfg_attr(test, derive(Clone))] +pub enum HunkHeaderIncludeLineNumber { + Yes, + No, +} + impl Config { pub fn get_style(&self, state: &State) -> &Style { match state { @@ -137,12 +158,20 @@ impl Config { State::HunkPlus(_, _) => &self.plus_style, State::CommitMeta => &self.commit_style, State::DiffHeader(_) => &self.file_style, + State::Grep(_) => &self.grep_hunk_header_style, State::HunkHeader(_, _, _, _) => &self.hunk_header_style, State::SubmoduleLog => &self.file_style, _ => delta_unreachable("Unreachable code reached in get_style."), } } + pub fn get_hunk_header_file_style(&self, state: &State) -> &Style { + match state { + State::Grep(_) => &self.grep_hunk_header_file_style, + _ => &self.hunk_header_file_style, + } + } + pub fn git_config(&self) -> Option<&GitConfig> { self.git_config.as_ref() } @@ -222,6 +251,12 @@ impl From for Config { opt.navigate_regex }; + let grep_output_type = match opt.grep_output_type.as_ref() { + "ripgrep" => GrepOutputType::Ripgrep, + "classic" => GrepOutputType::Classic, + _ => fatal("Invalid option for grep-output-type: Expected \"ripgrep\" or \"classic\"."), + }; + #[cfg(not(test))] let cwd_of_delta_process = opt.env.current_dir; #[cfg(test)] @@ -271,22 +306,35 @@ impl From for Config { git_config: opt.git_config, grep_context_line_style: styles["grep-context-line-style"], grep_file_style: styles["grep-file-style"], + grep_hunk_header_file_style: styles["grep-hunk-header-file-style"], + grep_hunk_header_style: styles["grep-hunk-header-style"], grep_line_number_style: styles["grep-line-number-style"], grep_match_line_style: styles["grep-match-line-style"], grep_match_word_style: styles["grep-match-word-style"], + grep_output_type, grep_separator_symbol: opt.grep_separator_symbol, handle_merge_conflicts: !opt.raw, hunk_header_file_style: styles["hunk-header-file-style"], hunk_header_line_number_style: styles["hunk-header-line-number-style"], hunk_header_style: styles["hunk-header-style"], - hunk_header_style_include_file_path: opt + hunk_header_style_include_file_path: if opt .hunk_header_style .split(' ') - .any(|s| s == "file"), - hunk_header_style_include_line_number: opt + .any(|s| s == "file") + { + HunkHeaderIncludeFilePath::Yes + } else { + HunkHeaderIncludeFilePath::No + }, + hunk_header_style_include_line_number: if opt .hunk_header_style .split(' ') - .any(|s| s == "line-number"), + .any(|s| s == "line-number") + { + HunkHeaderIncludeLineNumber::Yes + } else { + HunkHeaderIncludeLineNumber::No + }, hyperlinks: opt.hyperlinks, hyperlinks_commit_link_format: opt.hyperlinks_commit_link_format, hyperlinks_file_link_format: opt.hyperlinks_file_link_format, diff --git a/src/delta.rs b/src/delta.rs index 1b0b2980b..740a6ad28 100644 --- a/src/delta.rs +++ b/src/delta.rs @@ -28,7 +28,7 @@ pub enum State { SubmoduleShort(String), // In a submodule section, with gitconfig diff.submodule = short Blame(String), // In a line of `git blame` output (key). GitShowFile, // In a line of `git show $revision:./path/to/file.ext` output - Grep, // In a line of `git grep` output + Grep(String), // In a line of `git grep` output (path) Unknown, // The following elements are created when a line is wrapped to display it: HunkZeroWrapped, // Wrapped unchanged line diff --git a/src/features/hyperlinks.rs b/src/features/hyperlinks.rs index 4747c1311..f96922b48 100644 --- a/src/features/hyperlinks.rs +++ b/src/features/hyperlinks.rs @@ -332,6 +332,8 @@ __path__: some matching line "raw", "--grep-line-number-style", "raw", + "--grep-output-type", + "classic", "--hunk-header-file-style", "raw", "--hunk-header-line-number-style", diff --git a/src/handlers/grep.rs b/src/handlers/grep.rs index 553104dc6..a0156fefc 100644 --- a/src/handlers/grep.rs +++ b/src/handlers/grep.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::fmt::Write; use lazy_static::lazy_static; use regex::Regex; @@ -6,12 +7,15 @@ use serde::Deserialize; use unicode_segmentation::UnicodeSegmentation; use crate::ansi; +use crate::config::{GrepOutputType, HunkHeaderIncludeFilePath, HunkHeaderIncludeLineNumber}; use crate::delta::{State, StateMachine}; use crate::handlers::{self, ripgrep_json}; use crate::paint::{self, expand_tabs, BgShouldFill, StyleSectionSpecifier}; use crate::style::Style; use crate::utils::process; +use super::hunk_header::HunkHeaderIncludeHunkLabel; + #[derive(Debug, PartialEq, Eq)] pub struct GrepLine<'b> { pub path: Cow<'b, str>, @@ -26,6 +30,7 @@ pub struct GrepLine<'b> { pub enum LineType { ContextHeader, Context, + FileHeader, Match, Ignore, } @@ -44,70 +49,177 @@ impl<'a> StateMachine<'a> { // If this is a line of git grep output then render it accordingly. pub fn handle_grep_line(&mut self) -> std::io::Result { self.painter.emit()?; - let mut handled_line = false; - let try_parse = matches!(&self.state, State::Grep | State::Unknown); + let (previous_path, try_parse) = match &self.state { + State::Grep(path) => (Some(path.clone()), true), + State::Unknown => (None, true), + _ => (None, false), + }; + let mut handled_line = false; if try_parse { let line = self.line.clone(); // TODO: avoid clone - if let Some(mut grep_line) = parse_grep_line(&line) { + if let Some(grep_line) = parse_grep_line(&line) { if matches!(grep_line.line_type, LineType::Ignore) { handled_line = true; return Ok(handled_line); } - - // Emit syntax-highlighted code - // TODO: Determine the language less frequently, e.g. only when the file changes. - if let Some(lang) = handlers::diff_header::get_extension(&grep_line.path) - .or(self.config.default_language.as_deref()) - { - self.painter.set_syntax(Some(lang)); - self.painter.set_highlighter(); - } - self.state = State::Grep; - - match ( - &grep_line.line_type, - OUTPUT_CONFIG.render_context_header_as_hunk_header, - ) { - // Emit context header line - (LineType::ContextHeader, true) => handlers::hunk_header::write_hunk_header( - &grep_line.code, - &[(grep_line.line_number.unwrap_or(0), 0)], - &mut self.painter, - &self.line, - &grep_line.path, - self.config, - )?, - _ => self._handle_grep_line(&mut grep_line)?, + self.state = State::Grep(grep_line.path.to_string()); + let first_path = previous_path.is_none(); + let new_path = first_path || previous_path.as_deref() != Some(&grep_line.path); + if new_path { + if let Some(lang) = handlers::diff_header::get_extension(&grep_line.path) + .or(self.config.default_language.as_deref()) + { + self.painter.set_syntax(Some(lang)); + self.painter.set_highlighter(); + } } + match self.config.grep_output_type { + GrepOutputType::Ripgrep => { + self.emit_ripgrep_format_grep_line(grep_line, new_path, first_path) + } + GrepOutputType::Classic => self.emit_classic_format_grep_line(grep_line), + }?; handled_line = true } } Ok(handled_line) } - fn _handle_grep_line(&mut self, grep_line: &mut GrepLine) -> std::io::Result<()> { - if self.config.navigate { - write!( - self.painter.writer, - "{}", - match ( - &grep_line.line_type, - OUTPUT_CONFIG.add_navigate_marker_to_matches - ) { - (LineType::Match, true) => "• ", - (_, true) => " ", - _ => "", - } + // Emulate ripgrep output: each section of hits from the same path has a header line, + // and sections are separated by a blank line. Set language whenever path changes. + fn emit_ripgrep_format_grep_line( + &mut self, + mut grep_line: GrepLine, + new_path: bool, + first_path: bool, + ) -> std::io::Result<()> { + if new_path { + // Emit new path header line + if !first_path { + let _ = self.painter.output_buffer.write_char('\n'); + } + handlers::hunk_header::write_hunk_header( + "", + &[(0, 0)], + None, + &mut self.painter, + &self.line, + &grep_line.path, + &self.state, + &HunkHeaderIncludeFilePath::Yes, + &HunkHeaderIncludeLineNumber::No, + &HunkHeaderIncludeHunkLabel::Yes, + self.config, )? } - self._emit_file_and_line_number(grep_line)?; - self._emit_code(grep_line)?; + // Emit the actual grep hit line + let code_style_sections = match (&grep_line.line_type, &grep_line.submatches) { + (LineType::Match, Some(submatches)) => { + // We expand tabs at this late stage because + // the tabs are escaped in the JSON, so + // expansion must come after JSON parsing. + // (At the time of writing, we are in this + // arm iff we are handling `ripgrep --json` + // output.) + grep_line.code = + paint::expand_tabs(grep_line.code.graphemes(true), self.config.tab_width) + .into(); + make_style_sections( + &grep_line.code, + submatches, + self.config.grep_match_word_style, + self.config.grep_match_line_style, + ) + } + (LineType::Match, None) => { + // HACK: We need tabs expanded, and we need + // the &str passed to + // `get_code_style_sections` to live long + // enough. But at this point it is guaranteed + // that this handler is going to handle this + // line, so mutating it is acceptable. + self.raw_line = expand_tabs(self.raw_line.graphemes(true), self.config.tab_width); + get_code_style_sections( + &self.raw_line, + self.config.grep_match_word_style, + self.config.grep_match_line_style, + &grep_line, + ) + .unwrap_or(StyleSectionSpecifier::Style( + self.config.grep_match_line_style, + )) + } + _ => StyleSectionSpecifier::Style(self.config.grep_context_line_style), + }; + // We are not really writing a hunk header; we're just using that API to + // write a syntax-highlighted line preceded by a possibly hyperlinked + // line number. + handlers::hunk_header::write_hunk_header( + &grep_line.code, + &[(grep_line.line_number.unwrap_or(0), 0)], + Some(code_style_sections), + &mut self.painter, + &self.line, + &grep_line.path, + &self.state, + &HunkHeaderIncludeFilePath::No, + if grep_line.line_number.is_some() { + &HunkHeaderIncludeLineNumber::Yes + } else { + &HunkHeaderIncludeLineNumber::No + }, + &HunkHeaderIncludeHunkLabel::No, + self.config, + ) + } + + fn emit_classic_format_grep_line(&mut self, grep_line: GrepLine) -> std::io::Result<()> { + match ( + &grep_line.line_type, + OUTPUT_CONFIG.render_context_header_as_hunk_header, + ) { + // Emit context header line + (LineType::ContextHeader, true) => handlers::hunk_header::write_hunk_header( + &grep_line.code, + &[(grep_line.line_number.unwrap_or(0), 0)], + None, + &mut self.painter, + &self.line, + &grep_line.path, + &self.state, + &self.config.hunk_header_style_include_file_path, + &self.config.hunk_header_style_include_line_number, + &HunkHeaderIncludeHunkLabel::Yes, + self.config, + )?, + _ => { + if self.config.navigate { + write!( + self.painter.writer, + "{}", + match ( + &grep_line.line_type, + OUTPUT_CONFIG.add_navigate_marker_to_matches + ) { + (LineType::Match, true) => "• ", + (_, true) => " ", + _ => "", + } + )? + } + self._emit_classic_format_file_and_line_number(&grep_line)?; + self._emit_classic_format_code(grep_line)?; + } + } Ok(()) } - fn _emit_file_and_line_number(&mut self, grep_line: &GrepLine) -> std::io::Result<()> { + fn _emit_classic_format_file_and_line_number( + &mut self, + grep_line: &GrepLine, + ) -> std::io::Result<()> { let separator = if self.config.grep_separator_symbol == "keep" { // grep, rg, and git grep use ":" for matching lines // and "-" for non-matching lines (and `git grep -W` @@ -145,7 +257,7 @@ impl<'a> StateMachine<'a> { Ok(()) } - fn _emit_code(&mut self, grep_line: &mut GrepLine) -> std::io::Result<()> { + fn _emit_classic_format_code(&mut self, mut grep_line: GrepLine) -> std::io::Result<()> { let code_style_sections = match (&grep_line.line_type, &grep_line.submatches) { (LineType::Match, Some(submatches)) => { // We expand tabs at this late stage because @@ -176,7 +288,7 @@ impl<'a> StateMachine<'a> { &self.raw_line, self.config.grep_match_word_style, self.config.grep_match_line_style, - grep_line, + &grep_line, ) .unwrap_or(StyleSectionSpecifier::Style( self.config.grep_match_line_style, @@ -278,14 +390,14 @@ fn make_output_config() -> GrepOutputConfig { // marker, since all non-header lines are matches. GrepOutputConfig { render_context_header_as_hunk_header: true, - add_navigate_marker_to_matches: false, - pad_line_number: false, + add_navigate_marker_to_matches: true, + pad_line_number: true, } } _ => GrepOutputConfig { render_context_header_as_hunk_header: true, - add_navigate_marker_to_matches: false, - pad_line_number: false, + add_navigate_marker_to_matches: true, + pad_line_number: true, }, } } @@ -349,7 +461,7 @@ fn make_grep_line_regex(regex_variant: GrepLineRegex) -> Regex { [^:|\ ] # try to be strict about what a file path can start with [^:]* # anything [^\ ]\.[^.\ :=-]{1,10} # extension - ) + ) " } GrepLineRegex::WithFileExtensionNoSpaces => { @@ -357,7 +469,7 @@ fn make_grep_line_regex(regex_variant: GrepLineRegex) -> Regex { ( # 1. file name (colons not allowed) [^:|\ ]+ # try to be strict about what a file path can start with [^\ ]\.[^.\ :=-]{1,6} # extension - ) + ) " } GrepLineRegex::WithoutSeparatorCharacters => { @@ -366,7 +478,7 @@ fn make_grep_line_regex(regex_variant: GrepLineRegex) -> Regex { [^:|\ =-] # try to be strict about what a file path can start with [^:=-]* # anything except separators [^:\ ] # a file name cannot end with whitespace - ) + ) " } }; @@ -395,7 +507,7 @@ fn make_grep_line_regex(regex_variant: GrepLineRegex) -> Regex { _ => { r#" (?: - ( + ( : # 2. match marker (?:([0-9]+):)? # 3. optional: line number followed by second match marker ) diff --git a/src/handlers/hunk_header.rs b/src/handlers/hunk_header.rs index 251e341d7..883981099 100644 --- a/src/handlers/hunk_header.rs +++ b/src/handlers/hunk_header.rs @@ -25,10 +25,10 @@ use lazy_static::lazy_static; use regex::Regex; use super::draw; -use crate::config::Config; +use crate::config::{Config, HunkHeaderIncludeFilePath, HunkHeaderIncludeLineNumber}; use crate::delta::{self, DiffType, InMergeConflict, MergeParents, State, StateMachine}; use crate::paint::{self, BgShouldFill, Painter, StyleSectionSpecifier}; -use crate::style::DecorationStyle; +use crate::style::{DecorationStyle, Style}; #[derive(Clone, Default, Debug, PartialEq, Eq)] pub struct ParsedHunkHeader { @@ -36,6 +36,11 @@ pub struct ParsedHunkHeader { line_numbers_and_hunk_lengths: Vec<(usize, usize)>, } +pub enum HunkHeaderIncludeHunkLabel { + Yes, + No, +} + impl<'a> StateMachine<'a> { #[inline] fn test_hunk_header_line(&self) -> bool { @@ -114,6 +119,7 @@ impl<'a> StateMachine<'a> { write_hunk_header( code_fragment, line_numbers_and_hunk_lengths, + None, &mut self.painter, line, if self.plus_file == "/dev/null" { @@ -121,6 +127,10 @@ impl<'a> StateMachine<'a> { } else { &self.plus_file }, + &self.state, + &self.config.hunk_header_style_include_file_path, + &self.config.hunk_header_style_include_line_number, + &HunkHeaderIncludeHunkLabel::Yes, self.config, )?; }; @@ -202,16 +212,22 @@ fn write_hunk_header_raw( Ok(()) } +#[allow(clippy::too_many_arguments)] pub fn write_hunk_header( code_fragment: &str, line_numbers_and_hunk_lengths: &[(usize, usize)], + style_sections: Option, painter: &mut Painter, line: &str, plus_file: &str, + state: &State, + include_file_path: &HunkHeaderIncludeFilePath, + include_line_number: &HunkHeaderIncludeLineNumber, + include_hunk_label: &HunkHeaderIncludeHunkLabel, config: &Config, ) -> std::io::Result<()> { let (mut draw_fn, _, decoration_ansi_term_style) = - draw::get_draw_function(config.hunk_header_style.decoration_style); + draw::get_draw_function(config.get_style(state).decoration_style); let line = if config.color_only { line.to_string() } else if !code_fragment.is_empty() { @@ -221,11 +237,25 @@ pub fn write_hunk_header( }; let plus_line_number = line_numbers_and_hunk_lengths[line_numbers_and_hunk_lengths.len() - 1].0; - let file_with_line_number = - paint_file_path_with_line_number(Some(plus_line_number), plus_file, config); + let file_with_line_number = paint_file_path_with_line_number( + Some(plus_line_number), + plus_file, + config.get_hunk_header_file_style(state), + &config.hunk_header_line_number_style, + include_file_path, + include_line_number, + config, + ); if !line.is_empty() || !file_with_line_number.is_empty() { - write_to_output_buffer(&file_with_line_number, line, painter, config); + write_to_output_buffer( + &file_with_line_number, + line, + style_sections, + include_hunk_label, + painter, + config, + ); draw_fn( painter.writer, &painter.output_buffer, @@ -244,19 +274,22 @@ pub fn write_hunk_header( fn paint_file_path_with_line_number( line_number: Option, plus_file: &str, + file_style: &Style, + line_number_style: &Style, + include_file_path: &HunkHeaderIncludeFilePath, + include_line_number: &HunkHeaderIncludeLineNumber, config: &Config, ) -> String { - let file_style = if config.hunk_header_style_include_file_path { - Some(config.hunk_header_file_style) - } else { - None + let file_style = match include_file_path { + HunkHeaderIncludeFilePath::Yes => Some(*file_style), + HunkHeaderIncludeFilePath::No => None, }; - let line_number_style = if config.hunk_header_style_include_line_number + let line_number_style = if matches!(include_line_number, HunkHeaderIncludeLineNumber::Yes) + && line_number.is_some() && !config.hunk_header_style.is_raw && !config.color_only - && line_number.is_some() { - Some(config.hunk_header_line_number_style) + Some(*line_number_style) } else { None }; @@ -276,10 +309,14 @@ fn paint_file_path_with_line_number( fn write_to_output_buffer( file_with_line_number: &str, line: String, + style_sections: Option, + include_hunk_label: &HunkHeaderIncludeHunkLabel, painter: &mut Painter, config: &Config, ) { - if !config.hunk_label.is_empty() { + if matches!(include_hunk_label, HunkHeaderIncludeHunkLabel::Yes) + && !config.hunk_label.is_empty() + { let _ = write!( &mut painter.output_buffer, "{} ", @@ -298,7 +335,7 @@ fn write_to_output_buffer( if !line.is_empty() { painter.syntax_highlight_and_paint_line( &line, - StyleSectionSpecifier::Style(config.hunk_header_style), + style_sections.unwrap_or(StyleSectionSpecifier::Style(config.hunk_header_style)), delta::State::HunkHeader( DiffType::Unified, ParsedHunkHeader::default(), @@ -392,7 +429,15 @@ pub mod tests { // This test confirms that `paint_file_path_with_line_number` returns a painted line number. let config = integration_test_utils::make_config_from_args(&[]); - let result = paint_file_path_with_line_number(Some(3), "some-file", &config); + let result = paint_file_path_with_line_number( + Some(3), + "some-file", + &config.hunk_header_style, + &config.hunk_header_line_number_style, + &config.hunk_header_style_include_file_path, + &config.hunk_header_style_include_line_number, + &config, + ); assert_eq!(result, "\u{1b}[34m3\u{1b}[0m"); } @@ -413,8 +458,15 @@ pub mod tests { let config = integration_test_utils::make_config_from_args(&["--features", "hyperlinks"]); let relative_path = PathBuf::from_iter(["some-dir", "some-file"]); - let result = - paint_file_path_with_line_number(Some(3), &relative_path.to_string_lossy(), &config); + let result = paint_file_path_with_line_number( + Some(3), + &relative_path.to_string_lossy(), + &config.hunk_header_style, + &config.hunk_header_line_number_style, + &config.hunk_header_style_include_file_path, + &config.hunk_header_style_include_line_number, + &config, + ); assert_eq!( result, @@ -438,8 +490,18 @@ pub mod tests { "omit", ]); - let result = paint_file_path_with_line_number(Some(3), "some-file", &config); + let result = paint_file_path_with_line_number( + Some(3), + "some-file", + &config.hunk_header_style, + &config.hunk_header_line_number_style, + &config.hunk_header_style_include_file_path, + &config.hunk_header_style_include_line_number, + &config, + ); + // result is + // "\u{1b}[1msome-file\u{1b}[0m:\u{1b}[34m3\u{1b}[0m" assert_eq!(result, ""); } @@ -458,7 +520,15 @@ pub mod tests { "hyperlinks", ]); - let result = paint_file_path_with_line_number(Some(3), "some-file", &config); + let result = paint_file_path_with_line_number( + Some(3), + "some-file", + &config.hunk_header_style, + &config.hunk_header_line_number_style, + &config.hunk_header_style_include_file_path, + &config.hunk_header_style_include_line_number, + &config, + ); assert_eq!(result, ""); } @@ -473,8 +543,18 @@ pub mod tests { "--navigate", ]); - let result = paint_file_path_with_line_number(Some(3), "δ some-file", &config); + let result = paint_file_path_with_line_number( + Some(3), + "δ some-file", + &config.hunk_header_style, + &config.hunk_header_line_number_style, + &config.hunk_header_style_include_file_path, + &config.hunk_header_style_include_line_number, + &config, + ); + // result is + // "\u{1b}[1mδ some-file\u{1b}[0m:\u{1b}[34m3\u{1b}[0m" assert_eq!(result, ""); } diff --git a/src/options/set.rs b/src/options/set.rs index 04f7d0e67..4644a9086 100644 --- a/src/options/set.rs +++ b/src/options/set.rs @@ -152,6 +152,10 @@ pub fn set_options( file_style, grep_context_line_style, grep_file_style, + grep_hunk_header_decoration_style, + grep_hunk_header_file_style, + grep_hunk_header_style, + grep_output_type, grep_line_number_style, grep_match_line_style, grep_match_word_style, diff --git a/src/paint.rs b/src/paint.rs index 67bd58c79..77e79835f 100644 --- a/src/paint.rs +++ b/src/paint.rs @@ -465,7 +465,7 @@ impl<'p> Painter<'p> { } State::Blame(_) => true, State::GitShowFile => true, - State::Grep => true, + State::Grep(_) => true, State::Unknown | State::CommitMeta | State::DiffHeader(_) diff --git a/src/parse_styles.rs b/src/parse_styles.rs index d0daf3420..d88958558 100644 --- a/src/parse_styles.rs +++ b/src/parse_styles.rs @@ -322,6 +322,16 @@ fn make_commit_file_hunk_header_styles(opt: &cli::Opt, styles: &mut HashMap<&str opt.git_config(), ), ), + ( + "grep-hunk-header-style", + style_from_str_with_handling_of_special_decoration_attributes( + &opt.grep_hunk_header_style, + None, + Some(&opt.grep_hunk_header_decoration_style), + true_color, + opt.git_config(), + ), + ), ( "hunk-header-style", style_from_str_with_handling_of_special_decoration_attributes( @@ -342,6 +352,16 @@ fn make_commit_file_hunk_header_styles(opt: &cli::Opt, styles: &mut HashMap<&str opt.git_config(), ), ), + ( + "grep-hunk-header-file-style", + style_from_str_with_handling_of_special_decoration_attributes( + &opt.grep_hunk_header_file_style, + None, + None, + true_color, + opt.git_config(), + ), + ), ( "hunk-header-line-number-style", style_from_str_with_handling_of_special_decoration_attributes(