summaryrefslogtreecommitdiffstats
path: root/src/handlers/grep.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/handlers/grep.rs')
-rw-r--r--src/handlers/grep.rs1166
1 files changed, 0 insertions, 1166 deletions
diff --git a/src/handlers/grep.rs b/src/handlers/grep.rs
deleted file mode 100644
index 8c3b6ce8..00000000
--- a/src/handlers/grep.rs
+++ /dev/null
@@ -1,1166 +0,0 @@
-use std::borrow::Cow;
-
-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<usize>,
- pub line_type: LineType,
- pub code: Cow<'b, str>,
- pub submatches: Option<Vec<(usize, usize)>>,
-}
-
-impl<'b> GrepLine<'b> {
- fn expand_tabs(&mut self, tab_cfg: &tabs::TabCfg) {
- let old_len = self.code.len();
- self.code = tabs::expand(&self.code, tab_cfg).into();
- let shift = self.code.len().saturating_sub(old_len);
- self.submatches = self.submatches.as_ref().map(|submatches| {
- submatches
- .iter()
- .map(|(a, b)| (a + shift, b + shift))
- .collect()
- });
- }
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)]
-#[serde(rename_all = "lowercase")]
-pub enum LineType {
- ContextHeader,
- Context,
- FileHeader,
- Match,
- Ignore,
-}
-
-struct GrepOutputConfig {
- add_navigate_marker_to_matches: bool,
- render_context_header_as_hunk_header: bool,
- pad_line_number: bool,
-}
-
-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<bool> {
- self.painter.emit()?;
-
- 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(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
- }
- }
- Ok(handled_line)
- }
-
- // 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 {
- writeln!(self.painter.writer)?;
- }
- 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,
- )?
- }
- if new_section {
- writeln!(self.painter.writer, "--")?;
- }
- // Emit the actual grep hit line
- let code_style_sections = match (&grep_line.line_type, &grep_line.submatches) {
- (LineType::Match, Some(_)) => {
- // 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.expand_tabs(&self.config.tab_cfg);
- make_style_sections(
- &grep_line.code,
- &grep_line.submatches.unwrap(),
- 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.path,
- grep_line.line_number,
- )
- .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_classic_format_file_and_line_number(
- &mut self,
- grep_line: &GrepLine,
- ) -> std::io::Result<()> {
- let separator = if self.config.grep_separator_symbol == "keep" {
- grep_line.line_type.file_path_separator()
- } else {
- // But ":" results in a "file/path:number:"
- // construct that terminal emulators are more likely
- // to recognize and render as a clickable link. If
- // navigate is enabled then there is already a good
- // visual indicator of match lines (in addition to
- // the grep-match-style highlighting) and so we use
- // ":" for matches and non-matches alike.
- &self.config.grep_separator_symbol
- };
- write!(
- self.painter.writer,
- "{}",
- paint::paint_file_path_with_line_number(
- grep_line.line_number,
- &grep_line.path,
- OUTPUT_CONFIG.pad_line_number,
- separator,
- true,
- Some(self.config.grep_file_style),
- Some(self.config.grep_line_number_style),
- self.config
- )
- )?;
- Ok(())
- }
-
- 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(_)) => {
- // 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.expand_tabs(&self.config.tab_cfg);
- make_style_sections(
- &grep_line.code,
- &grep_line.submatches.unwrap(),
- 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 the 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.path,
- grep_line.line_number,
- )
- .unwrap_or(StyleSectionSpecifier::Style(
- self.config.grep_match_line_style,
- ))
- }
- _ => StyleSectionSpecifier::Style(self.config.grep_context_line_style),
- };
- self.painter.syntax_highlight_and_paint_line(
- &format!("{}\n", grep_line.code),
- code_style_sections,
- self.state.clone(),
- BgShouldFill::default(),
- );
- Ok(())
- }
-}
-
-fn make_style_sections<'a>(
- line: &'a str,
- submatches: &[(usize, usize)],
- match_style: Style,
- non_match_style: Style,
-) -> StyleSectionSpecifier<'a> {
- let mut sections = Vec::new();
- let mut curr = 0;
- for (start_, end_) in submatches {
- let (start, end) = (*start_, *end_);
- if start > curr {
- sections.push((non_match_style, &line[curr..start]))
- };
- sections.push((match_style, &line[start..end]));
- curr = end;
- }
- if curr < line.len() {
- sections.push((non_match_style, &line[curr..]))
- }
- StyleSectionSpecifier::StyleSections(sections)
-}
-
-// Return style sections describing colors received from git.
-fn get_code_style_sections<'b>(
- raw_line: &'b str,
- match_style: Style,
- non_match_style: Style,
- path: &str,
- line_number: Option<usize>,
-) -> Option<StyleSectionSpecifier<'b>> {
- if let Some(prefix_end) = ansi::ansi_preserving_index(
- raw_line,
- match line_number {
- Some(n) => format!("{}:{}:", path, n).len() - 1,
- None => path.len(),
- },
- ) {
- let match_style_sections = ansi::parse_style_sections(&raw_line[(prefix_end + 1)..])
- .iter()
- .map(|(ansi_term_style, s)| {
- if ansi_term_style.is_bold
- && ansi_term_style.foreground == Some(ansi_term::Colour::Red)
- {
- (match_style, *s)
- } else {
- (non_match_style, *s)
- }
- })
- .collect();
- Some(StyleSectionSpecifier::StyleSections(match_style_sections))
- } else {
- None
- }
-}
-
-fn make_output_config() -> GrepOutputConfig {
- match &*process::calling_process() {
- process::CallingProcess::GitGrep(command_line)
- if command_line.short_options.contains("-W")
- || command_line.long_options.contains("--function-context") =>
- {
- // --function-context is in effect: i.e. the entire function is
- // being displayed. In that case we don't render the first line as a
- // header, since the second line is the true next line, and it will
- // be more readable to have these displayed normally. We do add the
- // navigate marker, since match lines will be surrounded by (many)
- // non-match lines. And, since we are printing (many) successive lines
- // of code, we pad line numbers <100 in order to maintain code
- // alignment up to line 9999.
- GrepOutputConfig {
- render_context_header_as_hunk_header: false,
- add_navigate_marker_to_matches: true,
- pad_line_number: true,
- }
- }
- process::CallingProcess::GitGrep(command_line)
- if command_line.short_options.contains("-p")
- || command_line.long_options.contains("--show-function") =>
- {
- // --show-function is in effect, i.e. the function header is being
- // displayed, along with matches within the function. Therefore we
- // render the first line as a header, but we do not add the navigate
- // 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: true,
- }
- }
- _ => GrepOutputConfig {
- render_context_header_as_hunk_header: true,
- add_navigate_marker_to_matches: false,
- pad_line_number: true,
- },
- }
-}
-
-enum GrepLineRegex {
- WithFileExtensionAndLineNumber,
- WithFileExtension,
- WithFileExtensionNoSpaces,
- WithoutSeparatorCharacters,
-}
-
-lazy_static! {
- static ref GREP_LINE_REGEX_ASSUMING_FILE_EXTENSION_AND_LINE_NUMBER: Regex =
- make_grep_line_regex(GrepLineRegex::WithFileExtensionAndLineNumber);
-}
-
-lazy_static! {
- static ref GREP_LINE_REGEX_ASSUMING_FILE_EXTENSION_NO_SPACES: Regex =
- make_grep_line_regex(GrepLineRegex::WithFileExtensionNoSpaces);
-}
-
-lazy_static! {
- static ref GREP_LINE_REGEX_ASSUMING_FILE_EXTENSION: Regex =
- make_grep_line_regex(GrepLineRegex::WithFileExtension);
-}
-
-lazy_static! {
- static ref GREP_LINE_REGEX_ASSUMING_NO_INTERNAL_SEPARATOR_CHARS: Regex =
- make_grep_line_regex(GrepLineRegex::WithoutSeparatorCharacters);
-}
-
-// See tests for example grep lines
-fn make_grep_line_regex(regex_variant: GrepLineRegex) -> Regex {
- // Grep tools such as `git grep` and `rg` emit lines like the following,
- // where "xxx" represents arbitrary code. Note that there are 3 possible
- // "separator characters": ':', '-', '='.
-
- // The format is ambiguous, but we attempt to parse it.
-
- // src/co-7-fig.rs:xxx
- // src/co-7-fig.rs:7:xxx
- // src/co-7-fig.rs-xxx
- // src/co-7-fig.rs-7-xxx
- // src/co-7-fig.rs=xxx
- // src/co-7-fig.rs=7=xxx
-
- // Makefile:xxx
- // Makefile:7:xxx
- // Makefile-xxx
- // Makefile-7-xxx
-
- // Make-7-file:xxx
- // Make-7-file:7:xxx
- // Make-7-file-xxx
- // Make-7-file-7-xxx
-
- let file_path = match regex_variant {
- GrepLineRegex::WithFileExtensionAndLineNumber | GrepLineRegex::WithFileExtension => {
- r"
- ( # 1. file name (colons not allowed)
- [^:|\ ] # try to be strict about what a file path can start with
- [^:]* # anything
- [^\ ]\.[^.\ :=-]{1,10} # extension
- )
- "
- }
- GrepLineRegex::WithFileExtensionNoSpaces => {
- r"
- ( # 1. file name (colons not allowed)
- [^:|\ ]+ # try to be strict about what a file path can start with
- [^\ ]\.[^.\ :=-]{1,6} # extension
- )
- "
- }
- GrepLineRegex::WithoutSeparatorCharacters => {
- r"
- ( # 1. file name (colons not allowed)
- [^:|\ =-] # try to be strict about what a file path can start with
- [^:=-]* # anything except separators
- [^:\ ] # a file name cannot end with whitespace
- )
- "
- }
- };
-
- let separator = match regex_variant {
- GrepLineRegex::WithFileExtensionAndLineNumber => {
- r#"
- (?:
- (
- : # 2. match marker
- ([0-9]+): # 3. line number followed by second match marker
- )
- |
- (
- - # 4. nomatch marker
- ([0-9]+)- # 5. line number followed by second nomatch marker
- )
- |
- (
- = # 6. context header marker
- ([0-9]+)= # 7. line number followed by second header marker
- )
- )
- "#
- }
- _ => {
- r#"
- (?:
- (
- : # 2. match marker
- (?:([0-9]+):)? # 3. optional: line number followed by second match marker
- )
- |
- (
- - # 4. nomatch marker
- (?:([0-9]+)-)? # 5. optional: line number followed by second nomatch marker
- )
- |
- (
- = # 6. context header marker
- (?:([0-9]+)=)? # 7. optional: line number followed by second header marker
- )
- )
- "#
- }
- };
-
- Regex::new(&format!(
- "(?x)
-^
-{file_path}
-{separator}
-(.*) # 8. code (i.e. line contents)
-$
-",
- ))
- .unwrap()
-}
-
-pub fn parse_grep_line(line: &str) -> Option<GrepLine> {
- if line.starts_with('{') {
- ripgrep_json::parse_line(line)
- } else {
- match &*process::calling_process() {
- process::CallingProcess::GitGrep(_) | process::CallingProcess::OtherGrep => [
- &*GREP_LINE_REGEX_ASSUMING_FILE_EXTENSION_AND_LINE_NUMBER,
- &*GREP_LINE_REGEX_ASSUMING_FILE_EXTENSION_NO_SPACES,
- &*GREP_LINE_REGEX_ASSUMING_FILE_EXTENSION,
- &*GREP_LINE_REGEX_ASSUMING_NO_INTERNAL_SEPARATOR_CHARS,
- ]
- .iter()
- .find_map(|regex| _parse_grep_line(regex, line)),
- _ => None,
- }
- }
-}
-
-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();
- let (line_type, line_number) = &[
- (2, LineType::Match),
- (4, LineType::Context),
- (6, LineType::ContextHeader),
- ]
- .iter()
- .find_map(|(i, line_type)| {
- if caps.get(*i).is_some() {
- let line_number: Option<usize> = caps.get(i + 1).and_then(|m| m.as_str().parse().ok());
- Some((*line_type, line_number))
- } else {
- None
- }
- })
- .unwrap(); // The regex matches so one of the three alternatives must have matched
- 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,
- code,
- submatches: None,
- })
-}
-
-#[cfg(test)]
-mod tests {
- use crate::handlers::grep::{parse_grep_line, GrepLine, GrepType, LineType};
- use crate::utils::process::tests::FakeParentArgs;
-
- #[test]
- fn test_parse_grep_match() {
- let fake_parent_grep_command = "git --doesnt-matter grep --nor-this nor_this -- nor_this";
- let _args = FakeParentArgs::for_scope(fake_parent_grep_command);
-
- 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,
- code: "xxx".into(),
- submatches: None,
- })
- );
-
- 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,
- code: "use crate::minusplus::MinusPlus;".into(),
- submatches: None,
- })
- );
-
- assert_eq!(
- parse_grep_line(
- "src/config.rs: pub line_numbers_style_minusplus: MinusPlus<Style>,"
- ),
- Some(GrepLine {
- grep_type: GrepType::Classic,
- path: "src/config.rs".into(),
- line_number: None,
- line_type: LineType::Match,
- code: " pub line_numbers_style_minusplus: MinusPlus<Style>,".into(),
- submatches: None,
- })
- );
-
- assert_eq!(
- parse_grep_line("src/con-fig.rs:use crate::minusplus::MinusPlus;"),
- Some(GrepLine {
- grep_type: GrepType::Classic,
- path: "src/con-fig.rs".into(),
- line_number: None,
- line_type: LineType::Match,
- code: "use crate::minusplus::MinusPlus;".into(),
- submatches: None,
- })
- );
-
- assert_eq!(
- parse_grep_line(
- "src/con-fig.rs: pub line_numbers_style_minusplus: MinusPlus<Style>,"
- ),
- Some(GrepLine {
- grep_type: GrepType::Classic,
- path: "src/con-fig.rs".into(),
- line_number: None,
- line_type: LineType::Match,
- code: " pub line_numbers_style_minusplus: MinusPlus<Style>,".into(),
- submatches: None,
- })
- );
-
- assert_eq!(
- parse_grep_line(
- "src/de lta.rs:pub fn delta<I>(lines: ByteLines<I>, writer: &mut dyn Write, config: &Config) -> std::io::Result<()>"
- ),
- Some(GrepLine {
- grep_type: GrepType::Classic,
- path: "src/de lta.rs".into(),
- line_number: None,
- line_type: LineType::Match,
- code: "pub fn delta<I>(lines: ByteLines<I>, writer: &mut dyn Write, config: &Config) -> std::io::Result<()>".into(),
- submatches: None,
- })
- );
-
- assert_eq!(
- parse_grep_line(
- "src/de lta.rs: pub fn new(writer: &'a mut dyn Write, config: &'a Config) -> Self {"
- ),
- Some(GrepLine {
- grep_type: GrepType::Classic,
- path: "src/de lta.rs".into(),
- line_number: None,
- line_type: LineType::Match,
- code: " pub fn new(writer: &'a mut dyn Write, config: &'a Config) -> Self {".into(),
- submatches: None,
- })
- );
- }
-
- #[test]
- fn test_parse_grep_n_match() {
- 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);
-
- assert_eq!(
- parse_grep_line("src/co-7-fig.rs:7:xxx"),
- Some(GrepLine {
- grep_type: GrepType::Classic,
- path: "src/co-7-fig.rs".into(),
- line_number: Some(7),
- line_type: LineType::Match,
- code: "xxx".into(),
- submatches: None,
- })
- );
-
- assert_eq!(
- parse_grep_line("src/config.rs:21:use crate::minusplus::MinusPlus;"),
- Some(GrepLine {
- grep_type: GrepType::Classic,
- path: "src/config.rs".into(),
- line_number: Some(21),
- line_type: LineType::Match,
- code: "use crate::minusplus::MinusPlus;".into(),
- submatches: None,
- })
- );
-
- assert_eq!(
- parse_grep_line(
- "src/config.rs:95: pub line_numbers_style_minusplus: MinusPlus<Style>,"
- ),
- Some(GrepLine {
- grep_type: GrepType::Classic,
- path: "src/config.rs".into(),
- line_number: Some(95),
- line_type: LineType::Match,
- code: " pub line_numbers_style_minusplus: MinusPlus<Style>,".into(),
- submatches: None,
- })
- );
-
- assert_eq!(
- parse_grep_line("Makefile:10:test: unit-test end-to-end-test"),
- Some(GrepLine {
- grep_type: GrepType::Classic,
- path: "Makefile".into(),
- line_number: Some(10),
- line_type: LineType::Match,
- code: "test: unit-test end-to-end-test".into(),
- submatches: None,
- })
- );
-
- assert_eq!(
- parse_grep_line(
- "Makefile:16: ./tests/test_raw_output_matches_git_on_full_repo_history"
- ),
- Some(GrepLine {
- grep_type: GrepType::Classic,
- path: "Makefile".into(),
- line_number: Some(16),
- line_type: LineType::Match,
- code: " ./tests/test_raw_output_matches_git_on_full_repo_history".into(),
- submatches: None,
- })
- );
- }
-
- #[test]
- #[ignore]
- fn test_parse_grep_n_match_file_name_with_dashes_and_no_extension() {
- // git grep -n
- // This fails: we can't parse it currently.
- let fake_parent_grep_command =
- "/usr/local/bin/git --doesnt-matter grep --nor-this nor_this -- nor_this";
- let _args = FakeParentArgs::once(fake_parent_grep_command);
-
- assert_eq!(
- parse_grep_line("etc/examples/119-within-line-edits:4:repo=$(mktemp -d)"),
- Some(GrepLine {
- grep_type: GrepType::Classic,
- path: "etc/examples/119-within-line-edits".into(),
- line_number: Some(4),
- line_type: LineType::Match,
- code: "repo=$(mktemp -d)".into(),
- submatches: None,
- })
- );
- }
-
- #[test]
- fn test_parse_grep_n_match_directory_name_with_dashes() {
- let fake_parent_grep_command =
- "/usr/local/bin/git --doesnt-matter grep --nor-this nor_this -- nor_this";
- let _args = FakeParentArgs::once(fake_parent_grep_command);
-
- assert_eq!(
- parse_grep_line("etc/META-INF/foo.properties:4:value=hi-there"),
- Some(GrepLine {
- grep_type: GrepType::Classic,
- path: "etc/META-INF/foo.properties".into(),
- line_number: Some(4),
- line_type: LineType::Match,
- code: "value=hi-there".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";
- let _args = FakeParentArgs::for_scope(fake_parent_grep_command);
-
- 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::Context,
- code: "xxx".into(),
- submatches: None,
- })
- );
-
- assert_eq!(
-