CodeVetter · Repo Unpack · Behavior traces
BurntSushi/ripgrep
Three flows dominate: startup/arg-parsing, a default recursive search, and the file-listing path. All of them funnel through `flags::parse()` → `HiArgs` and then diverge on `Mode`. The parallel search path is notable because parallelism lives entirely in the directory walker — `search_parallel` just hands a closure to `WalkParallel::run` and never manages threads itself. The indexed path currently degrades gracefully: `index::read` opens the index and then falls straight through to the ordinary exhaustive search.
- Startup order: `main` → `flags::parse()` → `parse_low()` which first calls `Logger::init()`, then `Parser::new().parse(env::args_os().skip(1), &mut low)`, then `set_log_levels(&low)` so `--trace` is live before config parsing.
- If a config file yields arguments, the parser discards the first `LowArgs`, builds a fresh `LowArgs::default()`, re-parses `config_args ++ argv`, and re-runs `set_log_levels` because logging flags may have changed.
- `Parser::new()` returns a `&'static Parser` memoized in a `OnceLock`, so the flag-name trie (`FlagMap` over short names, long names, aliases and negations) is built exactly once per process even though `lookup()` calls it repeatedly.
- `HiArgs::from_low_args` begins by asserting `low.special == None` ("special mode demands short-circuiting"), which encodes the invariant that help/version never reach high-level conversion.
- Default search trace: `run` → `search(args, mode)` → `args.walk_builder()?.build()` → `haystack_builder.build_from_result` → `args.sort(...)` → `args.search_worker(args.matcher()?, args.searcher()?, args.printer(mode, args.stdout()))` → loop `searcher.search(&haystack)`.
- Inside one file's search: `SearchWorker::search` sets binary detection (explicit vs implicit) on the `Searcher`, then `search_path` dispatches on the `PatternMatcher` enum to the generic `search_path(m, searcher, printer, path)`, which calls `p.sink_with_path(&matcher, path)` and `searcher.search_path`.
- `Searcher::search_path` → `search_file_maybe_path` first tries `self.config.mmap.open(file, path)` and searches the resulting slice; only if mmap is unavailable does it fall back to the incremental reader path.
- The searcher has three internal strategies implemented in `glue.rs`: `ReadByLine` (streaming reader), `SliceByLine` (whole-slice, e.g. mmap) and `MultiLine` (when `--multiline` is on).
- Parallel search trace: `search_parallel` creates a `BufferWriter`, an `AtomicBool` matched/searched pair and an optional `Mutex<Stats>`, then `args.walk_builder()?.build_parallel().run(|| Box::new(move |result| ...))` clones a `SearchWorker` per thread.
- Early exit under `-q/--quiet` is implemented twice: the sequential loop `break`s when `matched && args.quit_after_match()`, and the parallel closure returns `WalkState::Quit`, which aborts the whole traversal.
- Broken pipe is handled at three levels: inside the search loop (`break`), inside `bufwtr.print` (`WalkState::Quit`), and finally in `main`, which walks `err.chain()` for an `io::Error` of kind `BrokenPipe` and returns exit code 0.
- Preprocessor trace: `search_preprocessor` builds `Command::new(bin).arg(path).stdin(Stdio::from(File::open(path)?))`, wraps it in a `CommandReader`, searches the reader, then calls `rdr.close()` so a non-zero exit code surfaces stderr as a Rust error.
- Indexed search currently does no index-based pruning: `index::read` calls `args.index_read()?`, discards the handle into `let _index`, and then delegates to the ordinary `search`/`search_parallel` ("Do an exhaustive search for now").
- Index discovery trace: `IndexDiscovery::discover` checks `RIPGREP_INDEX_PATH` first, then walks `cwd.ancestors()` looking for a `.ripgrep` directory containing `index.db`, returning `None` when nothing is found.
- Man page generation trace: `generate()` groups `FLAGS` by `doc_category()` into a `BTreeMap`, renders each flag's roff via `generate_flag`, then substitutes `!!VERSION!!` and per-category `!!name!!` placeholders in `template.rg.1`.