Sitelet https://github.com/nacro90/numb.nvim
Skip to content

Repository files navigation

numb.nvim

Peek lines of the buffer while you type :{number}, and jump only when you mean it.

CI License: MIT Neovim 0.10+

demo

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.

Features

  • 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,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.
  • 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, with User NumbPeek / User NumbUnpeek events 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,15d highlights 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. :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.

Requirements

Neovim 0.10 or newer. Nothing else; numb.nvim uses only stock Neovim and Lua.

Installation

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'

vim-plug:

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'

Usage

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 }

Options

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.

Runtime control

: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 highlight

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.

Float peek

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.

Statusline integration

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.

Lua API

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.

Troubleshooting

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.

Documentation

:h numb covers everything above in reference form, option by option and function by function.

Contributing

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.

License

MIT

About

Peek lines just when you intend

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

880 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages