Contributing to numb.nvim
August 3, 2026 ยท View on GitHub
Thanks for taking the time to contribute! This document covers the workflow, expectations, and tooling for changes to numb.nvim.
Code of Conduct
This project follows the Contributor Covenant.
Getting Started
Requirements:
- Neovim 0.10 or newer
- Stylua and selene, fetched at the versions CI pins by
scripts/tools.sh - A POSIX shell to run
scripts/check.sh
Clone, fetch the tools, and verify the local environment:
git clone https://github.com/nacro90/numb.nvim.git
cd numb.nvim
./scripts/tools.sh # downloads stylua and selene into .tools/
export PATH="$PWD/.tools:$PATH"
./scripts/check.sh
Do not skip the tools. Without them check.sh refuses to run the formatting and
lint stages, and formatting drift or an undefined variable will only surface as a
red build after you push. Both are single binaries with no Lua of their own, so
there is nothing to vendor; scripts/tools.sh pins the same versions CI installs.
scripts/check.sh runs the formatter check, a load smoke test, and the
regression suite. CI runs the same script on every push and pull request to
master.
Project Layout
lua/numb/init.lua everything that touches editor state
lua/numb/address.lua Ex address resolution, pure
lua/numb/config.lua option defaults and validation, pure
lua/numb/health.lua :checkhealth numb
plugin/numb.lua calls setup() once the plugin is on the runtimepath
doc/numb.txt the help file behind :h numb, checked against the code
tests/init.lua wires Neovim for the suite
tests/run.lua headless regression suite
scripts/check.sh every gate CI runs, and each one runnable on its own
scripts/tools.sh fetches the stylua and selene versions CI pins
scripts/verify_*.lua the gates that need a running Neovim
The plugin is three steps, all of them in init.lua:
CmdlineChangedasksnumb.addresswhat the command line points at.peek()previews that line, saving the window options, cursor and view it changes intostate.win_states[winnr].CmdlineLeaverestores that saved state, staying at the target when the command was confirmed rather than aborted.
address.lua and config.lua are pure on purpose: their rules can be tested by
calling one function, with no window, buffer or command line involved. Keep new
logic that does not need editor state in one of them.
Adding an option touches four files, and scripts/verify_doc.lua fails the build
if any of them is missed: the default in config.DEFAULTS, the row in the
Defaults: block of doc/numb.txt plus a *numb-{option}* tag for it, the
table in README.md, and a test in tests/run.lua. The same check requires a
*numb.{function}()* tag for every public function, so the help file is not
optional documentation here.
Reporting Bugs
Open an issue with:
- Neovim version (
nvim --versionfirst line). - A minimal reproduction config (ideally a
minimal_init.lua). - Step-by-step actions you took.
- What you expected vs. what happened.
- Screenshots or terminal recordings if the bug is visual.
Proposing Features
Open an issue first describing the use case and the proposed user-facing behavior. Discussing the design before opening a pull request avoids wasted work and keeps the plugin's footprint small.
Pull Request Workflow
- Fork the repository and create a feature branch from
master. - Make focused commits. One concern per commit; keep diffs reviewable.
- Add or update tests in
tests/run.luafor any behavior change. - Add an entry to the
[Unreleased]section ofCHANGELOG.md(see below). - Run
./scripts/check.shand make sure it passes locally. - Open a PR against
masterdescribing motivation, user-facing changes, and testing steps. Link related issues. - Attach screenshots or recordings when altering visuals.
Commit Messages
Short, imperative summaries. Conventional Commits style is preferred:
feat: push origin to jumplist on confirmed peek
fix: clamp relative jumps to buffer bounds
refactor: drop unused logging module
docs: add CHANGELOG.md
test: cover multi-window state
Keep the subject under ~70 characters. Use the body for the why when it is not obvious from the diff.
Changelog Entries
This project follows Keep a Changelog.
Add a bullet to the appropriate section under [Unreleased] in
CHANGELOG.md as part of the same commit or PR. Section order:
- Added: new features.
- Changed: changes in existing behavior.
- Deprecated: soon-to-be-removed features.
- Removed: features removed in this release.
- Fixed: bug fixes.
- Security: vulnerability fixes.
Testing
Headless regression tests live in tests/run.lua. Add a test whenever you
fix a bug or add an option. If a regression cannot be observed in CI, it
will eventually come back.
Run tests directly:
nvim --headless -u tests/init.lua -i NONE -n +"lua require('tests.run').run()" +qall
Coverage expectations are documented in CLAUDE.md under "Test Coverage
Checklist". Aim to cover absolute and relative jumps, arithmetic
expressions, out-of-bounds clamping, window option restoration, multi-window
state isolation, and fold behavior.
Style
- 2-space indentation, 120-column limit (enforced by
stylua.toml). - No call parentheses for simple calls:
require "numb"notrequire("numb"). - Descriptive local names (
peek_line,cursor_state) over single letters. - Modules addressed as
require('numb.<submodule>'); filenames lowercase with underscores. - Public API and internal state should carry LuaCATS type annotations.
Run ./scripts/check.sh format lint before committing. CI rejects unformatted
code and any lint error.
Design Principles
- Keep dependencies minimal: no runtime requirements outside stock Neovim and Lua.
- Prefer feature flags via
require('numb').setup{...}over global state. - Document every new option in
README.mdand indoc/numb.txt. - Avoid scope creep. Peek behavior should remain unobtrusive and fast.
Releases
Releases are cut by maintainers. The process is documented in CLAUDE.md
under "Release Workflow" (SemVer + annotated tags + GitHub Releases). If you
believe a release is overdue, open an issue rather than a PR.
Questions
If something is unclear, open a draft PR with questions or start a discussion. It is easier to clarify early than to rework a finished implementation.