Documentation

Search

greppy's search commands are sorted by one question: what do you know about the code you are looking for?

You know Command
what it does, not its name greppy search "WHAT IT DOES"
its name or part of it greppy search-symbol NAME
literal text, a config key, a message greppy search-pattern REGEX
a mix, and you want one ranked list greppy plus QUERY

All four use grep's exit codes: 0 for a hit, 1 for none. The examples run on serde 1.0.229.

search: by meaning

greppy search "convert a field name to snake case"
greppy search collect errors and report them at the end --kind method
greppy search "collect errors and report them at the end" --path serde_derive/ --code

search embeds your description with EmbeddingGemma and returns the closest definitions as a ranked list. Each line gives the location, the name and a short purpose hint. Everything after the verb is joined into one query, so quotes are optional. The ranked list has eight hits, also with --all.

$ greppy search "convert a field name to snake case"
serde_derive/src/internals/case.rs:82  RenameRule::apply_to_field — converts a string field into a case variant
serde_derive/src/internals/case.rs:174  rename_fields — tests that case field renames apply to their original values  test
serde_derive/src/internals/case.rs:57  RenameRule::apply_to_variant — converts a variant string to its canonical form
serde_derive/src/internals/case.rs:140  rename_variants — tests that apply_to_variant returns the expected name, type, and case for each variant  test
serde_derive/src/internals/case.rs:9  RenameRule
serde_derive/src/internals/attr.rs:133  unraw — wraps an Ident into a new Ident with a trimmed string
serde_derive/src/internals/case.rs:45  RenameRule::from_str — looks up a named rule in RENAME_RULES
test_suite/tests/test_identifier.rs:160  newtype_fallthrough — tests that a newtype enum falls through to its token  test

The query names neither apply_to_field nor RenameRule. The first hit is still the method that applies a rename rule to a field name. --kind narrows the kind of definition:

$ greppy search collect errors and report them at the end --kind method
serde_derive/src/internals/ctxt.rs:62  Ctxt::drop — panics if errors are present and the thread is not panicking
serde_derive/src/internals/ctxt.rs:46  Ctxt::check — combines any pending errors into a single error
…
serde_derive/src/internals/ctxt.rs:41  Ctxt::syn_error — records a syn::Error into the errors collection
…

--json adds the signature and the score to each hit, for example "signature": "pub fn apply_to_field(self, field: &str) -> String" and "score": 0.5393914580345154.

Treat the hits as answer candidates. Pick the best one and answer from the symbol, location and hint you already have, instead of rephrasing the question into another semantic search. When search reports no match, a search-pattern for a concrete term from the question is the next step.

search also finds code the graph does not reach, such as a reflection target or a generated handler, as long as you can describe what it does.

While the embeddings are building

greppy builds the embeddings in the background after the graph. The first semantic query in a new repository waits until the one-time embeddings are published and shows its progress on stderr. Then it answers with exit code 0, without partial hits.

$ greppy search "compress the ledger records"
semantic-search: semantic index building — 0/600 spans, ETA ~1m 15s (backend metal)
…
mod_05.py:300  compress_ledger_5_27 — compresses a list of items into a flat list of values
…

The omitted stderr lines report the state of each phase, such as embedding: 256/600 spans; phase ETA about 20s, plus the PID, the worker count and the command greppy --root DIR index status --json for details.

Scripts wait until greppy index status --json exits 0 and reports both "healthy": true and "embedding_complete": true. While a build is running, the command exits 75.

search-symbol: by name

greppy search-symbol rename
greppy search-symbol Rename --kind enum

search-symbol returns the definitions whose name contains NAME, the first 20 by default. Each line names the kind of definition:

$ greppy search-symbol rename
test_suite/tests/ui/rename:1  serde.test_suite.tests.ui.rename  folder  test
serde_derive/src/internals/attr.rs:1386  get_renames  function
serde_derive/src/internals/case.rs:174  rename_fields  function  test
serde_derive/src/internals/attr.rs:925  Variant::rename_by_rules  method
…
$ greppy search-symbol Rename --kind enum
serde_derive/src/internals/case.rs:9  RenameRule  enum
test_suite/tests/test_annotations.rs:614  RenameEnum  enum  test
test_suite/tests/test_annotations.rs:630  RenameEnumSerializeDeserialize  enum  test

