Peek lines of the buffer while you type :{number}, and jump only when you mean it.
Colorscheme: vim-substrata
Typing :120 in Vim is a blind jump: you lose your place, look around, and press
<C-o> to crawl back. numb.nvim previews the destination as you type it. Confirm
with <CR> and you are there, abort with <Esc> and the window goes back to the
cursor position, the window options and the vertical scroll position it had.
- Peek numeric Ex addresses. Absolute (
:15), relative (:+5,:-3), chained (:++), the line symbols:.and:$, and arithmetic on any of them (:.+5,:$-3,:10-2). Out of bounds targets clamp to the first or last line instead of erroring. Marks and searches (:'a,:/foo/) are left to Vim and are not previewed. - Preview destructive ranges.
:50,80dhighlights lines 50 to 80 before you commit. Neovim previews:substitutethroughinccommandand nothing else, so:d,:y,:m,:tand:ghad no preview at all. - Preview far jumps without losing your place. With
peek_style = "float"(or"auto", which only floats when the target is off screen) the target is shown in a strip over the window while the window itself does not move; confirming still lands on the target. - An API for other plugins.
require('numb').peek()gives pickers and symbol lists the same preview, withUser NumbPeek/User NumbUnpeekevents to follow any peek. - Faithful to Ex semantics. Both separators are honored (
,counts from the cursor,;from the previous address), and when more than two addresses are given the last two win, so:5,10,15dhighlights the 10 to 15 that Ex will really act on. - Stays out of the way. Per window state, folds unfolded so the target is
really on screen, the jumplist entry pushed so
<C-o>still works, and no runtime dependencies. - Batteries included.
:Numbto toggle at runtime,vim.w.numb_peekingfor your statusline,:checkhealth numbwhen something looks off, and:h numbfor the full reference.
Neovim 0.10 or newer. Nothing else; numb.nvim uses only stock Neovim and Lua.
numb.nvim calls setup() itself from plugin/numb.lua, so it starts working as
soon as it is on your runtimepath. Passing options is the only reason to call
setup() yourself.
With lazy.nvim:
{ 'nacro90/numb.nvim' }
-- or, to change the defaults
{
'nacro90/numb.nvim',
opts = {
centered_peeking = false,
},
}With vim.pack, the manager built into Neovim 0.12:
vim.pack.add {
'https://github.com/nacro90/numb.nvim',
}Pinning a version, and other plugin managers
vim.pack.add also takes a table with a version field, which accepts a branch,
a tag, a commit hash, or a range built with vim.version.range():
vim.pack.add {
{ src = 'https://github.com/nacro90/numb.nvim', version = vim.version.range('1.x') },
}Paq:
paq 'nacro90/numb.nvim'Plug 'nacro90/numb.nvim'Packer is no longer maintained and its repository is archived. Use it only if your config already depends on it:
use 'nacro90/numb.nvim'Type a line address on the command line and watch the buffer follow along:
:3
:+12
:$-5
:80,120dNothing has to be called for that: plugin/numb.lua already ran setup(). From
an init.vim, pass options through :lua when you want to change them:
:lua require('numb').setup{ centered_peeking = false }Every option may be omitted; the rest keep their defaults.
| Option | Default | Effect |
|---|---|---|
show_numbers |
true |
Set number in the peeked window |
show_cursorline |
true |
Set cursorline in the peeked window |
hide_relativenumbers |
true |
Turn relativenumber off, so the numbers stop shifting |
number_only |
false |
Peek only when the command line is nothing but an address, so :15 peeks and :15,20d does not |
centered_peeking |
true |
Center the previewed line, as zz does, except that near the end of the buffer the window stays full |
range_peek |
true |
Highlight the whole range while typing :N,M{cmd} |
disable_for_buftype |
{} |
buftype values to leave alone, for example { 'terminal' } |
disable_for_filetype |
{} |
filetype values to leave alone, for example { 'fugitive' } |
peek_style |
"window" |
Where a peek is drawn: "window" in the window itself, "float" in a float over it that leaves the window untouched, "auto" in place when the target is on screen and in a float when it is not |
float |
{ height = 0.4, position = "auto" } |
How a float peek looks: height as a fraction of the window or a number of rows, position as "bottom", "top" or "auto", and an optional win_config function; see Float peek |
require('numb').setup {
show_numbers = true,
show_cursorline = true,
hide_relativenumbers = true,
number_only = false,
centered_peeking = true,
range_peek = true,
disable_for_buftype = {},
disable_for_filetype = {},
peek_style = "window",
float = { height = 0.4, position = "auto" },
}Nothing is excluded by default, terminal buffers included: Vim performs :15 in
a terminal buffer exactly as it does anywhere else, so excluding one means
accepting a jump that happens with nothing shown before it. Exclude a type when
that is the trade you want.
A misspelled option name, or a value of the wrong type, is reported through
vim.notify and ignored. Invalid configuration never raises: the affected
option keeps its default and the rest of your table is applied as usual.
:Numb disable " stop peeking
:Numb enable " resume peeking with the configuration already in effect
:Numb toggle " flip the current state (the default when no argument is given)Subcommands are tab completed. The same operations from Lua:
require('numb').enable(opts?) -- opts is optional and overrides the config
require('numb').disable()
require('numb').is_enabled() -- boolean
require('numb').is_peeking(winnr?) -- boolean, current window when omitted
require('numb').get_config() -- a copy of the active optionsYour options survive a disable(), so enable() resumes with them and there is
no need to call setup() again.
To keep the plugin from loading at all, set the guard variable before startup:
vim.g.loaded_numb = 1The range preview uses the NumbRange highlight group, linked to Visual by
default. Override it whenever you like, before or after setup():
vim.api.nvim_set_hl(0, 'NumbRange', { bg = '#3a3a50' })A highlight belongs to a buffer rather than a window, so the range shows up in
every split displaying that buffer. The cursor, the window options and
vim.w.numb_peeking stay per window.
With peek_style = "float" the target is shown in a float over the window
instead of in the window itself. The window keeps its cursor, its scroll
position and its options, so where you were stays in sight while you look
elsewhere. The float closes when the peek ends, and <CR> lands on the target
as usual, jumplist entry included. "auto" peeks in place when the target is
already on screen and in a float when it is not, deciding again on every
keystroke:
require('numb').setup {
peek_style = 'auto',
}The float takes up float.height of the window (a fraction below 1, a number
of rows from 1 up, never fewer than 3) on the edge float.position names;
"auto" is the bottom unless that would cover the cursor line. By default it
draws only a top edge titled with the line and the buffer's length, and it
respects winborder on Neovim 0.11 and later. float.win_config gets the
configuration numb computed for nvim_open_win and returns the one to use, so
it has the last word:
require('numb').setup {
peek_style = 'float',
float = {
height = 12,
win_config = function(config)
config.border = 'rounded'
config.title_pos = 'center'
return config
end,
},
}The float uses NormalFloat, FloatBorder and FloatTitle, never takes focus,
and opens and moves without window autocommands. A window too small for a
float peeks in place instead. vim.w.numb_peeking and the win of the peek
events still name the window being peeked; User NumbPeek also carries the
float as float_win. See :h numb-float for the details.
While a peek is active, numb.nvim sets vim.w.numb_peeking = true in that
window, and clears it as soon as the peek ends, whether confirmed or aborted.
The scope is the window, so two splits viewing the same buffer never cross-flag
each other.
require('lualine').setup {
sections = {
lualine_x = {
function() return vim.w.numb_peeking and 'peek' or '' end,
},
},
}require('numb').is_peeking() answers the same question from Lua.
Other plugins can use the same preview.
require('numb').peek(winnr, line, opts?) previews a line in any window, 0
being the current one, and returns a handle with update(line, opts?),
accept(), cancel() and is_active(). Pass
opts.range = { first, last } to highlight a range as well, and
opts.style to draw that peek with another peek_style than the configured
one. accept() jumps
right away and pushes the jumplist entry, so <C-o> returns; cancel() puts
the window back as it was. A picker previewing its selection looks like this:
local numb = require('numb')
local preview
local function on_selection_changed(win, line)
-- update() returns false once the handle is inactive, so open a new peek
if not (preview and preview:update(line)) then
preview = numb.peek(win, line)
end
end
local function on_confirm()
if preview then preview:accept() end
preview = nil
end
local function on_close()
if preview then preview:cancel() end
preview = nil
endOnly one peek is live at a time: a new peek(), or a command line that
addresses a line, takes over, and the old handle stops doing anything. While
the plugin is disabled peek() returns a handle that is inactive from the
start. Every peek, the command line's included, fires User NumbPeek when it
opens or moves and User NumbUnpeek once when it ends, never twice, with the
window, the line and the range in the event data. NumbUnpeek is skipped only
if putting the window back or landing raises an error, which can come from an
autocommand of yours such as WinEnter or from the :normal the landing runs.
The API is public from 1.3.0, and a breaking change to it needs a major
version. See :h numb.peek() and :h NumbPeek-events for the full contract.
Run :checkhealth numb. It reports the Neovim version, where numb.nvim was
loaded from, whether a second copy is shadowing it on the runtimepath, whether
setup() ran, whether the autocommands are still installed, and the
configuration currently in effect, so a pasted report is self-contained.
:h numb covers everything above in reference form, option by option and
function by function.
Contributions are welcome. CONTRIBUTING.md has the full
workflow: layout, coding style, tests, commit conventions and changelog
discipline. In short, run ./scripts/check.sh before opening a pull request and
add a test for whatever you changed.
Release history lives in CHANGELOG.md, following Keep a Changelog.
