ripgrepSearch method
Run rg (ripgrep) with pattern and additional args, parsing output
into SearchMatch objects.
Falls back to the built-in search method if rg is not available.
Implementation
Future<SearchResult> ripgrepSearch(
String pattern, {
List<String> args = const [],
String? directory,
}) async {
final sw = Stopwatch()..start();
final dir = directory ?? projectRoot;
try {
final result = await Process.run('rg', [
'--json',
'--line-number',
...args,
pattern,
dir,
]);
if (result.exitCode != 0 && result.exitCode != 1) {
// Exit code 1 = no matches; other codes are errors.
throw ProcessException(
'rg',
args,
result.stderr.toString(),
result.exitCode,
);
}
final matches = <SearchMatch>[];
var filesSearched = 0;
final seenFiles = <String>{};
for (final line in LineSplitter.split(result.stdout.toString())) {
if (line.isEmpty) continue;
try {
final json = jsonDecode(line) as Map<String, dynamic>;
final type = json['type'] as String?;
if (type == 'match') {
final data = json['data'] as Map<String, dynamic>;
final path =
(data['path'] as Map<String, dynamic>)['text'] as String;
final lineNum = data['line_number'] as int;
final lineText =
(data['lines'] as Map<String, dynamic>)['text'] as String;
final submatches = data['submatches'] as List<dynamic>;
seenFiles.add(path);
for (final sub in submatches) {
final s = sub as Map<String, dynamic>;
matches.add(
SearchMatch(
filePath: path,
lineNumber: lineNum,
column: s['start'] as int,
matchLength: (s['end'] as int) - (s['start'] as int),
lineContent: lineText.trimRight(),
),
);
}
} else if (type == 'summary') {
final data = json['data'] as Map<String, dynamic>;
final stats = data['stats'] as Map<String, dynamic>?;
if (stats != null) {
filesSearched = stats['searches'] as int? ?? seenFiles.length;
}
}
} catch (_) {
// Skip malformed JSON lines.
}
}
sw.stop();
return SearchResult(
matches: matches,
totalMatches: matches.length,
filesSearched: filesSearched > 0 ? filesSearched : seenFiles.length,
duration: sw.elapsed,
);
} on ProcessException {
// rg not found — fall back to built-in search.
sw.stop();
return search(
pattern,
SearchScope.project,
const SearchOptions(),
targetPath: dir,
);
}
}