Read exactly one symbol, change exactly one symbol
What you get
Your agent reads a function, a method or a type by name, without hunting for line numbers. For a file it gets the outline. Edits are equally targeted: replace one symbol, rename it everywhere, change several files in one patch. Every edit reports back with a receipt, and greppy undo reverses it in one step.
Before and after
First task for serde, a read: Show me ContentDeserializer::deserialize_any.
Without greppy
serde/src/private/de.rs has twelve functions named deserialize_any. The agent finds the right one through its impl block and then cuts it out with sed.
$ grep -n 'impl.*for ContentDeserializer\|fn deserialize_any' serde/src/private/de.rs
38: fn deserialize_any<V>(self, _visitor: V) -> Result<V::Value, E>
1120: impl<'de, E> Deserializer<'de> for ContentDeserializer<'de, E>
1126: fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
1546: fn deserialize_any<V>(mut self, visitor: V) -> Result<V::Value, Self::Error>
…
$ sed -n '1126,/^ }$/p' serde/src/private/de.rs
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
match self.content {
Content::Bool(v) => visitor.visit_bool(v),
…
}
With greppy
$ greppy read ContentDeserializer::deserialize_any
serde/src/private/de.rs:1126-1154 ContentDeserializer::deserialize_any
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
match self.content {
Content::Bool(v) => visitor.visit_bool(v),
…
Content::Map(v) => visit_content_map(v, visitor),
}
}
| without greppy | with greppy | |
|---|---|---|
| Calls | 2 | 1 |
| Output characters | 2,737 | 1,639 |
| Tokens | 696 | 364 |
Second task, an edit: Rename get_lit_str to get_str_lit everywhere. The edit ran in a private copy of serde.
Without greppy
The agent checks every match to make sure it really means the function, then replaces by text substitution.
$ grep -rnw get_lit_str --include='*.rs' .
./serde_derive/src/internals/attr.rs:416: if let Some(s) = get_lit_str(cx, TAG, &meta)? {
./serde_derive/src/internals/attr.rs:438: if let Some(s) = get_lit_str(cx, CONTENT, &meta)? {
…
./serde_derive/src/internals/attr.rs:1411:fn get_lit_str(
…
./serde_derive/src/internals/attr.rs:1545: let Some(string) = get_lit_str(cx, BORROW, meta)? else {
$ perl -pi -e 's/\bget_lit_str\b/get_str_lit/g' serde_derive/src/internals/attr.rs
With greppy
$ greppy rename get_lit_str get_str_lit
applied serde_derive/src/internals/attr.rs:416,438,490,787,1086,1411,1460,1481,1523,1545 a6f0ae
| without greppy | with greppy | |
|---|---|---|
| Calls | 2 | 1 |
| Output characters | 1,039 | 97 |
| Tokens | 316 | 44 |
Both ways produce a byte-identical file: the definition and nine calls in serde_derive/src/internals/attr.rs are now named get_str_lit. The receipt names every changed line and the edit ID. One command reverses the whole rename:
$ greppy undo
reversed a6f0ae serde_derive/src/internals/attr.rs
How it works
read fetches the definition through the index and prints its current bytes, doc comments and attributes included. On request (--handle), every read command prints a handle that pins the file, the byte range and the content hash. replace-span refuses as soon as the file has changed since. rename changes the definition and every reference the graph resolves, and refuses when the old name still occurs as an identifier without a resolved reference. patch lands a unified diff atomically across all files: either all of them change, or none does. --verify checks the touched files with the matching local checker and reports its diagnostics. Every edit gets an edit ID, and undo reverses it as a whole.
The commands
| Command | what it does |
|---|---|
greppy read S |
a symbol's definition, byte-exact with doc comments and attributes; for a file, its outline |
greppy read-file F --lines A:B |
exactly the line range A to B of a file, inclusive and 1-based |
greppy replace S |
replace a definition, with --body its body; the new text comes as an argument or through stdin |
greppy patch --verify |
apply a unified diff as one transaction across all files and check the touched files |
greppy rename S NAME |
rename a definition and every graph-resolved reference |
greppy undo |
reverse the last edit, with ID a specific one |
More in the docs
- Read source:
read,read-smart,read-fileand handles - Edit transactionally: all edit commands, receipts,
--verifyandundo