needle

July 20, 2026 · View on GitHub

A fast, signal-aware fuzzy picker for neovim, rendered in plain splits (a prompt window + a results window). It ranks files not just by fuzzy match score but by weighted signals about how relevant a file is to what you're currently doing.

Installation

Install with neovim's native plugin manager, vim.pack (Neovim 0.12+):

vim.pack.add({ "https://github.com/dlants/needle" })
require("needle").setup()

needle has no required external dependencies and only requires Neovim 0.12+ (for vim.pack). The files picker prefers fd and falls back to rg, then to find — so it works out of the box, but fd or rg is recommended for the best file listing.

Sources

CommandFunctionWhat it lists
:Needle [dir]M.files(opts)files under the search root (fd/rg/find driven)
:NeedleBuffersM.buffers()the buffer list
:NeedleHelpM.help()help tags

The search root is picked from the current buffer: cwd if the buffer is under it, else the nearest git root, else the buffer's directory. :Needle accepts an optional directory argument.

Ranking signals

Files are scored by a fuzzy match (needle/score.lua) plus weighted signals, shown as a blamg flag prefix column:

  • in buffer list
  • adjacent dir — directory proximity to open buffers (0..1)
  • recent access — exponential decay against access half-life
  • recent mtime
  • git dirty

Access history is persisted to stdpath("data")/needle/state.json and updated on BufWinEnter (throttled).

Keymaps (inside the picker)

KeyAction
<C-j> / <C-n> / <Down>next result
<C-k> / <C-p> / <Up>previous result
<C-d> / <C-u>jump 10 down / up
<CR>open
<C-x>open in split
<C-v>open in vsplit
<C-t>open in tab
<C-h>toggle unrestricted (--no-ignore) file listing
<Esc> / <C-c>close

Setup

require("needle").setup({
  -- files_command = nil,        -- override: a list, or function(unrestricted) -> list
  -- exclude_dirs = {},          -- extra dir names to skip (.git/node_modules always skipped)
  weights = {
    in_buffer     = 60,
    adjacent_dir  = 50,
    recent_access = 50,
    recent_mtime  = 5,
    git_dirty     = 8,
  },
  access_half_life_s = 86400,    -- 1 day
  mtime_half_life_s  = 604800,   -- 1 week
  max_render         = 80,
  debounce_ms        = 25,
})

Suggested keymaps:

vim.keymap.set("n", "<leader>f", function() require("needle").files() end)
vim.keymap.set("n", "<leader>b", function() require("needle").buffers() end)
vim.keymap.set("n", "<leader>h", function() require("needle").help() end)

Other neovim plugins by dlants:

  • magenta.nvim — transparent tools for agentic AI workflows.
  • shuck — a streamed shell-command picker (live-grep replacement).
  • glean — a git diff reviewer in a single foldable buffer.