
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.
: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.:50,80d highlights lines 50 to 80 before you
commit. Neovim previews :substitute through inccommand and nothing else, so
:d, :y, :m, :t and :g had no preview at all.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.require('numb').peek() gives pickers and
symbol lists the same preview, with User NumbPeek / User NumbUnpeek
events to follow any peek., counts from the
cursor, ; from the previous address), and when more than two addresses are
given the last two win, so :5,10,15d highlights the 10 to 15 that Ex will
really act on.<C-o> still works, and no
runtime dependencies.:Numb to toggle at runtime, vim.w.numb_peeking for
your statusline, :checkhealth numb when something looks off, and :h numb
for 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',
}
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,120d
Nothing 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 options
Your 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 = 1
The 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
end
Only 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.