"Not capable enough to consult, but everything your need to refer"
https://github.com/user-attachments/assets/d441844b-12ef-45a8-b2d4-560f8d7e6f10
fd) and Quickfixhttps://github.com/user-attachments/assets/2f91247d-5db0-42e6-b487-b97abb5e8b86
https://github.com/user-attachments/assets/3d31ddb8-abda-43c9-b62e-4b2d5b3ec890
https://github.com/user-attachments/assets/5b7be64e-e3f1-43c3-83d7-27c69b796cd1
refer.nvim is a minimalist picker for Neovim.
It is designed to:
It is not designed to:
This plugin only provides you with a picker. I am not spending any energy to develop an optimised fuzzy sorter. There are already implementations out there that you can use. This plugin provides you with the ability to register these fuzzy sorter implementations.
There are already some sorters registered:
blink.cmp installed. Or else it will download just the library from
GitHub.). matchfuzzy (Vim is generous... :h matchfuzzy().). mini.fuzzy if installed (This supports strings with
spaces in them, unlike the above two options.). Files picker).Grep picker).Using lazy.nvim:
{
"juniorsundar/refer.nvim",
dependencies = {
-- Optional:
-- "saghen/blink.cmp",
-- "nvim-mini/mini.fuzzy",
},
config = function()
-- The plugin autoloads, but you can pass opts to setup.
require("refer").setup(
-- opts
)
end
}
Use :Refer <subcommand> to launch pickers:
| Command | Description |
|---|---|
Files |
Fuzzy find files using fd (Async) |
Grep |
Live grep using ripgrep (Async) |
Selection |
Search for word under cursor or visual selection |
Lines |
Filter lines in the current buffer |
Buffers |
Switch between open buffers |
OldFiles |
Browse recently opened files |
Commands |
Execute Vim commands interactively |
References |
List LSP references for symbol under cursor |
Definitions |
Go to LSP definition for symbol under cursor |
Implementations |
Go to LSP implementation for symbol under cursor |
Declarations |
Go to LSP declaration for symbol under cursor |
Symbols |
List LSP document symbols for current buffer |
LspServers |
Manage LSP servers (start/stop) |
Macros |
Edit and preview Vim registers/macro content |
Extras FindFile |
Emacs-style filesystem picker (requires extras.find_file = true in setup) |
vim.ui.selectvim.ui.selectUse refer as the interface for vim.ui.select (used by code actions and
plugins):
require("refer").setup_ui_select()
You can customize key bindings inside the picker window.
require("refer").setup({
keymaps = {
-- Bind to a built-in action name
["<C-x>"] = "close",
-- Bind to a built-in action with a description
["<C-z>"] = { action = "close", desc = "Close picker" },
-- Or use a custom function (desc is optional)
["<C-y>"] = function(selection, builtin)
print("You selected: " .. selection)
builtin.actions.close()
end
}
})
Default Keymaps:
| Key | Action | Description |
|---|---|---|
<Tab> |
complete_selection |
Complete selection |
<CR> |
select_input |
Confirm selection |
<C-n> / <Down> |
next_item |
Next item |
<C-p> / <Up> |
prev_item |
Previous item |
<C-v> |
toggle_preview |
Toggle preview |
<C-u> |
scroll_preview_up |
Scroll preview up |
<C-d> |
scroll_preview_down |
Scroll preview down |
<C-s> |
cycle_sorter |
Cycle sorter |
<C-q> |
send_to_qf |
Send to quickfix |
<C-g> |
send_to_grep |
Send to grep buffer (experimental) |
<M-a> |
select_all |
Select all |
<M-d> |
deselect_all |
Deselect all |
<M-t> |
toggle_all |
Toggle all marks |
<Esc> / <C-c> |
close |
Close picker |
| — | edit_entry |
Open selected item in current window |
| — | split_entry |
Open selected item in a horizontal split |
| — | vsplit_entry |
Open selected item in a vertical split |
| — | tab_entry |
Open selected item in a new tab |
| — | select_entry |
Call on_select with item and its attached data |
You can define custom sorting algorithms. For example, a simple prefix matcher:
require("refer").setup({
custom_sorters = {
my_prefix_sorter = function(items, query)
local matches = {}
for _, item in ipairs(items) do
if vim.startswith(item, query) then
table.insert(matches, item)
end
end
return matches
end,
},
-- Add to available sorters to allow cycling to it with <C-s>
available_sorters = { "blink", "my_prefix_sorter", "lua" },
})
Teach refer how to parse specific text formats to enable file preview and
navigation. This is useful if you are piping custom logs or tool output into
refer.
Scenario: You have input lines formatted like: src/main.lua [Line 10, Col 5].
require("refer").setup({
custom_parsers = {
my_log_format = {
-- Lua pattern with capture groups
pattern = "^(.-)%s+%[Line (%d+), Col (%d+)%]",
-- Map capture groups to keys ("filename", "lnum", "col", "content")
keys = { "filename", "lnum", "col" },
-- Optional type conversion
types = { lnum = tonumber, col = tonumber },
},
}
})
find)By default, refer uses fd. If you prefer standard find, you can provide a
custom command generator function.
require("refer").setup({
providers = {
files = {
-- Return the command as a table of strings
find_command = function(query)
return { "find", ".", "-type", "f", "-name", "*" .. query .. "*" }
end
}
}
})
grep)By default, refer uses rg (ripgrep). If you prefer standard grep, you can
provide a custom command generator function.
require("refer").setup({
providers = {
grep = {
-- Return the command as a table of strings
grep_command = function(query)
return { "grep", "-rnI", query, "." }
end
}
}
})
local refer = require("refer")
refer.pick(
{ "Option A", "Option B", "Option C" },
function(item)
print("You picked: " .. item)
end,
{
prompt = "Pick one > ",
-- Custom keymaps for this picker
keymaps = {
["<C-d>"] = function(selection, builtin)
print("Deleted: " .. selection)
builtin.actions.close()
end
}
}
)
ReferItem)Items passed to pick() can be plain strings or structured ReferItem
tables of the form { text = string, data = any }. When a structured item is
selected, data is passed directly to your on_select callback — no parser
needed.
Plain strings continue to work unchanged; they are normalized automatically.
local refer = require("refer")
refer.pick(
{
{ text = "src/main.lua", data = { path = "src/main.lua", lnum = 1 } },
{ text = "src/util.lua", data = { path = "src/util.lua", lnum = 1 } },
},
function(selection, data)
-- `selection` is the display text; `data` is the attached table
vim.cmd("edit " .. data.path)
vim.api.nvim_win_set_cursor(0, { data.lnum, 0 })
end,
{ prompt = "Jump > " }
)
Create a picker that runs a shell command based on your query (e.g., locate).
local refer = require("refer")
refer.pick_async(
function(query)
-- Return the command to run as a table of strings
-- Return nil to stop/wait (e.g. if query is too short)
if #query < 3 then return nil end
return { "locate", query }
end,
function(selection)
vim.cmd("edit " .. selection)
end,
{
prompt = "Locate > ",
debounce_ms = 200,
}
)
Create a picker with custom string formats and teach refer how to parse them
so the built-in file previewer works.
local refer = require("refer")
refer.pick(
{
-- Alternate format as col:lnum:filename
"10:5:lua/refer/picker.lua",
"20:1:README.md",
},
function(selection, data)
if data and data.filename then
vim.cmd("edit " .. data.filename)
vim.api.nvim_win_set_cursor(0, {data.lnum, data.col - 1})
end
end,
{
prompt = "Navigate > ",
preview = { enabled = true },
-- Custom parser for "row:col:filename"
parser = function(selection)
local lnum, col, filename = selection:match("^(%d+):(%d+):(.+)$")
if filename then
return {
filename = filename,
lnum = tonumber(lnum),
col = tonumber(col)
}
end
return nil
end
}
)
Sometimes you want the preview to correspond to something unrelated to the choice currently under selection. For example, you want to create a picker to move through the headings of a markdown file. You want the selections to be the heading text, but you want the preview to be where in the markdown file the heading is found.
local refer = require("refer")
local api = vim.api
if vim.bo.filetype ~= "markdown" then
return
end
local bufnr = api.nvim_get_current_buf()
local filename = api.nvim_buf_get_name(bufnr)
local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false)
local choices = {}
local lookup = {}
for lnum, line in ipairs(lines) do
local hashes, title = line:match("^(#+)%s+(.+)$")
if hashes then
local level = #hashes
local indent = string.rep(" ", level - 1)
local display_text = indent .. title
if not lookup[display_text] then
table.insert(choices, display_text)
lookup[display_text] = lnum
end
end
end
refer.pick(
choices,
function(selection)
local lnum = lookup[selection]
if lnum then
api.nvim_win_set_cursor(0, {lnum, 0})
end
end,
{
prompt = "Outline > ",
preview = { enabled = true },
parser = function(selection)
local lnum = lookup[selection]
if lnum then
return {
filename = filename,
lnum = lnum,
col = 1
}
end
return nil
end
}
)
refer.nvim ships with optional extras that are disabled by default. Enable
them via setup():
require("refer").setup({
extras = {
find_file = true,
},
})
extras.find_fileInspired by João Paulo.
Link to config.
Registers :Refer Extras FindFile — an Emacs-style filesystem picker. It
opens at your current working directory and lets you navigate your filesystem
incrementally: selecting a directory descends into it, selecting a file opens
it.
| Key | Action |
|---|---|
<CR> |
Descend into directory or open file |
<Esc> / <C-c> |
Close picker |
You can register new :Refer subcommands from your init.lua or from
third-party plugins.
local refer = require("refer")
refer.add_command("MyPicker", function(opts)
refer.pick({ "Choice A", "Choice B" }, function(selection)
print("Selected: " .. selection)
end, {
prompt = "My Picker > "
})
end)
Now you can run :Refer MyPicker and it will appear in tab completion.
The default configuration with all available options:
require("refer").setup({
-- General Settings
max_height_percent = 0.4, -- Window height (0.1 - 1.0)
min_height = 1, -- Minimum lines
-- Async Settings
debounce_ms = 100, -- Delay for async searching
min_query_len = 2, -- Min chars to start async search
-- Sorting
available_sorters = { "blink", "mini", "native", "lua" },
default_sorter = "blink",
-- Preview Settings
preview = {
enabled = true,
max_lines = 1000,
},
-- UI Customization
ui = {
mark_char = "●",
mark_hl = "String",
input_position = "top", -- "top" or "bottom"
reverse_result = false,
winhighlight = "Normal:Normal,FloatBorder:Normal,WinSeparator:Normal,StatusLine:Normal,StatusLineNC:Normal",
highlights = {
prompt = "Title",
selection = "Visual",
header = "WarningMsg",
},
},
-- Provider Configuration
providers = {
files = {
ignored_dirs = { ".git", ".jj", "node_modules", ".cache" },
find_command = { "fd", "-H", "--type", "f", "--color", "never" },
},
grep = {
grep_command = { "rg", "--vimgrep", "--smart-case" },
},
},
-- Extras (all disabled by default)
extras = {
find_file = false, -- set to true to register :Refer Extras FindFile
},
-- See "Tutorials" section for keymaps, custom_sorters, and custom_parsers
})