diff --git a/crates/bashkit/src/builtins/rg.rs b/crates/bashkit/src/builtins/rg.rs index 765268cb..50aa7ce3 100644 --- a/crates/bashkit/src/builtins/rg.rs +++ b/crates/bashkit/src/builtins/rg.rs @@ -5,7 +5,7 @@ //! Usage: //! rg PATTERN [PATH...] //! rg -i PATTERN file # case insensitive -//! rg -n PATTERN file # show line numbers (default) +//! rg -n PATTERN file # show line numbers (off by default in non-tty) //! rg -c PATTERN file # count matches //! rg -l PATTERN file # files with matches //! rg -v PATTERN file # invert match @@ -46,7 +46,7 @@ impl RgOptions { pattern: String::new(), paths: Vec::new(), ignore_case: false, - line_numbers: true, // rg shows line numbers by default + line_numbers: false, // non-tty: suppress line numbers (real rg behavior) count_only: false, files_with_matches: false, invert_match: false, @@ -68,6 +68,7 @@ impl RgOptions { match chars[j] { 'i' => opts.ignore_case = true, 'n' => opts.line_numbers = true, + 'N' => opts.line_numbers = false, 'c' => opts.count_only = true, 'l' => opts.files_with_matches = true, 'v' => opts.invert_match = true, @@ -89,6 +90,8 @@ impl RgOptions { // no-op } else if opt == "no-line-number" { opts.line_numbers = false; + } else if opt == "line-number" { + opts.line_numbers = true; } // ignore other long options } else { @@ -472,7 +475,8 @@ mod tests { } #[tokio::test] - async fn test_rg_line_numbers_default() { + async fn test_rg_no_line_numbers_default() { + // Non-tty: line numbers suppressed by default (like real rg) let result = run_rg( &["world", "/test.txt"], None, @@ -480,7 +484,68 @@ mod tests { ) .await; assert_eq!(result.exit_code, 0); - // Line numbers on by default, "world" is on line 2 - assert!(result.stdout.contains("2:")); + assert_eq!(result.stdout.trim(), "world"); + assert!(!result.stdout.contains("2:")); + } + + #[tokio::test] + async fn test_rg_line_numbers_explicit() { + // -n flag enables line numbers + let result = run_rg( + &["-n", "world", "/test.txt"], + None, + &[("/test.txt", b"hello\nworld\n")], + ) + .await; + assert_eq!(result.exit_code, 0); + assert!(result.stdout.contains("2:world")); + } + + #[tokio::test] + async fn test_rg_no_line_number_flag_short() { + // -N flag explicitly disables line numbers + let result = run_rg( + &["-N", "world", "/test.txt"], + None, + &[("/test.txt", b"hello\nworld\n")], + ) + .await; + assert_eq!(result.exit_code, 0); + assert_eq!(result.stdout.trim(), "world"); + } + + #[tokio::test] + async fn test_rg_no_line_number_flag_long() { + // --no-line-number flag explicitly disables line numbers + let result = run_rg( + &["--no-line-number", "world", "/test.txt"], + None, + &[("/test.txt", b"hello\nworld\n")], + ) + .await; + assert_eq!(result.exit_code, 0); + assert_eq!(result.stdout.trim(), "world"); + } + + #[tokio::test] + async fn test_rg_line_number_long_flag() { + // --line-number flag enables line numbers + let result = run_rg( + &["--line-number", "world", "/test.txt"], + None, + &[("/test.txt", b"hello\nworld\n")], + ) + .await; + assert_eq!(result.exit_code, 0); + assert!(result.stdout.contains("2:world")); + } + + #[tokio::test] + async fn test_rg_stdin_no_line_numbers() { + // Stdin piped: no line numbers by default + let result = run_rg(&["hello"], Some("hello world\n"), &[]).await; + assert_eq!(result.exit_code, 0); + assert_eq!(result.stdout.trim(), "hello world"); + assert!(!result.stdout.contains("1:")); } } diff --git a/crates/bashkit/tests/spec_cases/grep/rg.test.sh b/crates/bashkit/tests/spec_cases/grep/rg.test.sh new file mode 100644 index 00000000..f9172947 --- /dev/null +++ b/crates/bashkit/tests/spec_cases/grep/rg.test.sh @@ -0,0 +1,94 @@ +### rg_basic_match +# Basic rg pattern match +printf 'hello world\ngoodbye\nhello again\n' | rg hello +### expect +hello world +hello again +### end + +### rg_no_line_numbers_default +# rg suppresses line numbers when piped (non-tty) +printf 'foo\nbar\nbaz\n' | rg bar +### expect +bar +### end + +### rg_line_numbers_with_n +# -n flag enables line numbers +printf 'foo\nbar\nbaz\n' | rg -n bar +### expect +2:bar +### end + +### rg_no_line_number_N_flag +# -N flag explicitly suppresses line numbers +printf 'foo\nbar\n' | rg -N bar +### expect +bar +### end + +### rg_no_line_number_long +# --no-line-number long flag +printf 'foo\nbar\n' | rg --no-line-number bar +### expect +bar +### end + +### rg_line_number_long +# --line-number long flag enables line numbers +printf 'foo\nbar\nbaz\n' | rg --line-number bar +### expect +2:bar +### end + +### rg_no_match +# No match returns exit code 1 +printf 'foo\nbar\n' | rg xyz +### exit_code: 1 +### expect +### end + +### rg_case_insensitive +# Case insensitive search +printf 'Hello\nWORLD\nhello\n' | rg -i hello +### expect +Hello +hello +### end + +### rg_count +# Count matches +printf 'foo\nbar\nfoo again\n' | rg -c foo +### expect +2 +### end + +### rg_invert_match +# Invert match +printf 'foo\nbar\nbaz\n' | rg -v foo +### expect +bar +baz +### end + +### rg_fixed_strings +# Fixed string (no regex) +printf 'a.b\naxb\n' | rg -F 'a.b' +### expect +a.b +### end + +### rg_word_boundary +# Word boundary match +printf 'cat\ncatch\nmy cat\n' | rg -w cat +### expect +cat +my cat +### end + +### rg_max_count +# Stop after N matches +printf 'foo\nfoo\nfoo\n' | rg -m 1 foo +### expect +foo +### end