format.nvim
July 26, 2026 · View on GitHub
format.nvim is an asynchronous code formatting plugin for neovim.
By using nvim_buf_get_lines api, format.nvim is able to format the buffer which has not been saved.
On a formatter success It will update current buffer via nvim_buf_set_lines, and marks, jumps, etc.
are all maintained after formatting.
- Features
- Install
- Configuration
- Usage
- Custom Formatter
- Default Formatters
- How It Works
- Debug
- Self-Promotion
- Feedback
- Credits
Features
- Async — formatters run in background via job.nvim, no UI blocking
- No temp files — buffer content is piped via stdin, unsaved buffers work seamlessly
- Auto-fallback — each filetype has an ordered formatter list; the first available executable wins
- Range formatting — format a visual selection or arbitrary line range
- Buffer lock — optionally lock the buffer during formatting to prevent edit conflicts
- Hooks — run custom
pre/postfunctions before and after formatting - Custom formatters — register your own formatters per filetype via
setup() - Marks preserved — marks, jumps, and folds are maintained after formatting
- Lightweight — minimal dependencies, pure Lua, no LSP required
Install
nvim-plug
require('plug').add({
{
'wsdjeg/format.nvim',
cmds = { 'Format' },
depends = {
{ 'wsdjeg/job.nvim' },
{ 'wsdjeg/notify.nvim' },
},
},
})
lazy.nvim
{
'wsdjeg/format.nvim',
cmd = { 'Format', 'FormatWrite', 'FormatLock', 'FormatWriteLock' },
dependencies = {
'wsdjeg/job.nvim',
'wsdjeg/notify.nvim',
},
config = function()
require('format').setup({})
end,
}
LuaRocks
luarocks install format.nvim
Dependencies:
- job.nvim — async job execution (required)
- notify.nvim — notification display (required)
- logger.nvim — debug logging (optional)
Configuration
require('format').setup({
-- register custom formatters per filetype
custom_formatters = {
lua = {
exe = 'stylua',
args = { '-' },
stdin = true,
},
},
-- timeout in milliseconds (default: 5000)
timeout = 5000,
})
Usage
Commands
| Command | Description |
|---|---|
:Format | Format current buffer or selected range |
:FormatWrite | Format and write the buffer to disk |
:FormatLock | Format with buffer locked during formatting |
:FormatWriteLock | Format + write with buffer locked |
Use ! to force a specific filetype: :Format! python
Use a formatter name to pick one explicitly: :Format stylua
Format on save
local augroup = vim.api.nvim_create_augroup('format_on_save', { clear = true })
vim.api.nvim_create_autocmd({ 'BufWritePre' }, {
pattern = { '*' },
group = augroup,
callback = function(_)
vim.cmd('FormatWrite')
end,
})
Range formatting
In visual mode, select lines and run:
:'<,'>Format
Or specify line numbers directly:
:10,20Format
Embedded code block formatting
Format code blocks inside markdown files using context_filetype.vim:
require('plug').add({
{
'wsdjeg/format.nvim',
config = function()
require('format').setup({
custom_formatters = {
lua = {
exe = 'stylua',
args = { '-' },
stdin = true,
},
},
})
end,
config_before = function()
vim.keymap.set('n', '<leader>lf', function()
local cf = vim.call('context_filetype#get')
if vim.o.filetype == 'markdown' and cf.filetype ~= 'markdown' then
local line1 = cf['range'][1][1]
local line2 = cf['range'][2][1]
vim.cmd(string.format('%s,%sFormat! %s', line1, line2, cf.filetype))
end
end, { silent = true, desc = 'format code block' })
end,
cmds = { 'Format' },
depends = { { 'Shougo/context_filetype.vim' } },
},
})
Before/after format hooks
This feature is inspired by formatter.nvim, instead of using User autocmd, format.nvim uses functions.
require('format').format(bang, user_input, start_line, end_line, {
hooks = {
pre = function(buf) end,
post = function(buf) end,
},
})
Lock buffer when formatting
Using FormatLock or FormatWriteLock commands instead of Format or FormatWrite, format.nvim will lock the buffer when formatting.
Custom Formatter
| name | description | optional/required |
|---|---|---|
exe | string, formatter executable or path | required |
args | table<string>, list of arguments | optional |
stdin | boolean, send data to the stdin of the formatter | optional |
Example — register a custom formatter for TypeScript:
require('format').setup({
custom_formatters = {
typescript = {
exe = 'prettier',
args = { '--stdin-filepath', vim.fn.expand('%:p') },
stdin = true,
},
},
})
Default Formatters
| Language | Formatters | Notes |
|---|---|---|
| C | uncrustify, clang-format, astyle | auto-fallback to first available |
| Lua | stylua | |
| Python | yapf | |
| Rust | rustfmt | |
| Go | gofmt | |
| JSON | prettier | |
| Markdown | prettier | --parser markdown |
| JavaScript | js-beautify | respects shiftwidth() |
| Java | astyle | --mode=java |
| Assembly | asmfmt | |
| Elixir | mix format | mix format - |
| TOML | taplo, topiary | auto-fallback to first available |
How It Works
- Reads buffer lines via
nvim_buf_get_lines(no file save needed) - Finds the first available formatter for the current filetype
- Pipes buffer content to the formatter via stdin (async, via job.nvim)
- Collects formatted output and replaces buffer lines via
nvim_buf_set_lines - Preserves marks, jumps, folds, and undo history
Buffer ──nvim_buf_get_lines──▶ stdin ──▶ formatter ──▶ stdout ──▶ nvim_buf_set_lines──▶ Buffer
Debug
If you want to read the runtime log of format.nvim, you need to install logger.nvim.
require('plug').add({
{
'wsdjeg/format.nvim',
depends = {
{
'wsdjeg/job.nvim',
},
{
'wsdjeg/notify.nvim',
},
{
'wsdjeg/logger.nvim',
},
},
},
})
[ 15:02:25:277 ] [ Info ] [ format.nvim ] using custom formatter:stylua
[ 15:02:25:277 ] [ Info ] [ format.nvim ] running formatter: stylua
[ 15:02:25:331 ] [ Info ] [ format.nvim ] formatter: stylua exit code:0 single:0
[ 15:03:59:481 ] [ Info ] [ format.nvim ] using default formatter:prettier
[ 15:03:59:482 ] [ Info ] [ format.nvim ] running formatter: D:\Scoop\apps\nodejs\current\bin\prettier.CMD
[ 15:04:00:340 ] [ Info ] [ format.nvim ] formatter: prettier exit code:0 single:0
Self-Promotion
Like this plugin? Star the repository on GitHub.
Love this plugin? Follow me on GitHub.
Feedback
If you encounter any bugs or have suggestions, please file an issue in the issue tracker