wayfinder.nvim is a guided code exploration tool for the current symbol.
Wayfinder is not a general search tool. It does not try to replace Telescope or grep. It replaces the manual loop of jump, grep, back, open, back, and "where was that test again?"
From the current symbol or file, Wayfinder gathers the most relevant nearby code:
- definitions
- references
- callers
- likely tests
- recent commits
It opens as a centered 3-pane picker, loads sources progressively, and keeps the screen focused on facets, rows, badges, previews, and a Trail you can actually keep.
Pin useful stops into Trail while you explore, then save that Trail per project and resume the same path later.
- Centered 3-pane floating layout
- Facet rail with counts
- Dense result list with badges and grouped headers
- Syntax-highlighted preview
- Preview header with project-relative file context and line range
- Trail facet for pinned breadcrumbs
- Persistent named Trails per project
- Explicit resume for the last active saved Trail
- Async, cancelable LSP loading plus async tests and git loading
- Local filter with negation and phrase matching
- Jump actions
- Neovim
0.10+
With lazy.nvim:
{
"error311/wayfinder.nvim",
opts = {},
}Wayfinder works with the default setup:
require("wayfinder").setup({})
vim.keymap.set("n", "<leader>wf", "<Plug>(WayfinderOpen)", { desc = "Wayfinder" })Open it on a symbol for definitions, references, callers, likely tests, and recent commits. If there is no symbol under the cursor, it falls back to the current file.
- Open Wayfinder on the current symbol or file.
- Move across
Calls,Refs,Tests,Git, andTrail. - Use preview to confirm the right match before jumping.
- Pin useful stops into Trail while exploring.
- Save, reload, or resume a Trail later if you want to keep that exploration path.
If you want to tune the layout, keep it small:
require("wayfinder").setup({
layout = {
width = 0.88,
height = 0.72,
},
})You do not need to set any scope or performance options for normal repos.
If you work in a large repo or monorepo, Wayfinder can narrow broad sources like text matches, likely tests, and git history, while keeping slow LSP work bounded.
Mental model:
project: search the whole project, usually the git rootpackage: search the nearest app/package/module rootcwd: search from the current Neovim working directoryfile_dir: search from the current file directory
Common monorepo setup:
require("wayfinder").setup({
performance = "fast",
scope = {
mode = "package",
package_markers = {
"package.json",
"tsconfig.json",
"pyproject.toml",
"go.mod",
"Cargo.toml",
".git",
},
},
limits = {
refs = { max_results = 200, timeout_ms = 1200 },
text = { enabled = true, max_results = 100, timeout_ms = 800 },
tests = { max_results = 50, timeout_ms = 700 },
git = { enabled = true, max_commits = 15, timeout_ms = 400 },
},
})Those package_markers are just common defaults. They are not required files, and you can override them if your repo uses different boundaries.
performance presets:
fast: tighter limits and shorter timeoutsbalanced: default behaviorfull: broader limits and looser timeouts
vim.keymap.set("n", "<leader>wf", "<Plug>(WayfinderOpen)", { desc = "Wayfinder" })
vim.keymap.set("n", "<leader>wtn", "<Plug>(WayfinderTrailNext)", { desc = "Wayfinder Trail Next" })
vim.keymap.set("n", "<leader>wtp", "<Plug>(WayfinderTrailPrev)", { desc = "Wayfinder Trail Prev" })
vim.keymap.set("n", "<leader>wto", "<Plug>(WayfinderTrailOpen)", { desc = "Wayfinder Trail Open" })
vim.keymap.set("n", "<leader>wts", "<Plug>(WayfinderTrailShow)", { desc = "Wayfinder Trail Show" })Core:
:Wayfinder:WayfinderExportQuickfix:WayfinderExportTrailQuickfix
Trail:
:WayfinderTrailNext:WayfinderTrailPrev:WayfinderTrailOpen:WayfinderTrailShow:WayfinderTrailSave:WayfinderTrailSaveAs:WayfinderTrailLoad:WayfinderTrailResume:WayfinderTrailDelete:WayfinderTrailRename
Mappings:
<Plug>(WayfinderOpen)<Plug>(WayfinderTrailNext)<Plug>(WayfinderTrailPrev)<Plug>(WayfinderTrailOpen)<Plug>(WayfinderTrailShow)
j/kmovegg/Gfirst / last result<PageUp>/<PageDown>page movement<C-u>/<C-d>move by half a pageh/lswitch facet<Tab>next facet<S-Tab>previous facet<CR>jumpsopen in splitvopen in vsplittopen in tabppin into TrailPopen Trail immediatelySopen Trail menu[previous saved Trail]next saved Trailxexport current facet to quickfixddremove pinned trail itemdaclear Trail/filter<C-l>clear filterDtoggle detailsrrefreshqclose- mouse wheel scrolls results
Trail is the working breadcrumb list you build while exploring.
Core Trail actions:
ppins the current itemPopens the Trail facetSopens the Trail menu for save/resume/load/rename/delete actions[/]cycle through saved Trails for the current projectddremoves the selected Trail itemdaclears the current Trail
Facet hopping within one open Wayfinder session remembers the last selected item per facet when it still exists.
Trail commands outside Wayfinder:
:WayfinderTrailNextopens the next Trail item:WayfinderTrailPrevopens the previous Trail item:WayfinderTrailOpenopens the current Trail item:WayfinderTrailShowopens Wayfinder on the Trail facet:WayfinderTrailResumeloads the last active saved Trail for the current project
Persistent named Trails:
- saved Trails are project-scoped and stored under Neovim state, not in your repo
- nothing persists automatically just because you pin items
- normal
:Wayfinderopens do not auto-load saved Trails - if you never save a Trail, ordinary Trail behavior stays the same
:WayfinderTrailSavesaves the current working Trail:WayfinderTrailSaveAssaves the current working Trail under a different name:WayfinderTrailLoadloads a saved Trail back into the current working Trail:WayfinderTrailResumeloads the most recently saved or loaded Trail for this project:WayfinderTrailRenamerenames a saved Trail:WayfinderTrailDeletedeletes a saved Trail entry- once a saved Trail is loaded,
Save Trailupdates that same saved Trail andSave Trail Ascreates a named variant
Top bar Trail states:
Trail (2 saved)Trail (2 saved) • unsavedTrail (3 saved): auth bugTrail (3 saved): auth bug • modified
Filter examples:
usermatchesuseruser testrequires both termsuser !specexcludes matches containingspec"user service"matches that exact phrasecreate !"git status"excludes that exact phrase
Quickfix export:
:WayfinderExportQuickfixexports the current visible facet in its current order:WayfinderExportTrailQuickfixexports Trail in Trail order
Callsshows LSP definitions and callersRefsis split intoLSP ReferencesandText Matches- weak-source reasons now show up in the top bar for the current selection
Testsis heuristic and intentionally ranked below calls and refsTestscan show a small heuristic reason like filename or symbol-text matchingGitshows recent commits touching the current fileGitcan show a small file-touch reason while commit metadata stays in details
scope.modecontrols how far Wayfinder searches:projectuses the project rootcwduses the current Neovim working directorypackageuses the nearest package/module markerfile_diruses the current file directory
limitscan cap expensive sources without changing the UI model:refs.max_results,refs.timeout_mstext.enabled,text.max_results,text.timeout_mstests.max_results,tests.timeout_msgit.enabled,git.max_commits,git.timeout_ms
With package scope enabled, Wayfinder keeps text matches, likely tests, and other broad searches inside the nearest app or module instead of spilling across the full repo.
The repo includes a small fixture app plus a tiny demo LSP so screenshots and gifs are reproducible.
nvim -u demo/minimal_init.lua demo/fixture-app/src/user_service.tsMove the cursor onto createUser, then run :Wayfinder.
More demo notes are in demo/README.md.
:checkhealth wayfinder reports:
- Neovim version
- plugin load status
ripgrepavailability for Text Matchesgitavailability for the Git facet- active LSP clients for the current buffer
- resolved scope root for the current buffer
- saved Trail storage root
- saved Trail storage file for the current project
- saved Trail count for the current project
- last active saved Trail for the current project
- current
performanceandscope.modeconfig
:checkhealth wayfinderMIT



