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('_', "-"),
}
}