A miss exits 1 and still offers the best tier of near matches, under a heading such as similar names: or closest by meaning:. The cascade tries case and underscore variants, edit distance, word overlap and finally meaning:

$ greppy search-symbol apply_to_snake_case
status: no_matches
scope: indexed definitions in the repository
message: no definition named `apply_to_snake_case`
next: search source text: greppy search-pattern apply_to_snake_case --fixed
next: refresh definitions after source changes: greppy index .

similar names:
serde_derive/src/internals/case.rs:23  RenameRule::SnakeCase  enum variant
serde_derive/src/internals/case.rs:82  RenameRule::apply_to_field  method
serde_derive/src/internals/case.rs:57  RenameRule::apply_to_variant  method
serde_derive/src/internals/case.rs:26  RenameRule::ScreamingSnakeCase  enum variant

You don't need search-symbol before a navigation command: who-calls, impact, callees, brief and path take the name directly.

search-pattern: by text

greppy search-pattern 'TODO|FIXME'
greppy search-pattern 'deny_unknown_fields' --fixed
greppy search-pattern 'deny_unknown_fields' --fixed --path serde_derive/ --all

search-pattern finds every place the regular expression matches, including comments, strings and configuration, and names the definition each match sits in. --fixed takes the pattern as literal text.

$ greppy search-pattern 'TODO|FIXME'
serde_core/src/private/mod.rs:5
serde_derive/src/de/enum_externally.rs:63  serde_derive/src/de/enum_externally.rs::Function::deserialize
serde_derive/src/de/enum_untagged.rs:32  serde_derive/src/de/enum_untagged.rs::Function::deserialize
…
serde_derive/src/de/struct_.rs:296  serde_derive/src/de/struct_.rs::Function::deserialize_map
serde_derive/src/de/struct_.rs:592  serde_derive/src/de/struct_.rs::Function::deserialize_map_in_place

A pattern with many matches prints the count and a sample of five lines. --all prints every match, --code prints each matched line verbatim. --kind assigns a text match to the definition it sits in.

$ greppy search-pattern 'deny_unknown_fields' --fixed
— 41 matches in 11 files; showing 5

serde_derive/src/internals/symbol.rs:13  serde_derive/src/internals/symbol.rs::Variable::DENY_UNKNOWN_FIELDS
test_suite/tests/regression/issue2792.rs:6
test_suite/tests/test_de.rs:107
test_suite/tests/test_de_error.rs:31
test_suite/tests/test_de_error.rs:39

When a pattern that looks like a regular expression misses because of --fixed, greppy names the flag and the matching next command, with exit code 1:

$ greppy search-pattern 'deny_unknown_fields\b' --fixed
status: no_matches
scope: live Greppy-discovered source files in the repository
message: no matches
next: search excluded source directly: greppy rg -n -F 'deny_unknown_fields\b' .
reason: --fixed took the pattern literally, and it reads as a regular expression
next: search it as a regular expression: greppy search-pattern 'deny_unknown_fields\b'
next: search definition names: greppy search-symbol 'deny_unknown_fields\b'

A line inside a definition carries a qualified locator that greppy read accepts. With it, you read the enclosing definition directly:

$ greppy read serde_derive/src/internals/attr.rs::Container::deny_unknown_fields
serde_derive/src/internals/attr.rs:563-565  Container::deny_unknown_fields
    pub fn deny_unknown_fields(&self) -> bool {
        self.deny_unknown_fields
    }

plus: fused ranking

greppy plus "rename all fields" --k 5 --explain

plus combines literal and full-text signals, symbol names, fuzzy meaning and graph neighbours into one ranked list of grep-like file:line:snippet lines. This is search output, not a generated answer. --k N sets the number of hits (default 10), --explain appends the score, the signals and the symbol to each line, and --code prints the enclosing source span under hits that have a symbol:

