From 83ca127e9e6a1e78d28e83c7ebe11ac1a5cc5faf Mon Sep 17 00:00:00 2001 From: Dan Davison Date: Thu, 4 May 2023 22:53:53 -0400 Subject: Introduce grep-output-type option: ripgrep or classic --- README.md | 4 +- manual/src/grep.md | 22 ++- src/cli.rs | 46 +++++- src/config.rs | 56 +++++++- src/delta.rs | 4 +- src/features/hyperlinks.rs | 2 + src/handlers/grep.rs | 323 +++++++++++++++++++++++++++++++++---------- src/handlers/hunk_header.rs | 147 ++++++++++++++++---- src/handlers/ripgrep_json.rs | 2 + src/options/set.rs | 4 + src/paint.rs | 2 +- src/parse_styles.rs | 70 ++++++++-- 12 files changed, 555 insertions(+), 127 deletions(-) diff --git a/README.md b/README.md index 302de0de..1aa2a6f4 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,9 @@ Side-by-side view wraps long lines automatically: [[User manual](https://dandavison.github.io/delta/grep.html)] -
image
+
+image +
### Installation and usage diff --git a/manual/src/grep.md b/manual/src/grep.md index 60a61d07..a090389c 100644 --- a/manual/src/grep.md +++ b/manual/src/grep.md @@ -2,19 +2,33 @@ Delta applies syntax-highlighting and other enhancements to standard grep output such as from [ripgrep](https://github.com/BurntSushi/ripgrep/) (aka `rg`), `git grep`, grep, etc. If you don't need special features of `git grep`, then for best results pipe `rg --json` output to delta: this avoids parsing ambiguities that are inevitable with the output of `git grep` and `grep`. -To customize the colors and syntax highlighting, see `grep-match-line-style`, `grep-match-word-style`, `grep-context-line-style`, `grep-file-style`, `grep-line-number-style`. +To customize the colors and syntax highlighting, see the `grep-*` options in `delta --help`. + Note that `git grep` can display the "function context" for matches and that delta handles this output specially: see the `-p` and `-W` options of `git grep`. ```sh -rg --json handle | delta +rg --json -C 2 handle | delta ``` -
image
+
+image +
+ +If you enable hyperlinks then grep hits will be formatted as [OSC8 hyperlinks](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda) in terminal emulators that support the feature. If you're using VSCode, IntelliJ, or PyCharm, then use the dedicated URL handlers. I.e. one of the following lines: + +```gitconfig +[delta] + hyperlinks = true + hyperlinks-file-link-format = "vscode://file/{path}:{line}" + # or: hyperlinks-file-link-format = "idea://open?file={path}&line={line}" + # or: hyperlinks-file-link-format = "pycharm://open?file={path}&line={line}" +``` -If you enable hyperlinks then grep hits will be formatted as [OSC8 hyperlinks](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda) in terminal emulators that support the feature. It is then possible to make your OS handle a click on those links by opening your editor at the correct file and line number (e.g. via ). +For editors that don't have special URL handlers, it is possible to use a tool like to make your OS handle a click on those links by opening your editor at the correct file and line number, e.g. ```gitconfig [delta] hyperlinks = true hyperlinks-file-link-format = "file-line://{path}:{line}" + # Now configure your OS to handle "file-line" URLs ``` diff --git a/src/cli.rs b/src/cli.rs index 80c73b90..50dab5a6 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -396,17 +396,51 @@ pub struct Opt { /// See STYLES section. Defaults to zero-style. pub grep_context_line_style: Option, - #[arg(long = "grep-file-style", value_name = "STYLE")] + #[arg( + long = "grep-file-style", + default_value = "magenta", + value_name = "STYLE" + )] /// Style string for file paths in grep output. /// - /// See STYLES section. Defaults to hunk-header-file-path-style. - pub grep_file_style: Option, + /// See STYLES section. + pub grep_file_style: String, + + #[arg(long = "grep-header-decoration-style", value_name = "STYLE")] + /// Style string for the header decoration in grep output. + /// + /// Default is "none" when grep-ouput-type-is "ripgrep", otherwise defaults + /// to value of header-decoration-style. See hunk-header-decoration-style. + pub grep_header_decoration_style: Option, + + #[arg(long = "grep-header-file-style", value_name = "STYLE")] + /// Style string for the file path part of the header in grep output. + /// + /// See hunk_header_file_style. + pub grep_header_file_style: Option, + + #[arg(long = "grep-header-style", value_name = "STYLE")] + /// Style string for the header in grep output. + /// + /// See hunk-header-style. + pub grep_header_style: Option, - #[arg(long = "grep-line-number-style", value_name = "STYLE")] + #[arg( + long = "grep-line-number-style", + default_value = "green", + 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, + /// See STYLES section. + pub grep_line_number_style: String, + + #[arg(long = "grep-output-type", 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" if `rg --json` format is detected, otherwise "classic". + pub grep_output_type: Option, #[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 3d411ea4..cab21eb7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -65,15 +65,19 @@ pub struct Config { pub git_plus_style: Style, pub grep_context_line_style: Style, pub grep_file_style: Style, + pub classic_grep_header_file_style: Style, + pub classic_grep_header_style: Style, + pub ripgrep_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: Option, 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 +133,24 @@ pub struct Config { pub zero_style: Style, } +#[derive(Debug, Eq, PartialEq, Clone)] +pub enum GrepType { + 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,6 +159,7 @@ impl Config { State::HunkPlus(_, _) => &self.plus_style, State::CommitMeta => &self.commit_style, State::DiffHeader(_) => &self.file_style, + State::Grep(GrepType::Ripgrep, _, _, _) => &self.classic_grep_header_style, State::HunkHeader(_, _, _, _) => &self.hunk_header_style, State::SubmoduleLog => &self.file_style, _ => delta_unreachable("Unreachable code reached in get_style."), @@ -222,6 +245,13 @@ impl From for Config { opt.navigate_regex }; + let grep_output_type = match opt.grep_output_type.as_deref() { + Some("ripgrep") => Some(GrepType::Ripgrep), + Some("classic") => Some(GrepType::Classic), + None => None, + _ => 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 +301,36 @@ 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"], + classic_grep_header_file_style: styles["classic-grep-header-file-style"], + classic_grep_header_style: styles["classic-grep-header-style"], + ripgrep_header_style: styles["ripgrep-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 1b0b2980..7db15575 100644 --- a/src/delta.rs +++ b/src/delta.rs @@ -8,7 +8,9 @@ use bytelines::ByteLines; use crate::ansi; use crate::config::delta_unreachable; use crate::config::Config; +use crate::config::GrepType; use crate::features; +use crate::handlers::grep; use crate::handlers::hunk_header::ParsedHunkHeader; use crate::handlers::{self, merge_conflict}; use crate::paint::Painter; @@ -28,7 +30,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(GrepType, grep::LineType, String, Option), // In a line of `git grep` output (grep_type, line_type, path, line_number) 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 4747c131..f96922b4 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 eec32c8a..f8614f7d 100644 --- a/src/handlers/grep.rs +++ b/src/handlers/grep.rs @@ -1,18 +1,25 @@ use std::borrow::Cow; +use std::fmt::Write; use lazy_static::lazy_static; use regex::Regex; use serde::Deserialize; use crate::ansi; +use crate::config::{ + delta_unreachable, GrepType, HunkHeaderIncludeFilePath, HunkHeaderIncludeLineNumber, +}; use crate::delta::{State, StateMachine}; use crate::handlers::{self, ripgrep_json}; use crate::paint::{self, BgShouldFill, StyleSectionSpecifier}; use crate::style::Style; use crate::utils::{process, tabs}; +use super::hunk_header::HunkHeaderIncludeHunkLabel; + #[derive(Debug, PartialEq, Eq)] pub struct GrepLine<'b> { + pub grep_type: GrepType, pub path: Cow<'b, str>, pub line_number: Option, pub line_type: LineType, @@ -25,6 +32,7 @@ pub struct GrepLine<'b> { pub enum LineType { ContextHeader, Context, + FileHeader, Match, Ignore, } @@ -39,84 +47,228 @@ lazy_static! { static ref OUTPUT_CONFIG: GrepOutputConfig = make_output_config(); } +impl LineType { + fn file_path_separator(&self) -> &str { + // grep, rg, and git grep use ":" for matching lines + // and "-" for non-matching lines (and `git grep -W` + // uses "=" for a context header line). + match self { + LineType::Match => ":", + LineType::Context => "-", + LineType::ContextHeader => "=", + LineType::Ignore | LineType::FileHeader => "", + } + } +} + 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, previous_line_type, previous_line, try_parse) = match &self.state { + State::Grep(_, line_type, path, line_number) => { + (Some(path.clone()), Some(line_type), line_number, true) + } + State::Unknown => (None, None, &None, true), + _ => (None, None, &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)?, + let first_path = previous_path.is_none(); + let new_path = first_path || previous_path.as_deref() != Some(&grep_line.path); + // Emit a '--' section separator when output contains context lines (i.e. *grep option -A, -B, -C is in effect). + let new_section = !new_path + && (previous_line_type == Some(&LineType::Context) + || grep_line.line_type == LineType::Context) + && previous_line < &grep_line.line_number.as_ref().map(|n| n - 1); + self.state = State::Grep( + self.config + .grep_output_type + .clone() + .unwrap_or_else(|| grep_line.grep_type.clone()), + grep_line.line_type, + grep_line.path.to_string(), + grep_line.line_number, + ); + 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.state { + State::Grep(GrepType::Ripgrep, _, _, _) => self.emit_ripgrep_format_grep_line( + grep_line, + new_path, + first_path, + new_section, + ), + State::Grep(GrepType::Classic, _, _, _) => { + self.emit_classic_format_grep_line(grep_line) + } + _ => delta_unreachable("Impossible state while handling 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, + new_section: 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_line_of_code_with_optional_path_and_line_number( + "", + &[(0, 0)], + None, + &mut self.painter, + &self.line, + &grep_line.path, + self.config.ripgrep_header_style.decoration_style, + &self.config.grep_file_style, + &self.config.grep_line_number_style, + &HunkHeaderIncludeFilePath::Yes, + &HunkHeaderIncludeLineNumber::No, + &HunkHeaderIncludeHunkLabel::Yes, + "", + self.config, )? } - self._emit_file_and_line_number(grep_line)?; - self._emit_code(grep_line)?; + if new_section { + let _ = self.painter.output_buffer.write_str("--\n"); + } + // 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 = tabs::expand(&grep_line.code, &self.config.tab_cfg).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 = tabs::expand(&self.raw_line, &self.config.tab_cfg); + 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), + }; + handlers::hunk_header::write_line_of_code_with_optional_path_and_line_number( + &grep_line.code, + &[(grep_line.line_number.unwrap_or(0), 0)], + Some(code_style_sections), + &mut self.painter, + &self.line, + &grep_line.path, + crate::style::DecorationStyle::NoDecoration, + &self.config.grep_file_style, + &self.config.grep_line_number_style, + &HunkHeaderIncludeFilePath::No, + if grep_line.line_number.is_some() { + &HunkHeaderIncludeLineNumber::Yes + } else { + &HunkHeaderIncludeLineNumber::No + }, + &HunkHeaderIncludeHunkLabel::No, + grep_line.line_type.file_path_separator(), + 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 (`git grep -W`) + (LineType::ContextHeader, true) => { + handlers::hunk_header::write_line_of_code_with_optional_path_and_line_number( + &grep_line.code, + &[(grep_line.line_number.unwrap_or(0), 0)], + None, + &mut self.painter, + &self.line, + &grep_line.path, + self.config.classic_grep_header_style.decoration_style, + &self.config.classic_grep_header_file_style, + &self.config.grep_line_number_style, + &self.config.hunk_header_style_include_file_path, + &self.config.hunk_header_style_include_line_number, + &HunkHeaderIncludeHunkLabel::Yes, + grep_line.line_type.file_path_separator(), + 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` - // uses "=" for a context header line). - match grep_line.line_type { - LineType::Match => ":", - LineType::Context => "-", - LineType::ContextHeader => "=", - LineType::Ignore | LineType::FileHeader => "", - } + grep_line.line_type.file_path_separator() } else { // But ":" results in a "file/path:number:" // construct that terminal emulators are more likely @@ -144,7 +296,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 @@ -153,9 +305,7 @@ impl<'a> StateMachine<'a> { // (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(); + grep_line.code = tabs::expand(&grep_line.code, &self.config.tab_cfg).into(); make_style_sections( &grep_line.code, submatches, @@ -170,12 +320,12 @@ impl<'a> StateMachine<'a> { // enough. But at the 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); + self.raw_line = tabs::expand(&self.raw_line, &self.config.tab_cfg); get_code_style_sections( &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,13 +428,13 @@ fn make_output_config() -> GrepOutputConfig { GrepOutputConfig { render_context_header_as_hunk_header: true, add_navigate_marker_to_matches: false, - pad_line_number: false, + pad_line_number: true, } } _ => GrepOutputConfig { render_context_header_as_hunk_header: true, add_navigate_marker_to_matches: false, - pad_line_number: false, + pad_line_number: true, }, } } @@ -347,8 +497,8 @@ 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 [^:]* # anything - [^\ ]\.[^.\ :=-]{1,10} # extension - ) + [^\ ]\.[^.\ :=-]{1,10} # extension + ) " } GrepLineRegex::WithFileExtensionNoSpaces => { @@ -356,7 +506,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 => { @@ -365,7 +515,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 - ) + ) " } }; @@ -385,7 +535,7 @@ fn make_grep_line_regex(regex_variant: GrepLineRegex) -> Regex { ) | ( - = # 6. match marker + = # 6. context header marker ([0-9]+)= # 7. line number followed by second header marker ) ) @@ -394,7 +544,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 ) @@ -405,7 +555,7 @@ fn make_grep_line_regex(regex_variant: GrepLineRegex) -> Regex { ) | ( - = # 6. match marker + = # 6. context header marker (?:([0-9]+)=)? # 7. optional: line number followed by second header marker ) ) @@ -418,7 +568,7 @@ fn make_grep_line_regex(regex_variant: GrepLineRegex) -> Regex { ^ {file_path} {separator} -(.*) # 8. code (i.e. line contents) +(.*) # 8. code (i.e. line contents) $ ", )) @@ -464,6 +614,7 @@ pub fn _parse_grep_line<'b>(regex: &Regex, line: &'b str) -> Option let code = caps.get(8).unwrap().as_str().into(); Some(GrepLine { + grep_type: GrepType::Classic, path: file, line_number: *line_number, line_type: *line_type, @@ -474,7 +625,7 @@ pub fn _parse_grep_line<'b>(regex: &Regex, line: &'b str) -> Option #[cfg(test)] mod tests { - use crate::handlers::grep::{parse_grep_line, GrepLine, LineType}; + use crate::handlers::grep::{parse_grep_line, GrepLine, GrepType, LineType}; use crate::utils::process::tests::FakeParentArgs; #[test] @@ -485,6 +636,7 @@ mod tests { assert_eq!( parse_grep_line("src/co-7-fig.rs:xxx"), Some(GrepLine { + grep_type: GrepType::Classic, path: "src/co-7-fig.rs".into(), line_number: None, line_type: LineType::Match, @@ -496,6 +648,7 @@ mod tests { assert_eq!( parse_grep_line("src/config.rs:use crate::minusplus::MinusPlus;"), Some(GrepLine { + grep_type: GrepType::Classic, path: "src/config.rs".into(), line_number: None, line_type: LineType::Match, @@ -509,6 +662,7 @@ mod tests { "src/config.rs: pub line_numbers_style_minusplus: MinusPlus