grug-far opens a project-wide search as an editable buffer and writes your edits back across files
Delete a match from the results buffer and it drops out of the write; edit a match and Sync rewrites that line in the file it came from.
The rename you cannot read before it lands
Renaming userService to userClient across a
repository is a chore every editor solves badly. A :s
substitution rewrites one buffer. :cdo and
:cfdo drive that substitution over a quickfix list, but
you approve the list of locations before you see the substituted
text. A quickfix replace or a shell rg | sed
pipe is fire-and-forget: the write reaches disk before you have read a
single changed line. grug-far.nvim, by Stephan Badragan, answers with
one move. The matches open in a single Neovim buffer, and that buffer
is editable.1
Search runs on ripgrep. What comes back is editable text, and a Sync command writes your changes back across every file at once.1 The plugin's own requirements make the dependency plain: ripgrep is the search backend itself and is required, at version 14 or above, 15 recommended.1
Editing the matches themselves
The plainest way to see what that buys is to search and replace
nothing. Open the split with :GrugFar and type a bare
pattern into the Search field, say TODO. Every
TODO in the project renders as one line per match,
grouped under the file it came from. Now the part the other tools have
no answer for: those result lines are editable. Fix the wording on the
three you mean to keep, delete the lines for the matches you want to
skip, and press <localleader>s.2
:GrugFar
Search: TODO
auth.lua:14 -- TODO validate the token before this point
auth.lua:52 -- TODO rate-limit repeated failures
db.lua:9 -- TODO retry once on a dropped socket
db.lua:41 -- TODO(legacy) leave this one alone <- delete this line to skip it
<localleader>s " Sync All: write the edited lines back, file by file
That keystroke is Sync All, and the README states the model in one sentence: it “does a line by line sync based on what's in the results area.”1 Deleting a result line is not a special command. The line is simply no longer in the results area, so Sync does not write it. A project-wide edit you assemble by hand and read in full before any byte reaches disk is what the quickfix path does not offer.
Why Replace runs ripgrep, not sed
The default engine is ripgrep itself.2
Some older find-and-replace plugins shelled out to sed; grug-far does
not. Its ripgrep engine replaces by running ripgrep, and the source
carries no sed anywhere.3
For a capture-group rename, put a group in Search and reference it in
Replace: search (\w+)Service, replace
${1}Client, then press
<localleader>r. Underneath, the engine runs
rg --replace='${1}Client' --passthrough on each matched
file.1
The capture syntax is ripgrep's, which is the Rust regex crate's:
$1 or ${1} for the first group, a named
$word for (?P<word>\w+), and
$0 for the whole match. It is never sed's
\1.4
userService becomes userClient and
authService becomes authClient, everywhere
at once.
Replace and Sync All usually land the same bytes, and the difference
matters only at one point. Replace runs the rg command
and, in the README's words, “does not depend at all on what's in
the results area,” while Sync reads the buffer.1
If you never touch the result lines, the two produce equivalent
outcomes. The editable buffer earns its place the moment a blanket
substitution is wrong: that is where you correct it by hand, delete
the false positives, and sync only what you approved.
Swapping in ast-grep drops the sync buffer
The same surface drives a second engine. Press
<localleader>e to swap to ast-grep, one of the
three engines the default configuration enables alongside a rules
variant, and the Search field becomes an ast-grep pattern.2
The structural rewrite ast-grep is built for, turning
$A && $A() into
$A?.(), runs here as
ast-grep run --pattern='$A && $A()' --rewrite='$A?.()'
--update-all, previewed as a diff in the editor before you
apply it.56 The line to draw is
plain: ast-grep is the standalone matcher and rewriter, and grug-far
is the interactive surface that can drive it. The signature move does
not come with it. Sync is a ripgrep-engine feature; the ast-grep
engine ships no sync path at all, so on it you edit the Replace field
and run Replace, never the result lines.61
What it costs, and whether to trust it
The bill is short. grug-far needs Neovim 0.11.0 or newer, with tag
1.6.3 pinned for anyone still on 0.10, and the ripgrep dependency
already named.1 ast-grep
is a second, optional install, wanted only if you use its engine, 0.36
or above recommended.1
Two limits bind the model. A search stops after
maxSearchMatches, 2000 by default, on the reasoning that
millions of matches are most likely pointless and can freeze the
buffer; a rename that must touch more sites than that needs a
narrower pattern or a raised cap.2
And sync, the feature the whole case rests on, is the narrowest
surface of all: ripgrep engine only, and disabled even there under
--multiline and --multiline-dot-all, where
you fall back to Replace.1
- Project-wide matches open as one editable buffer you read in full before any write lands.
-
The default engine is ripgrep's own
--replace, with Rust-regex capture groups, not a sed shell-out. - A second engine drives ast-grep's structural rewrites from the same buffer, previewed as a diff.
- The edit-and-sync move is ripgrep-only, and off under multiline search.
- Search caps at 2000 matches by default, so the largest renames need tuning.
- ripgrep is a hard dependency; structural rewrites add ast-grep as a second one.
Maintenance is active and checkable. The latest tag is 1.6.76, dated 2026-07-28, ten days before this was written, and the tag stream runs weekly to fortnightly across 2026.7 The Releases tab reads empty, which is not abandonment: the version record lives in the tags, not in GitHub releases. The commit log agrees, with the most recent commit landing the same day and outside contributors' pull requests merged by the maintainer.8 The open backlog is two issues, both filed 2026-08-04, against a single maintainer and roughly two thousand stars.910 A low open count beside active commits is consistent with responsive triage, though the record shows the counts, not the response times.
Reach for grug-far when a rename spans the project and you want to read the whole change before it lands, on patterns a regular expression can match. Its one real distinction, the editable and syncable results buffer, is ripgrep-only, so a codebase whose renames are inherently structural spends its time on the ast-grep engine, where grug-far is a diff-previewing front end and little more.6 Development is steady but single-handed; a maintainer count of one is the standing risk to weigh, and a stalled tag stream would be the signal to revisit it.7
Sources
- MagicDuck/grug-far.nvim · README
- MagicDuck/grug-far.nvim · lua/grug-far/opts.lua
- MagicDuck/grug-far.nvim · engine/ripgrep/replace.lua
- BurntSushi/ripgrep · GUIDE.md
- MagicDuck/grug-far.nvim · engine/astgrep/getArgs.lua
- MagicDuck/grug-far.nvim · engine/astgrep/replace.lua
- MagicDuck/grug-far.nvim · Tags
- MagicDuck/grug-far.nvim · Commits
- MagicDuck/grug-far.nvim · Issues
- GitHub · MagicDuck (Stephan Badragan)