$ greppy plus "rename all fields" --k 5 --explain
serde_derive/src/internals/symbol.rs:27:pub const RENAME_ALL_FIELDS: Symbol = Symbol("rename_all_fields");  # score=1.000 signals=fuzzy,fuzzy-token,symbol symbol=serde_derive/src/internals/symbol.rs::Variable::RENAME_ALL_FIELDS
test_suite/tests/test_macros.rs:782:fn test_rename_all_fields() {   # score=0.936 signals=fuzzy,fuzzy-token,kind=code,symbol,vector symbol=test_suite/tests/test_macros.rs::Function::test_rename_all_fields
serde_derive/src/internals/attr.rs:555:pub fn rename_all_fields_rules(&self) -> RenameAllRules {    # score=0.934 signals=fuzzy,fuzzy-token,kind=code,symbol,vector symbol=serde_derive/src/internals/attr.rs::Container::rename_all_fields_rules
serde_derive/src/internals/case.rs:174:fn rename_fields() { # score=0.873 signals=fuzzy,fuzzy-token,kind=code,vector symbol=serde_derive/src/internals/case.rs::Function::rename_fields
serde_derive/src/internals/attr.rs:940:pub fn rename_all_rules(&self) -> RenameAllRules {   # score=0.872 signals=fuzzy,fuzzy-token,kind=code,vector symbol=serde_derive/src/internals/attr.rs::Variant::rename_all_rules

A one-word query skips the semantic signal and ranks on the exact signals. greppy says so on stderr:

$ greppy plus deny_unknown_fields --k 3 --explain
grep: skipped EmbeddingGemma for literal/exact query; using exact signals only
serde_core/src/de/impls.rs:2124://     #[serde(deny_unknown_fields)]    # score=1.000 signals=text symbol=-
serde_core/src/de/impls.rs:2418://     #[serde(deny_unknown_fields)]    # score=1.000 signals=text symbol=-
serde_core/src/de/impls.rs:2598://     #[serde(deny_unknown_fields)]    # score=1.000 signals=text symbol=-

plus takes --k, --explain, --code and --json. --kind, --path and --all belong to the other three search commands.

Shared flags

These flags work with search, search-symbol and search-pattern:

Flag Effect
--kind K results of one kind: function, method, class, struct, enum or trait. greppy ignores any other value with a note that lists the valid values
--path P results under this file or directory; repeatable
--code also prints the source at each result
--all every result instead of the first few; search stays at eight hits
--json the same answer as data, with exact counts

expand

Some results offer a prepared evidence pack, such as the last line of a brief:

expand be76477a311cb85b — the call tree below expand_derive_deserialize sketched, 100 functions, 1186 lines

greppy expand ID prints it in one call, so you don't open each file:line location yourself. With --json, the offer is in the expand field.

$ greppy expand be76477a311cb85b
serde_derive/src/de.rs:84
fn precondition(cx: &Ctxt, cont: &Container) {
  85  precondition_sized
  86  precondition_no_de_lifetime
}

serde_derive/src/de.rs:145
    fn new(cont: &Container) -> Self {
  147  this_type
  148  this_value
  149  borrowed_lifetimes
  150  build_generics
    }
…

Chain into read

greppy search-symbol apply_to_field --json | greppy read -
$ greppy search-symbol apply_to_field --json | greppy read -
serde_derive/src/internals/case.rs:81-109  RenameRule::apply_to_field
    /// Apply a renaming rule to a struct field, returning the version expected in the source.
    pub fn apply_to_field(self, field: &str) -> String {
        match self {
            None | LowerCase | SnakeCase => field.to_owned(),
            UpperCase => field.to_ascii_uppercase(),
…
            KebabCase => field.replace('_', "-"),
            ScreamingKebabCase => ScreamingSnakeCase.apply_to_field(field).replace('_', "-"),
        }
    }