From 253b93be3378fa7750e0c4a08fc8736f984e5b95 Mon Sep 17 00:00:00 2001 From: Mohsen Zainalpour Date: Mon, 22 Jun 2026 19:24:29 -0400 Subject: [PATCH] grep: detect format flags inside clustered short options has_format_flag only matched exact tokens like "-l", but extract_pattern_path merges boolean short flags into one cluster (e.g. `-rln` becomes `-ln` after stripping `r`). A bundled -l/-c/-o/-L/-Z never matched, so grep_cmd tried to NUL-parse rg's output as if it were normal match lines, even though -l/-c/etc. change rg's output shape entirely. The result was a false "0 matches" instead of the real answer. Now has_format_flag also scans the individual letters of a short-flag cluster, so a bundled format flag correctly falls through to the existing passthrough path. --- src/cmds/system/grep_cmd.rs | 38 ++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/cmds/system/grep_cmd.rs b/src/cmds/system/grep_cmd.rs index b3954526a..e392c618c 100644 --- a/src/cmds/system/grep_cmd.rs +++ b/src/cmds/system/grep_cmd.rs @@ -500,8 +500,9 @@ fn parse_match_line(line: &str) -> Option<(String, usize, &str)> { fn has_format_flag>(extra_args: &[T]) -> bool { extra_args.iter().any(|arg| { - matches!( - arg.as_ref(), + let s = arg.as_ref(); + if matches!( + s, "-c" | "--count" | "--count-matches" | "-l" @@ -515,7 +516,20 @@ fn has_format_flag>(extra_args: &[T]) -> bool { | "--json" | "--passthru" | "--files" - ) + ) { + return true; + } + // extract_pattern_path merges boolean short flags into one cluster + // token (e.g. `-rln` becomes `-ln` after stripping `r`), so a format + // flag like `-l` can arrive bundled with others rather than as its + // own arg. Clusters only ever contain boolean letters (see + // parse_cluster), so scanning characters here is safe. + if let Some(rest) = s.strip_prefix('-') { + if !rest.is_empty() && !rest.starts_with('-') { + return rest.chars().any(|c| matches!(c, 'c' | 'l' | 'L' | 'o' | 'Z')); + } + } + false }) } @@ -1119,6 +1133,24 @@ mod tests { assert!(!has_format_flag(&["-i", "-w", "-A", "3"])); } + #[test] + fn test_format_flag_detects_clustered_l() { + // `-rln` becomes `-ln` after extract_pattern_path strips the `r`. + // -l (files-with-matches) is bundled with -n here, not standalone. + assert!(has_format_flag(&["-ln"])); + } + + #[test] + fn test_format_flag_detects_clustered_c() { + assert!(has_format_flag(&["-ic"])); + } + + #[test] + fn test_format_flag_ignores_double_dash() { + // Long flags must not be mistaken for a short cluster. + assert!(!has_format_flag(&["--ignore-case"])); + } + // Verify line numbers are always enabled in rg invocation (grep_cmd.rs:24). // The -n/--line-numbers clap flag in main.rs is a no-op accepted for compat. #[test]