From ae26b5497cc5f7b958d7fa2683c0ebd002109c54 Mon Sep 17 00:00:00 2001 From: Dan Davison Date: Sat, 12 Aug 2023 22:08:54 -0400 Subject: Fix handling of tabs in grep output (#1507) Fixes #1501 --- src/handlers/grep.rs | 59 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/src/handlers/grep.rs b/src/handlers/grep.rs index 772baf11..8c3b6ce8 100644 --- a/src/handlers/grep.rs +++ b/src/handlers/grep.rs @@ -26,6 +26,20 @@ pub struct GrepLine<'b> { pub submatches: Option>, } +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 { @@ -159,17 +173,17 @@ impl<'a> StateMachine<'a> { } // Emit the actual grep hit line let code_style_sections = match (&grep_line.line_type, &grep_line.submatches) { - (LineType::Match, Some(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.code = tabs::expand(&grep_line.code, &self.config.tab_cfg).into(); + grep_line.expand_tabs(&self.config.tab_cfg); make_style_sections( &grep_line.code, - submatches, + &grep_line.submatches.unwrap(), self.config.grep_match_word_style, self.config.grep_match_line_style, ) @@ -186,7 +200,8 @@ impl<'a> StateMachine<'a> { &self.raw_line, self.config.grep_match_word_style, self.config.grep_match_line_style, - &grep_line, + &grep_line.path, + grep_line.line_number, ) .unwrap_or(StyleSectionSpecifier::Style( self.config.grep_match_line_style, @@ -297,17 +312,17 @@ impl<'a> StateMachine<'a> { 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)) => { + (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.code = tabs::expand(&grep_line.code, &self.config.tab_cfg).into(); + grep_line.expand_tabs(&self.config.tab_cfg); make_style_sections( &grep_line.code, - submatches, + &grep_line.submatches.unwrap(), self.config.grep_match_word_style, self.config.grep_match_line_style, ) @@ -324,7 +339,8 @@ impl<'a> StateMachine<'a> { &self.raw_line, self.config.grep_match_word_style, self.config.grep_match_line_style, - &grep_line, + &grep_line.path, + grep_line.line_number, ) .unwrap_or(StyleSectionSpecifier::Style( self.config.grep_match_line_style, @@ -369,13 +385,14 @@ fn get_code_style_sections<'b>( raw_line: &'b str, match_style: Style, non_match_style: Style, - grep: &GrepLine, + path: &str, + line_number: Option, ) -> Option> { if let Some(prefix_end) = ansi::ansi_preserving_index( raw_line, - match grep.line_number { - Some(n) => format!("{}:{}:", grep.path, n).len() - 1, - None => grep.path.len(), + 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)..]) @@ -1101,7 +1118,7 @@ mod tests { let grep = parse_grep_line(&stripped).unwrap(); assert_eq!( - get_code_style_sections(&working_example, hit, miss, &grep), + get_code_style_sections(&working_example, hit, miss, &grep.path, grep.line_number), Some(StyleSectionSpecifier::StyleSections(vec![ (miss, " - "), (hit, "kind: Service"), @@ -1114,7 +1131,13 @@ mod tests { let broken_grep = parse_grep_line(&broken_stripped).unwrap(); assert_eq!( - get_code_style_sections(&broken_example, hit, miss, &broken_grep), + get_code_style_sections( + &broken_example, + hit, + miss, + &broken_grep.path, + broken_grep.line_number + ), Some(StyleSectionSpecifier::StyleSections(vec![( hit, "kind: Service" @@ -1126,7 +1149,13 @@ mod tests { let plus_grep = parse_grep_line(&plus_stripped).unwrap(); assert_eq!( - get_code_style_sections(&plus_example, hit, miss, &plus_grep), + get_code_style_sections( + &plus_example, + hit, + miss, + &plus_grep.path, + plus_grep.line_number + ), Some(StyleSectionSpecifier::StyleSections(vec![ (miss, " + let (style, non_emph_style) = "), (hit, "match"), -- cgit v1.2.3