diff options
Diffstat (limited to 'src/handlers/grep.rs')
-rw-r--r-- | src/handlers/grep.rs | 212 |
1 files changed, 166 insertions, 46 deletions
diff --git a/src/handlers/grep.rs b/src/handlers/grep.rs index bbdee10c..63cc1a3e 100644 --- a/src/handlers/grep.rs +++ b/src/handlers/grep.rs @@ -91,55 +91,60 @@ impl<'a> StateMachine<'a> { State::Unknown => (None, None, &None, true), _ => (None, None, &None, false), }; + if !try_parse { + return Ok(false); + } - let mut handled_line = false; - if try_parse { - let line = self.line.clone(); // TODO: avoid clone + // Try parse_raw_grep_line on raw_line, and fall back to parse_grep_line + let raw_line = self.raw_line.clone(); // TODO: avoid clone + let line; + let grep_line = if let Some(grep_line) = parse_raw_grep_line(&raw_line) { + grep_line + } else { + line = self.line.clone(); // TODO: avoid clone if let Some(grep_line) = parse_grep_line(&line) { - if matches!(grep_line.line_type, LineType::Ignore) { - handled_line = true; - return Ok(handled_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 + grep_line + } else { + return Ok(false); } + }; + + if matches!(grep_line.line_type, LineType::Ignore) { + return Ok(true); + } + 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_section { + self.painter.set_highlighter() } - Ok(handled_line) + if new_path { + self.painter.set_syntax(Some(grep_line.path.as_ref())); + 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."), + }?; + Ok(true) } // Emulate ripgrep output: each section of hits from the same path has a header line, @@ -464,6 +469,7 @@ fn make_output_config() -> GrepOutputConfig { } enum GrepLineRegex { + WithColor, WithFileExtensionAndLineNumber, WithFileExtension, WithFileExtensionNoSpaces, @@ -471,6 +477,11 @@ enum GrepLineRegex { } lazy_static! { + static ref GREP_LINE_REGEX_ASSUMING_COLOR: Regex = + make_grep_line_regex(GrepLineRegex::WithColor); +} + +lazy_static! { static ref GREP_LINE_REGEX_ASSUMING_FILE_EXTENSION_AND_LINE_NUMBER: Regex = make_grep_line_regex(GrepLineRegex::WithFileExtensionAndLineNumber); } @@ -516,6 +527,15 @@ fn make_grep_line_regex(regex_variant: GrepLineRegex) -> Regex { // Make-7-file-7-xxx let file_path = match regex_variant { + GrepLineRegex::WithColor => { + r" + \x1b\[35m # starts with ANSI color sequence + ( # 1. file name + [^\x1b]* # anything + ) + \x1b\[m # ends with ANSI color reset + " + } GrepLineRegex::WithFileExtensionAndLineNumber | GrepLineRegex::WithFileExtension => { r" ( # 1. file name (colons not allowed) @@ -545,6 +565,51 @@ fn make_grep_line_regex(regex_variant: GrepLineRegex) -> Regex { }; let separator = match regex_variant { + GrepLineRegex::WithColor => { + r#" + \x1b\[36m # starts with ANSI color sequence for separator + (?: + ( + : # 2. match marker + \x1b\[m # ANSI color reset + (?: # optional: line number followed by second match marker + \x1b\[32m # ANSI color sequence for line number + ([0-9]+) # 3. line number + \x1b\[m # ANSI color reset + \x1b\[36m # ANSI color sequence for separator + : # second match marker + \x1b\[m # ANSI color reset + )? + ) + | + ( + - # 4. nomatch marker + \x1b\[m # ANSI color reset + (?: # optional: line number followed by second nomatch marker + \x1b\[32m # ANSI color sequence for line number + ([0-9]+) # 5. line number + \x1b\[m # ANSI color reset + \x1b\[36m # ANSI color sequence for separator + - # second nomatch marker + \x1b\[m # ANSI color reset + )? + ) + | + ( + = # 6. context header marker + \x1b\[m # ANSI color reset + (?: # optional: line number followed by second context header marker + \x1b\[32m # ANSI color sequence for line number + ([0-9]+) # 7. line number + \x1b\[m # ANSI color reset + \x1b\[36m # ANSI color sequence for separator + = # second context header marker + \x1b\[m # ANSI color reset + )? + ) + ) + "# + } GrepLineRegex::WithFileExtensionAndLineNumber => { r#" (?: @@ -617,6 +682,23 @@ pub fn parse_grep_line(line: &str) -> Option<GrepLine> { } } +pub fn parse_raw_grep_line(raw_line: &str) -> Option<GrepLine> { + // Early exit if we don't have an escape sequence + if !raw_line.starts_with('\x1b') { + return None; + } + if !matches!( + &*process::calling_process(), + process::CallingProcess::GitGrep(_) | process::CallingProcess::OtherGrep + ) { + return None; + } + _parse_grep_line(&GREP_LINE_REGEX_ASSUMING_COLOR, raw_line).map(|mut grep_line| { + grep_line.code = ansi::strip_ansi_codes(&grep_line.code).into(); + grep_line + }) +} + pub fn _parse_grep_line<'b>(regex: &Regex, line: &'b str) -> Option<GrepLine<'b>> { let caps = regex.captures(line)?; let file = caps.get(1).unwrap().as_str().into(); @@ -649,7 +731,9 @@ pub fn _parse_grep_line<'b>(regex: &Regex, line: &'b str) -> Option<GrepLine<'b> #[cfg(test)] mod tests { - use crate::handlers::grep::{parse_grep_line, GrepLine, GrepType, LineType}; + use crate::handlers::grep::{ + parse_grep_line, parse_raw_grep_line, GrepLine, GrepType, LineType, + }; use crate::utils::process::tests::FakeParentArgs; #[test] @@ -863,6 +947,42 @@ mod tests { } #[test] + fn test_parse_grep_color() { + let fake_parent_grep_command = + "/usr/local/bin/git --doesnt-matter grep --nor-this nor_this -- nor_this"; + let _args = FakeParentArgs::for_scope(fake_parent_grep_command); + + let e = "\x1B"; + assert_eq!( + parse_raw_grep_line(&format!( + r#"{e}[35msrc/zlib-ng/configure{e}[m{e}[36m:{e}[m -a*=* | -{e}[1;32m-arch{e}[ms=*) ARCHS=$(echo $1 | sed 's/.*=//'); shift ;;"# + )), + Some(GrepLine { + grep_type: GrepType::Classic, + path: "src/zlib-ng/configure".into(), + line_number: None, + line_type: LineType::Match, + code: r#" -a*=* | --archs=*) ARCHS=$(echo $1 | sed 's/.*=//'); shift ;;"#.into(), + submatches: None, + }) + ); + + assert_eq!( + parse_raw_grep_line(&format!( + r#"{e}[35msrc/zlib-ng/configure{e}[m{e}[36m:{e}[m{e}[32m214{e}[m{e}[36m:{e}[m -a*=* | -{e}[1;32m-arch{e}[ms=*) ARCHS=$(echo $1 | sed 's/.*=//'); shift ;;"# + )), + Some(GrepLine { + grep_type: GrepType::Classic, + path: "src/zlib-ng/configure".into(), + line_number: Some(214), + line_type: LineType::Match, + code: r#" -a*=* | --archs=*) ARCHS=$(echo $1 | sed 's/.*=//'); shift ;;"#.into(), + submatches: None, + }) + ); + } + + #[test] fn test_parse_grep_no_match() { let fake_parent_grep_command = "/usr/local/bin/git --doesnt-matter grep --nor-this nor_this -- nor_this"; |