format.nvim

July 26, 2026 · View on GitHub

Run Tests GitHub License GitHub Issues or Pull Requests GitHub commit activity GitHub Release luarocks

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

  • 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/post functions 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:

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

CommandDescription
:FormatFormat current buffer or selected range
:FormatWriteFormat and write the buffer to disk
:FormatLockFormat with buffer locked during formatting
:FormatWriteLockFormat + 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

namedescriptionoptional/required
exestring, formatter executable or pathrequired
argstable<string>, list of argumentsoptional
stdinboolean, send data to the stdin of the formatteroptional

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

LanguageFormattersNotes
Cuncrustify, clang-format, astyleauto-fallback to first available
Luastylua
Pythonyapf
Rustrustfmt
Gogofmt
JSONprettier
Markdownprettier--parser markdown
JavaScriptjs-beautifyrespects shiftwidth()
Javaastyle--mode=java
Assemblyasmfmt
Elixirmix formatmix format -
TOMLtaplo, topiaryauto-fallback to first available

How It Works

  1. Reads buffer lines via nvim_buf_get_lines (no file save needed)
  2. Finds the first available formatter for the current filetype
  3. Pipes buffer content to the formatter via stdin (async, via job.nvim)
  4. Collects formatted output and replaces buffer lines via nvim_buf_set_lines
  5. 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

Credits