The Nightly Build.
Expert Tools

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

the results buffer, edited by handNVIM
: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
Fig. 1 · Sync All writes exactly the lines standing in the buffer back to their files; deleting a result line removes that match from the write.1

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

What holds up
  • 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.
What to be careful about
  • 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.

Verdict

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

  1. MagicDuck/grug-far.nvim · README
  2. MagicDuck/grug-far.nvim · lua/grug-far/opts.lua
  3. MagicDuck/grug-far.nvim · engine/ripgrep/replace.lua
  4. BurntSushi/ripgrep · GUIDE.md
  5. MagicDuck/grug-far.nvim · engine/astgrep/getArgs.lua
  6. MagicDuck/grug-far.nvim · engine/astgrep/replace.lua
  7. MagicDuck/grug-far.nvim · Tags
  8. MagicDuck/grug-far.nvim · Commits
  9. MagicDuck/grug-far.nvim · Issues
  10. GitHub · MagicDuck (Stephan Badragan)