Your agent sees how the code fits together
What you get
Your agent asks about relationships instead of text. Who calls a function, what does it call, what depends on a change: greppy answers in one call with the file, the line and the name of the function. The agent no longer maps matching lines to their functions by hand.
Before and after
The question for serde: Who calls visit_content_map?
Without greppy
grep returns lines, not callers. The agent searches for the word and then maps every match to the function it sits in.
$ grep -rnw visit_content_map --include='*.rs' .
./serde/src/private/de.rs:1103: fn visit_content_map<'de, V, E>(
./serde/src/private/de.rs:1152: Content::Map(v) => visit_content_map(v, visitor),
./serde/src/private/de.rs:1399: Content::Map(v) => visit_content_map(v, visitor),
./serde/src/private/de.rs:1415: Content::Map(v) => visit_content_map(v, visitor),
$ grep -n 'fn \|impl' serde/src/private/de.rs | awk -F: '$1>=1103 && $1<=1415'
1103: fn visit_content_map<'de, V, E>(
1120: impl<'de, E> Deserializer<'de> for ContentDeserializer<'de, E>
1126: fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
1156: fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value, Self::Error>
…
1394: fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
1404: fn deserialize_struct<V>(
With greppy
$ greppy who-calls visit_content_map
serde/src/private/de.rs:1152 ContentDeserializer::deserialize_any
serde/src/private/de.rs:1399 ContentDeserializer::deserialize_map
serde/src/private/de.rs:1415 ContentDeserializer::deserialize_struct
| without greppy | with greppy | |
|---|---|---|
| Calls | 2 | 1 |
| Output characters | 2,569 | 204 |
| Tokens | 738 | 45 |
How it works
Compiled-in tree-sitter parsers split every file into definitions. From them greppy builds a typed symbol graph: functions, methods and types are nodes; calls, usages, type references and imports are edges. who-calls follows a symbol's incoming edges, callees its outgoing ones. impact keeps going transitively and shows in one call everything a change reaches; path connects two symbols through their call sites. Before every query greppy checks the index against the working tree, so the answer matches the current state of your files. When a name is defined more than once, you qualify it with its file, for example serde_derive/src/de/struct_.rs::deserialize_map.
The commands
| Command | what it does |
|---|---|
greppy who-calls S |
who calls or uses S, with file and line; several symbols in one call |
greppy callees S |
the indexed definitions that S calls directly, with their location |
greppy impact S |
the transitive reach in one call: --direction incoming (default) shows what breaks when you change S, outgoing what S reaches |
greppy path --from A --to B |
every call chain between two symbols as one tree of their call sites |
greppy where-am-i |
the repository at a glance: layout, languages, entry points, test directories |
More in the docs
- Navigate the graph: every navigation command, flag and output format
- How greppy works: index, graph and embeddings
- Limits: what a static graph does not see