Only in /var/lib/copr-rpmbuild/results/ripgrep-edit/upstream-unpacked/Source0/ripgrep-edit-0.3.8: .distro diff -U2 -r /var/lib/copr-rpmbuild/results/ripgrep-edit/upstream-unpacked/Source0/ripgrep-edit-0.3.8/Cargo.toml /var/lib/copr-rpmbuild/results/ripgrep-edit/srpm-unpacked/ripgrep-edit-0.3.8.tar.gz-extract/ripgrep-edit-0.3.8/Cargo.toml --- /var/lib/copr-rpmbuild/results/ripgrep-edit/upstream-unpacked/Source0/ripgrep-edit-0.3.8/Cargo.toml 2026-03-06 17:30:00.000000000 +0000 +++ /var/lib/copr-rpmbuild/results/ripgrep-edit/srpm-unpacked/ripgrep-edit-0.3.8.tar.gz-extract/ripgrep-edit-0.3.8/Cargo.toml 2026-03-16 00:02:38.000000000 +0000 @@ -16,4 +16,6 @@ either = "1.15" ctrlc = { version = "3.5", features = ["termination"] } +rayon = "1" +num_cpus = "1" [[bin]] diff -U2 -r /var/lib/copr-rpmbuild/results/ripgrep-edit/upstream-unpacked/Source0/ripgrep-edit-0.3.8/README.md /var/lib/copr-rpmbuild/results/ripgrep-edit/srpm-unpacked/ripgrep-edit-0.3.8.tar.gz-extract/ripgrep-edit-0.3.8/README.md --- /var/lib/copr-rpmbuild/results/ripgrep-edit/upstream-unpacked/Source0/ripgrep-edit-0.3.8/README.md 2026-03-06 17:30:00.000000000 +0000 +++ /var/lib/copr-rpmbuild/results/ripgrep-edit/srpm-unpacked/ripgrep-edit-0.3.8.tar.gz-extract/ripgrep-edit-0.3.8/README.md 2026-03-16 00:02:38.000000000 +0000 @@ -55,5 +55,7 @@ - `--sortr `: Sort results in reverse order by path, modified, accessed, or created time - `--follow`: Follow symlinks +- `-j, --threads `: Number of threads to use - `--dump-on-error`: Dump processed tempfile to stderr on error +- `--output-size-limit `: Limit total ripgrep output size (e.g., 1024, 1k, 1m, 1g). If the limit is exceeded, the tool exits with code 3. - `--gbnf`: Generate dynamic GBNF grammar file - `--gbnf-control-lines`: Max number of GBNF control lines added as extra context @@ -67,4 +69,5 @@ rg-edit -E vim -C10 -U -e '(?s)^<<<<<<<+ .*?^\|\|\|\|\|\|\|+ .*?^>>>>>>>+ ' # resolve git conflicts rg-edit -E emacsclient -C10 -U -e '(?s)^<<<<<<<+ .*?^\|\|\|\|\|\|\|+ .*?^>>>>>>>+ ' +rg-edit -e '.*' -E vim src/ --output-size-limit 1m # fail if output larger than 1 megabyte (exits with code 3) ``` diff -U2 -r /var/lib/copr-rpmbuild/results/ripgrep-edit/upstream-unpacked/Source0/ripgrep-edit-0.3.8/emacs/rg-edit.el /var/lib/copr-rpmbuild/results/ripgrep-edit/srpm-unpacked/ripgrep-edit-0.3.8.tar.gz-extract/ripgrep-edit-0.3.8/emacs/rg-edit.el --- /var/lib/copr-rpmbuild/results/ripgrep-edit/upstream-unpacked/Source0/ripgrep-edit-0.3.8/emacs/rg-edit.el 2026-03-06 17:30:00.000000000 +0000 +++ /var/lib/copr-rpmbuild/results/ripgrep-edit/srpm-unpacked/ripgrep-edit-0.3.8.tar.gz-extract/ripgrep-edit-0.3.8/emacs/rg-edit.el 2026-03-16 00:02:38.000000000 +0000 @@ -100,4 +100,5 @@ "-E" "emacsclient" "--dump-on-error" + "--output-size-limit=100m" (if (and (fboundp 'gptel--model-capable-p) (gptel--model-capable-p 'gbnf)) diff -U2 -r /var/lib/copr-rpmbuild/results/ripgrep-edit/upstream-unpacked/Source0/ripgrep-edit-0.3.8/src/args.rs /var/lib/copr-rpmbuild/results/ripgrep-edit/srpm-unpacked/ripgrep-edit-0.3.8.tar.gz-extract/ripgrep-edit-0.3.8/src/args.rs --- /var/lib/copr-rpmbuild/results/ripgrep-edit/upstream-unpacked/Source0/ripgrep-edit-0.3.8/src/args.rs 2026-03-06 17:30:00.000000000 +0000 +++ /var/lib/copr-rpmbuild/results/ripgrep-edit/srpm-unpacked/ripgrep-edit-0.3.8.tar.gz-extract/ripgrep-edit-0.3.8/src/args.rs 2026-03-16 00:02:38.000000000 +0000 @@ -75,4 +75,8 @@ follow: bool, + /// Number of threads to use + #[clap(short = 'j', long = "threads", default_value_t = 0, value_parser = clap::value_parser!(u32).range(0..))] + threads: u32, + /// Paths to search in #[clap(num_args(0..))] @@ -83,4 +87,9 @@ dump_on_error: bool, + /// Limit the total size of ripgrep output in bytes (e.g., 1024, + /// 1k, 1m, 1g), if the limit is exceeded exit with code 3 + #[clap(long)] + output_size_limit: Option, + /// Generate GBNF grammar file with context lines #[clap(long)] diff -U2 -r /var/lib/copr-rpmbuild/results/ripgrep-edit/upstream-unpacked/Source0/ripgrep-edit-0.3.8/src/gbnf.rs /var/lib/copr-rpmbuild/results/ripgrep-edit/srpm-unpacked/ripgrep-edit-0.3.8.tar.gz-extract/ripgrep-edit-0.3.8/src/gbnf.rs --- /var/lib/copr-rpmbuild/results/ripgrep-edit/upstream-unpacked/Source0/ripgrep-edit-0.3.8/src/gbnf.rs 2026-03-06 17:30:00.000000000 +0000 +++ /var/lib/copr-rpmbuild/results/ripgrep-edit/srpm-unpacked/ripgrep-edit-0.3.8.tar.gz-extract/ripgrep-edit-0.3.8/src/gbnf.rs 2026-03-16 00:02:38.000000000 +0000 @@ -3,4 +3,5 @@ use either::Either; +use rayon::prelude::*; use std::fs::File; use std::io::Write; @@ -8,5 +9,5 @@ use crate::Args; -use crate::FileRanges; +use crate::{FileRange, FileRanges}; fn __quote_char(c: char, in_range: bool) -> String { @@ -248,4 +249,105 @@ } +fn find_control_lines_parallel( + file_ranges: &mut FileRanges, + args: &Args, +) -> Result<(), anyhow::Error> { + let num_cpus = if args.threads == 0 { + num_cpus::get() + } else { + args.threads.try_into().unwrap() + }; + + let before_context = args.context.max(args.before_context) as usize; + let after_context = args.context.max(args.after_context) as usize; + + // Define a closure to process a single range + let process_range = |range: &FileRange| -> (Option, Option, usize, usize) { + let lines = &range.lines; + let before_context = range.before_context.saturating_sub(before_context); + let after_context = range.after_context.saturating_sub(after_context); + + let control_lines_before = &lines[..before_context]; + let control_lines_after = &lines[lines.len() - after_context..]; + + let control_line_before = find_control_line(lines, control_lines_before, false); + let control_line_after = find_control_line(lines, control_lines_after, true); + + let control_line_before_index = if let Some(control_line) = control_line_before { + control_lines_before + .iter() + .rposition(|line| line == control_line) + .unwrap() + } else { + control_lines_before.len() + }; + + let control_line_after_index = if let Some(control_line) = control_line_after { + control_lines_after.len() + - control_lines_after + .iter() + .position(|line| line == control_line) + .unwrap() + - 1 + } else { + control_lines_after.len() + }; + + ( + control_line_before.cloned(), + control_line_after.cloned(), + control_line_before_index, + control_line_after_index, + ) + }; + + // Process all ranges sequentially + let all_ranges: Vec<_> = file_ranges.hash.values().flatten().collect(); + + // Process ranges in parallel if num_threads > 1, otherwise process sequentially + let results: Vec<_> = if num_cpus > 1 { + // Create a thread pool with limited parallelism + let pool = rayon::ThreadPoolBuilder::new() + .num_threads(num_cpus) + .build() + .unwrap(); + + // Process all ranges in parallel using the configured thread pool + pool.install(|| { + all_ranges + .par_iter() + .cloned() + .map(process_range) + .collect::>() + }) + } else { + all_ranges + .into_iter() + .map(process_range) + .collect::>() + }; + + // Update ranges using stable iteration + let mut result_index = 0; + for ranges in file_ranges.hash.values_mut() { + for range in ranges.iter_mut() { + let result: &(Option, Option, usize, usize) = &results[result_index]; + let ( + control_line_before, + control_line_after, + control_line_before_index, + control_line_after_index, + ) = result; + range.control_line_before = control_line_before.clone(); + range.control_line_after = control_line_after.clone(); + range.control_line_before_index = *control_line_before_index; + range.control_line_after_index = *control_line_after_index; + result_index += 1; + } + } + + Ok(()) +} + pub fn generate_gbnf_file( file: &mut File, @@ -253,7 +355,7 @@ args: &Args, ) -> Result<(), anyhow::Error> { + find_control_lines_parallel(file_ranges, args)?; + let mut file_rules = Vec::new(); - let before_context = args.context.max(args.before_context) as usize; - let after_context = args.context.max(args.after_context) as usize; for (filename, ranges) in &mut file_ranges.hash { @@ -267,36 +369,15 @@ for range in ranges { // Generate rules for each snippet - let lines = &range.lines.clone(); - let before_context = range.before_context.saturating_sub(before_context); - let after_context = range.after_context.saturating_sub(after_context); - - let control_lines_before = &lines[..before_context]; - let control_lines_after = &lines[lines.len() - after_context..]; - let control_line_before = find_control_line(lines, control_lines_before, false); - let control_line_after = find_control_line(lines, control_lines_after, true); + let control_line_before = range.control_line_before.as_ref(); + let control_line_after = range.control_line_after.as_ref(); - let index = if let Some(control_line) = control_line_before { + if let Some(control_line) = control_line_before { push_control_line(&mut file_rule, control_line); - - control_lines_before - .iter() - .rposition(|line| line == control_line) - .unwrap() - } else { - control_lines_before.len() - }; + } + let index = range.control_line_before_index; range.start += index; range.lines.drain(..index); - let index = if let Some(control_line) = control_line_after { - control_lines_after.len() - - control_lines_after - .iter() - .position(|line| line == &control_line.to_string()) - .unwrap() - - 1 - } else { - control_lines_after.len() - }; + let index = range.control_line_after_index; range.end -= index; range.lines.drain(range.lines.len() - index..); diff -U2 -r /var/lib/copr-rpmbuild/results/ripgrep-edit/upstream-unpacked/Source0/ripgrep-edit-0.3.8/src/main.rs /var/lib/copr-rpmbuild/results/ripgrep-edit/srpm-unpacked/ripgrep-edit-0.3.8.tar.gz-extract/ripgrep-edit-0.3.8/src/main.rs --- /var/lib/copr-rpmbuild/results/ripgrep-edit/upstream-unpacked/Source0/ripgrep-edit-0.3.8/src/main.rs 2026-03-06 17:30:00.000000000 +0000 +++ /var/lib/copr-rpmbuild/results/ripgrep-edit/srpm-unpacked/ripgrep-edit-0.3.8.tar.gz-extract/ripgrep-edit-0.3.8/src/main.rs 2026-03-16 00:02:38.000000000 +0000 @@ -26,5 +26,5 @@ } -#[derive(Debug)] +#[derive(Debug, Clone)] struct FileRange { start: usize, @@ -33,4 +33,8 @@ before_context: usize, after_context: usize, + control_line_before: Option, + control_line_after: Option, + control_line_before_index: usize, + control_line_after_index: usize, } @@ -49,4 +53,8 @@ before_context, after_context, + control_line_before: None, + control_line_after: None, + control_line_before_index: 0, + control_line_after_index: 0, } } @@ -316,4 +324,91 @@ } +fn parse_size_limit(size_str: &str) -> Result { + let size_str = size_str.trim(); + let mut multiplier = 1u64; + let mut num_str = size_str; + + if let Some(suffix) = size_str.chars().next_back() { + match suffix { + 'k' | 'K' => { + multiplier = 1024; + num_str = &size_str[..size_str.len() - 1]; + } + 'm' | 'M' => { + multiplier = 1024 * 1024; + num_str = &size_str[..size_str.len() - 1]; + } + 'g' | 'G' => { + multiplier = 1024 * 1024 * 1024; + num_str = &size_str[..size_str.len() - 1]; + } + _ => {} // No suffix, treat as bytes + } + } + + num_str + .parse::() + .map(|n| n * multiplier) + .map_err(|_| anyhow::anyhow!("Invalid size limit format: {}", size_str)) +} + +fn run_rg(rg_cmd: &mut Command, size_limit: Option) -> Result { + let mut child = rg_cmd + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .map_err(|e| { + if e.kind() == std::io::ErrorKind::NotFound { + anyhow::anyhow!("Error: rg is not installed.") + } else { + anyhow::anyhow!("Failed to execute rg: {e}") + } + })?; + let mut stdout = child.stdout.take().unwrap(); + let mut stderr = child.stderr.take().unwrap(); + + let mut buf = [0u8; 65536]; + let mut total_bytes = 0u64; + let mut rg_output = String::new(); + + loop { + match std::io::Read::read(&mut stdout, &mut buf) { + Ok(0) => break, + Ok(n) => { + if let Some(limit) = size_limit { + total_bytes += n as u64; + if total_bytes > limit { + child.kill()?; + let _ = child.wait(); + eprintln!( + "Output size limit ({:?} bytes) exceeded. The ripgrep output was too large.", + limit + ); + std::process::exit(3); + } + } + rg_output.push_str(&String::from_utf8_lossy(&buf[..n])); + } + Err(e) => { + if e.kind() == std::io::ErrorKind::Interrupted { + continue; + } + anyhow::bail!("Error reading rg output: {}", e); + } + }; + } + + let status = child.wait()?; + if !status.success() && status.code() != Some(1) { + let mut stderr_bytes = Vec::new(); + let _ = std::io::Read::read_to_end(&mut stderr, &mut stderr_bytes); + let stderr_str = String::from_utf8_lossy(&stderr_bytes); + assert!(status.code().unwrap() == 2); + anyhow::bail!("rg exited with {}: {}", status, stderr_str); + } + + Ok(rg_output) +} + fn main() -> Result<()> { let args = Args::parse(); @@ -387,20 +482,20 @@ rg_cmd.arg("--follow"); } + if args.threads > 0 { + rg_cmd.arg("--threads"); + rg_cmd.arg(args.threads.to_string()); + } for path in &args.paths { rg_cmd.arg(path); } - let output = rg_cmd.output(); - match output { - Ok(_) => {} - Err(e) => { - if e.kind() == std::io::ErrorKind::NotFound { - anyhow::bail!("Error: rg is not installed."); - } else { - anyhow::bail!("Failed to execute rg: {e}"); - } - } - } - let rg_output = String::from_utf8_lossy(&output.unwrap().stdout).into_owned(); + let size_limit = args + .output_size_limit + .as_ref() + .map(|s| parse_size_limit(s)) + .transpose()?; + + let rg_output = run_rg(&mut rg_cmd, size_limit)?; + if rg_output.trim().is_empty() { eprintln!("No results found.");