ripGrepStream function

Future<void> ripGrepStream(
  1. List<String> args,
  2. String target, {
  3. required void onLines(
    1. List<String> lines
    ),
})

Stream lines from ripgrep as they arrive, calling onLines per stdout chunk.

Unlike ripGrep which buffers the entire stdout, this flushes complete lines as soon as each chunk arrives. Partial trailing lines are carried across chunk boundaries.

Callers that want to stop early should cancel via the returned subscription or use a timeout. No EAGAIN retry, no internal timeout, stderr is ignored; interactive callers own recovery.

Implementation

Future<void> ripGrepStream(
  List<String> args,
  String target, {
  required void Function(List<String> lines) onLines,
}) async {
  await _codesignRipgrepIfNecessary();
  final (:rgPath, :rgArgs, :argv0) = ripgrepCommand();

  final process = await Process.start(rgPath, [
    ...rgArgs,
    ...args,
    target,
  ], environment: argv0 != null ? {'ARGV0': argv0} : null);

  String remainder = '';
  await process.stdout.transform(const SystemEncoding().decoder).forEach((
    chunk,
  ) {
    final data = remainder + chunk;
    final lines = data.split('\n');
    remainder = lines.removeLast();
    if (lines.isNotEmpty) {
      onLines(lines.map(_stripCR).toList());
    }
  });

  // Flush remaining.
  if (remainder.isNotEmpty) {
    onLines([_stripCR(remainder)]);
  }

  final exitCode = await process.exitCode;
  if (exitCode != 0 && exitCode != 1) {
    throw Exception('ripgrep exited with code $exitCode');
  }
}